Sorting

排序是算法里面一个老生常态的内容,基本是所有算法的第一关。

插入排序

算法思路

最简单的排序莫过于插入排序,需要N-1步,插入排序保证每个步骤0到p是有序的:


插入排序

代码实现

    public static  <AnyType extends Comparable<? super AnyType>> void insertSort(AnyType [] a) {
        int j = 0;

        for(int p = 1; p < a.length; p++) {
            AnyType tmp = a[p];
            for(j=p; j > 0 && tmp.compareTo(a[j - 1]) < 0; j--) {
                a[j] = a[j-1];
            }
            a[j] = tmp;
            System.out.print("After p = " + p);
            System.out.print("\t");
            for(AnyType num : a) {
                System.out.print(num);
                System.out.print('\t');
            }
            System.out.println();
        }
    }

时间复杂度

O(n2)

希尔排序

算法思路

希尔排序是根据他的创造人命名的(Donald Shell),它是第一种打破二次方时间复杂度的算法。我们通常选用N / 2并且逐步递减的方式来分序列的排序。

代码实现

 public static <AnyType extends Comparable<? super AnyType>> void shellSort(AnyType [] a) {
        int j;

        for(int gap = a.length / 2; gap > 0; gap /= 2) {
            for(int i = gap; i < a.length; i++) {
                AnyType tmp = a[i];
                for(j = i; j>=gap && tmp.compareTo(a[j - gap]) < 0; j -= gap) {
                    a[j] = a[j - gap];
                }
                a[j] = tmp;
            }
        }
    }

堆排序

算法思路

堆排序则是使用堆的数据结构来实现排序,可以在O(NlogN)复杂度完成。创建一个堆需要O(N)复杂度,然后通过不断的删除最小元素,将他们放在第二个数组中需要O(logN)复杂度。
但是这样的话需要更多的存储空间,同时消耗的内存也会增加。我们可以在每次删除最小元素后,滑动数组1个位置来避免新的数组的创建。

代码实现

    public static int leftChild(int i) {
        return i * 2 + 1;
    }

    private static <AnyType extends Comparable<? super AnyType>> void percDown(AnyType[] a, int i, int n) {
        int child;
        AnyType tmp;

        for(tmp = a[i]; leftChild(i) < n; i = child) {
            child = leftChild(i);
            if(child != n - 1 && a[child].compareTo(a[child + 1]) < 0) {
                child++;
            }
            if(tmp.compareTo(a[child]) < 0) {
                a[i] = a[child];
            } else {
                break;
            }
        }
        a[i] = tmp;
    }
    public static <AnyType> void swapReferences(AnyType[] a, int index1, int index2) {
        AnyType tmp = a[index1];
        a[index1] = a[index2];
        a[index2] = tmp;
    }

    public static <AnyType extends Comparable<? super AnyType>> void heapsort(AnyType[] a) {
        for(int i = a.length / 2 - 1; i >= 0; i--) {
            percDown(a, i, a.length);
        }
        for( int i = a.length - 1; i > 0; i--) {
            swapReferences(a, 0, i);
            percDown(a, 0, i);
        }
    }

归并排序

算法思路

归并排序能保证最坏复杂度是O(NlogN),它是将两个已排序好的序列合并到一起。可以通过输入数组A、B产生一个输出数组C。通过简单的比较A、B的大小,并将剩余数组复制到输出数组中。
显而易见,归并排序需要N-1次的比较,如果N=1,那么只有一个元素也就是排序完成,然后归并左半部分和右半部分。这种算法是比较典型的分治策略,这个问题并分为若干小问题然后递归的解决它。

代码实现

 private static <AnyType extends Comparable<? super AnyType>> void mergeSort(AnyType[] a, AnyType[] tmpArray, int left, int right) {
        if (left < right) {
            int center = (left + right) / 2;
            mergeSort(a, tmpArray, left, center);
            mergeSort(a, tmpArray, center + 1, right);
            merge(a, tmpArray, left, center + 1, right);
        }
    }

    public static <AnyType extends Comparable<? super AnyType>> void mergeSort(AnyType[] a) {
        AnyType[] tmpArray = (AnyType[]) new Comparable[a.length];
        mergeSort(a, tmpArray, 0, a.length - 1);
    }

    private static <AnyType extends Comparable<? super AnyType>> void merge(AnyType[] a, AnyType[] tmpArray, int leftPos,
                                                                            int rightPos, int rightEnd) {
        int leftEnd = rightPos - 1;
        int tmpPos = leftPos;
        int numElements = rightEnd - leftPos + 1;

        while (leftPos <= leftEnd && rightPos <= rightEnd) {
            if (a[leftPos].compareTo(a[rightPos]) <= 0) {
                tmpArray[tmpPos++] = a[leftPos++];
            } else {
                tmpArray[tmpPos++] = a[rightPos++];
            }
        }

        while (leftPos <= leftEnd) {
            tmpArray[tmpPos++] = a[leftPos++];
        }

        while (rightPos <= rightEnd) {
            tmpArray[tmpPos++] = a[rightPos++];
        }

        for (int i = 0; i < numElements; i++, rightEnd--) {
            a[rightEnd] = tmpArray[rightEnd];
        }
    }

快速排序

算法思路

正如其名,快速排序是一种非常快速的排序算法,它的平均时间复杂度为O(NlogN)。通过高度的内部循环优化,它可以非常高效。它的最坏时间复杂度为O(N2),但通过一些简单的优化就可以极大的避免这种情况的发生。
快速排序很容易被理解,类似于归并排序,快速排序也是一种分治策略。我们可以先来一种简单的方式,任意选择一个元素,然后将它分为三个部分,小于、等于、大于。然后递归的排序第一组和第三组。最后再将他们组合在一起,代码也非常好写

    public static void sort(List<Integer> items) {
        if(items.size() > 1) {
            List<Integer> smaller = new ArrayList<>();
            List<Integer> same = new ArrayList<>();
            List<Integer> larger = new ArrayList<>();
            
            Integer chosenItem = items.get(items.size() / 2);
            for(Integer i : items) {
                if(i < chosenItem) {
                    smaller.add(i);
                } else if(i > chosenItem) {
                    larger.add(i);
                } else {
                    same.add(i)
                }
            }
            sort(smaller);
            sort(larger);
            
            items.clear();
            items.addAll(smaller);
            items.addAll(same);
            items.addAll(larger);
        }
    }

但申请了额外的序列,递归的排序我们感觉这和归并排序差不多,我们需要避免使用额外的内存。
更为经典的快速排序可以这样实现,如果数组S有0或者1个元素直接返回。否则,选一个元素v,我们称它为枢纽。然后将S分成两个不相交的子集小于等于v和大于v,然后在各自的子序列中重复。
理想中,我们希望相等数量的key落在两个分组中。
通常,我们喜欢选择第一个元素作为枢纽,然而当数组已经是有序的时候,我们的时间复杂度会来到指数级。一种安全的方法是随机的选择这个枢纽,但计算机中的随机却不是那么简单。中位数表示数组中第N/2个大的数,它非常适合用来做枢纽,但是获得数组的中位数并不是一件简单的事情,因此我们通常会随机取3个数,然后用他们的中位数来代替。实践中,我们可以选择头尾和中间,然后从这三个数中选择中位数来作为枢纽。

   public static <AnyType extends Comparable<? super AnyType>> void quicksort(AnyType[] a) {
        quicksort(a, 0, a.length - 1);
    }

    private static <AnyType extends Comparable<? super AnyType>> AnyType median3(AnyType[] a, int left, int right) {
        int center = (left + right) / 2;
        if(a[center].compareTo(a[left]) < 0) {
            swapReferences(a, left, center);
        }
        if(a[right].compareTo(a[left]) < 0) {
            swapReferences(a, left, right);
        }
        if(a[right].compareTo(a[center]) < 0) {
            swapReferences(a, center, right);
        }
        swapReferences(a, center, right - 1);
        return a[right - 1];
    }

    private static <AnyType extends Comparable<? super AnyType>> void quicksort(AnyType[] a, int left, int right) {
        if(left <= right) {
            AnyType pivot = median3(a, left, right);

            int i = left;
            int j = right - 1;
            for(;;) {
                while (a[i].compareTo(pivot) < 0) {
                    i++;
                }
                while (a[j].compareTo(pivot) > 0) {
                    j--;
                }
                if(i < j) {
                    swapReferences(a, i, j);
                } else {
                    break;
                }
            }

            swapReferences(a, i, right - 1);
            quicksort(a, left, i - 1);
            quicksort(a, i + 1, right);
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354