Sorting1 - Comparison based

Bubble Sort

算法

第一轮从第一个元素开始,两两比较,如果左大右小则交换,这样一轮下来,最右边的值最大,和名字里的冒泡一样;第二轮从第一个到第 n − 1 n-1 n1个,依此类推。

代码

public static void bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

复杂度

时间复杂度:没有比较可以省略。
Best case: O ( n 2 ) O(n^2) O(n2)
Worst case: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)

Selection Sort

算法

先找到从左到右的最小元素,交换到 n u m s [ 0 ] nums[0] nums[0]的位置;第二次遍历总左侧第二到最右找到最小元素,交换到 n u m s [ 1 ] nums[1] nums[1]的位置;重复直到全部元素排序。
因为涉及到远距离交换所以不稳定。

代码

public static void selectionSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        int min = Integer.MAX_VALUE;
        int index = 0;
        for (int j = i; j < arr.length; j++) {
            if (arr[j] < min) {
                min = arr[j];
                index = j;
            }
        }
        int temp = arr[i];
        arr[i] = min;
        arr[index] = temp;
    }
}

复杂度

时间复杂度:任何情况都需要遍历所有元素寻找最小。
Best case: O ( n 2 ) O(n^2) O(n2)
Worst case: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)

Insertion Sort

算法

在数列中维持一个结构,index < i的数已经完成排序,这时将 n u m s [ i + 1 ] nums[i+1] nums[i+1]不断与左边的数比较,找到比左边数大的位置即停止。

例子:
0 , 3 , 4 ∣ 1 , 5 , 7 0,3,4|1,5,7 0,3,41,5,7 i = 3 i = 3 i=3

1 < 4 1 < 4 1<4则交换 -> 0 , 3 , 1 ∣ 4 , 5 , 7 0,3,1|4,5,7 0,3,14,5,7
1 < 3 1 < 3 1<3则交换 -> 0 , 1 , 3 ∣ 4 , 5 , 7 0,1,3|4,5,7 0,1,34,5,7
1 > 0 1 > 0 1>0则停止,把隔板右移(i++) -> 0 , 1 , 3 , 4 ∣ 5 , 7 0,1,3,4|5,7 0,1,3,45,7

代码

做了一点点更改,没有一直交换,记录下要插入的数字,顺移左侧需要移动的数字,最后把要插入的数字放在对应的位置。

public static void insertionSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        int temp = arr[i];
        int j;
        for (j = i; j >= 1 && arr[j - 1] > temp; j--) {
            arr[j] = arr[j - 1];
        }
        arr[j] = temp;
    }
}

复杂度

时间复杂度:
Best case: O ( n ) O(n) O(n),数组本来已经排序时每个数只需要往左比较一次即停止。
Worst case: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)

Heap Sort

算法

利用最大堆的数据结构特点,每个节点的值都大于两个子节点,排序就是把所有数放进堆,再一直取出顶端值。

堆可以通过数组进行表示, n u m s [ i ] nums[i] nums[i]的两个子节点为 n u m s [ 2 i + 1 ] nums[2i + 1] nums[2i+1] n u m s [ 2 i + 2 ] nums[2i+2] nums[2i+2]
加入元素时,比较 n u m s [ i ] nums[i] nums[i]和子节点的大小,如果小的话则往下交换(保持堆的性质)。
去除最大元素时,将根节点和最后一个叶节点交换,根节点再向下交换,重新达到最大堆的条件。

代码

public static void heapSort(int[] arr) {
    for (int i = arr.length / 2 - 1; i >= 0; i--) { // all nodes that are not leaves
        bubbleDown(arr, i, arr.length);
    }
    for (int i = arr.length - 1; i >= 0; i--) {
        int temp = arr[i];
        arr[i] = arr[0]; // arr[0] is the biggest, exchange it to the back
        arr[0] = temp;
        bubbleDown(arr, 0, i); // arr length is i since arr[i] was thought as off-heap now
    }
}

public static void bubbleDown(int[] arr, int i, int len) {
    int index = i;
    if (2 * i + 1 < len && arr[index] < arr[2 * i + 1]) {
        index = 2 * i + 1;
    }
    if (2 * i + 2 < len && arr[index] < arr[2 * i + 2]) {
        index = 2 * i + 2;
    }
    if (index != i) {
        // exchange with the bigger one
        int temp = arr[index];
        arr[index] = arr[i];
        arr[i] = temp;
        bubbleDown(arr, index, len); // e.g. 1 -> 2 -> 4 to  2 -> 1 -> 4 so recur on 1 again
    }
}

复杂度

时间复杂度:
Best case: O ( n l o g n ) O(nlogn) O(nlogn)
Worst case: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度: O ( 1 ) O(1) O(1) (bubble down操作只是对数组进行In-place操作)

Merge Sort

算法

分治法的应用。
每次将数组对半分,分别进行排序,base case为数组长度为0或1时直接返回。

代码

public static void mergeSort(int[] arr, int l, int r) {
    if (l >= r) {
        return;
    }
    int m = (l + r) / 2;

    mergeSort(arr, l, m);
    mergeSort(arr, m + 1, r);

    merge(arr, l, m, r);
}

public static void merge(int[] arr, int l, int m, int r) {

    int[] left = Arrays.copyOfRange(arr, l, m + 1);
    int[] right = Arrays.copyOfRange(arr, m + 1, r + 1);

    int i = 0, j = 0, k = l;

    while (i < left.length && j < right.length) {
        if (left[i] <= right[j])
            arr[k++] = left[i++];
        else {
            arr[k++] = right[j++];
        }
    }
    while (i < left.length)
        arr[k++] = left[i++];

    while (j < right.length)
        arr[k++] = right[j++];
}

复杂度

时间复杂度:根据递归树深度
Worst case: O ( l o g n ) O(logn) O(logn)
Best case: O ( l o g n ) O(logn) O(logn)

空间复杂度: O ( n ) = O ( n ) + O ( l o g n ) O(n) = O(n) + O(logn) O(n)=O(n)+O(logn)(recursion)
不是 O ( n l o g n ) O(nlogn) O(nlogn)是因为实际上计算是并行发生的,参考以下链接:
merge-sort-time-and-space-complexity

QuickSort

算法

选择一个数作为pivot,遍历数组,把比pivot小的放在一遍,比pivot大的放另外一边,再对大小两个数组递归排序。当pivot选择为中位数时,相当于In-place MergeSort.

例子:
5 , 7 , 0 , 6 , 1 , 9 5,7,0,6,1,9 5,7,0,6,1,9, p i v o t = 5 ( i n d e x = 0 ) pivot = 5 (index = 0) pivot=5(index=0)

Step1. 交换 n u m s [ 0 ] nums[0] nums[0] n u m s [ i n d e x ] nums[index] nums[index]
5 , 7 , 0 , 6 , 1 , 9 5,7,0,6,1,9 5,7,0,6,1,9

Step2. 初始化 i = 1 i = 1 i=1 i i i为小于和等于分界, j j j为等于和大于分界。
遍历 j = 1 , 2 , . . . j=1, 2, ... j=1,2,...
j = 1 j=1 j=1,放数字 7 7 7
5 ∣ 7 , 0 , 6 , 1 , 9 5|7,0,6,1,9 57,0,6,1,9 i = 1 i = 1 i=1
j = 2 j=2 j=2,放数字 0 0 0
5 , 0 ∣ 7 , 6 , 1 , 9 5,0|7,6,1,9 5,07,6,1,9 i = 2 i = 2 i=2
j = 3 j=3 j=3,放数字 6 6 6
5 , 0 ∣ 7 , 6 , 1 , 9 5,0|7,6,1,9 5,07,6,1,9 i = 2 i = 2 i=2
j = 4 j=4 j=4,放数字 1 1 1
5 , 0 , 1 ∣ 6 , 7 , 9 5,0,1|6,7,9 5,0,16,7,9 i = 3 i = 3 i=3
j = 5 j=5 j=5,放数字 9 9 9
5 , 0 , 1 ∣ 6 , 7 , 9 5,0,1|6,7,9 5,0,16,7,9 i = 3 i = 3 i=3

Step3. 交换 n u m s [ 0 ] nums[0] nums[0] n u m s [ i − 1 ] nums[i-1] nums[i1]
Step4. 递归排序子序列

常见pivot选择:

  1. 选择第一个元素,但在已经排序时不能很好的分割数据表现较差
  2. 随机选择
  3. 取第一个,最后一个,中间一个的中位数

代码

public class QuickSort {
    public static void main(String[] args) {
        int[] test = new int[]{5, 7, 0, 6, 1, 9};
        Utils.printarr(test);
        quicksort(test, 0, test.length - 1);
        Utils.printarr(test);
    }

    public static void quicksort(int[] arr, int l, int r) {
        // System.out.println(l+" "+r);
        if (l >= r) {
            return;
        }
        int pivot = arr[l];

        int i = l + 1;
        for (int j = l + 1; j <= r; j++) {
            if (arr[j] < pivot) {
                swap(arr, i, j);
                i++;
            }
        }
        swap(arr, l, i - 1);
        quicksort(arr, l, i - 2);
        quicksort(arr, i, r);
    }

    public static void swap(int[] arr, int l, int r) {
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }
}
Output:
5 7 0 6 1 9 
0 1 5 6 7 9 

复杂度

参考:quicksort

时间复杂度:
Worst case: O ( n 2 ) O(n^2) O(n2) (数组已经排序,pivot一直选第一个数)
Best case: O ( n l o g n ) O(nlogn) O(nlogn)
Average case: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度:
Worst case: O ( 1 ) O(1) O(1)
Best case: O ( l o g n ) O(logn) O(logn)
由递归深度决定。

3-Way QuickSort

算法

普通的Quicksort在处理有很多重复元素的情况下效果不佳,所以3-Way的想法是添加一类,与pivot相等的元素集合。
在QuickSort时记录一个大小分界 i i i,不断把比pivot小的数从 i i i右侧搬到 i i i左侧。
算法更改为记录两个分界,一个记录小于和等于的分界,一个记录等于和大于的边界。

例子:
0 , 7 , 5 , 1 , 5 , 9 , 3 , 5 , 5 0,7,5,1,5,9,3,5,5 0,7,5,1,5,9,3,5,5, p i v o t = 5 ( i n d e x = 2 ) pivot = 5 (index = 2) pivot=5(index=2)

Step1. 交换 n u m s [ 0 ] nums[0] nums[0] n u m s [ i n d e x ] nums[index] nums[index]
0 , 7 , 5 , 1 , 5 , 9 , 3 , 5 , 5 0,7,5,1,5,9,3,5,5 0,7,5,1,5,9,3,5,5

Step2. 初始化 i = j = 1 i = j = 1 i=j=1 i i i为小于和等于分界, j j j为等于和大于分界。
遍历 k = 1 , 2 , . . . k=1, 2, ... k=1,2,...
k = 1 k=1 k=1,放数字 7 7 7
5 ∣ ∣ 7 , 0 , 1 , 5 , 9 , 3 , 5 , 5 5||7,0,1,5,9,3,5,5 57,0,1,5,9,3,5,5 i = 1 , j = 1 i = 1, j = 1 i=1,j=1
k = 2 k=2 k=2,放数字 0 0 0
5 , 0 ∣ ∣ 7 , 1 , 5 , 9 , 3 , 5 , 5 5,0||7,1,5,9,3,5,5 5,07,1,5,9,3,5,5 i = 2 , j = 2 i = 2, j = 2 i=2,j=2
k = 3 k=3 k=3,放数字 1 1 1
5 , 0 , 1 ∣ ∣ 7 , 5 , 9 , 3 , 5 , 5 5,0,1||7,5,9,3,5,5 5,0,17,5,9,3,5,5 i = 3 , j = 3 i = 3, j = 3 i=3,j=3
k = 4 k=4 k=4,放数字 5 5 5
5 , 0 , 1 ∣ 5 ∣ 7 , 9 , 3 , 5 , 5 5,0,1|5|7,9,3,5,5 5,0,157,9,3,5,5 i = 3 , j = 4 i = 3, j = 4 i=3,j=4
k = 5 k=5 k=5,放数字 9 9 9
5 , 0 , 1 ∣ 5 ∣ 7 , 9 , 3 , 5 , 5 5,0,1|5|7,9,3,5,5 5,0,157,9,3,5,5 i = 3 , j = 4 i = 3, j = 4 i=3,j=4
k = 6 k=6 k=6,放数字 3 3 3
5 , 0 , 1 , 3 ∣ 5 ∣ 7 , 9 , 5 , 5 5,0,1,3|5|7,9,5,5 5,0,1,357,9,5,5 i = 4 , j = 5 i = 4, j = 5 i=4,j=5
k = 7 k=7 k=7,放数字 5 5 5
5 , 0 , 1 , 3 ∣ 5 , 5 ∣ 9 , 7 , 5 5,0,1,3|5,5|9,7,5 5,0,1,35,59,7,5 i = 4 , j = 6 i = 4, j = 6 i=4,j=6
k = 8 k=8 k=8,放数字 5 5 5
5 , 0 , 1 , 3 ∣ 5 , 5 , 5 ∣ 7 , 9 5,0,1,3|5,5,5|7,9 5,0,1,35,5,57,9 i = 4 , j = 7 i = 4, j = 7 i=4,j=7

Step3. 交换 n u m s [ 0 ] nums[0] nums[0] n u m s [ i − 1 ] nums[i-1] nums[i1]
3 , 0 , 1 , 5 ∣ 5 , 5 , 5 ∣ 7 , 9 3,0,1,5|5,5,5|7,9 3,0,1,55,5,57,9
分割板 i i i减小1后得
3 , 0 , 1 ∣ 5 , 5 , 5 , 5 ∣ 7 , 9 3,0,1|5,5,5,5|7,9 3,0,15,5,5,57,9

Step4. 递归排序子序列

代码

public class QuickSort {
    public static void main(String[] args) {
        int[] test = new int[]{5,7,0,1,5,9,3,5,5};
        Utils.printarr(test);
        threeWayQuicksort(test, 0, test.length - 1);
        Utils.printarr(test);
    }

    public static void threeWayQuicksort(int[] arr, int l, int r) {
        if (l >= r) {
            return;
        }
        int pivot = arr[l];

        int i = l + 1, j = l + 1;
        for (int k = l + 1; k <= r; k++) {
            if (arr[k] == pivot) {
                swap(arr, j, k);
                j++;
            } else if (arr[k] < pivot) {
                swap(arr, j, k);
                swap(arr, i, j);
                i++;
                j++;
            }
        }
        swap(arr, l, i - 1);
        threeWayQuicksort(arr, l, i - 2);
        threeWayQuicksort(arr, j, r);
    }
    public static void swap(int[] arr, int l, int r) {
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }
}
Output: 
5 7 0 1 5 9 3 5 5 
0 1 3 5 5 5 5 7 9

复杂度

时间复杂度:
Average: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度:
Average: O ( l o g n ) O(logn) O(logn)

Shell Sort

参考:
https://en.wikipedia.org/wiki/Shellsort

算法

可以看作一个分组的Insertion Sort。

先是一些符号定义:
g a p gap gap s e q u e n c e sequence sequence: 一列递减的数,值为每轮的gap,最后一个数为1
h − s o r t h-sort hsort: 对任意 i i i,子列 { a i , a i + h , a i + 2 h , . . . } \{a_{i}, a_{i+h}, a_{i+2h}, ...\} {ai,ai+h,ai+2h,...}有序

排序过程:
g a p gap gap s e q u e n c e sequence sequence中依次选择 g a p = h gap = h gap=h,对 { a i , a i + h , a i + 2 h , . . . } \{a_{i}, a_{i+h}, a_{i+2h}, ...\} {ai,ai+h,ai+2h,...}进行插入排序,直至 g a p = 1 gap = 1 gap=1时全部排序。该排序的大意是首先通过长距离的交换,来减少后续短距离交换时的逆序数。

常用的 g a p gap gap s e q u e n c e sequence sequence:

  1. 2 k + 1 2^k + 1 2k+1
  2. 4 k + 3 ⋅ 2 k − 1 + 1 {4^{k}+3\cdot 2^{k-1}+1} 4k+32k1+1
  3. 1 , 4 , 10 , 23 , 57 , 132 , 301 , 701 1, 4, 10, 23, 57, 132, 301, 701 1,4,10,23,57,132,301,701 (From experiment, no formula)

代码

public class ShellSort {
    public static void main(String[] args) {
        int[] test = new int[]{1, 4, 10, 3, 5, 17, 7, 2, 6, 8, 20, 15, 32, 9, 13};
        sort(test, new int[] {10, 4, 1});
        for (int i : test) {
            System.out.print(i + " ");
        }
    }

    public static void sort(int[] nums, int[] gapSeq) {
        for (int h : gapSeq) {
            hsort(nums, h);
        }
    }

    public static void hsort(int[] nums, int h) {
        int temp;
        for (int i = 0; i < h; i++) { // h subsequences to sort
            for (int j = i + h; j < nums.length; j += h) { // go through numbers in subsequence
                temp = nums[j];
                int k;
                for (k = j; k >= h && temp < nums[k - h]; k -= h) {
                    nums[k] = nums[k - h]; // shift right
                }
                nums[k] = temp; // insert nums[j]
            }
        }
    }
}
Output: 1 2 3 4 5 6 7 8 9 10 13 15 17 20 32

算法正确性

定理:一个 h − s o r t h-sort hsort的子列在进行 k − s o r t k-sort ksort后依然是一个 h − s o r t h-sort hsort子列。
反证法:假设原来 a i < a i + h a_{i} < a_{i + h} ai<ai+h, k − s o r t k-sort ksort结束变成了 a i ′ > a i + h ′ a'_{i} > a'_{i + h} ai>ai+h,
如果是假设 a i = a i ′ a_{i} = a'_{i} ai=ai a i + h a_{i + h} ai+h发生了变化,有两种可能,
第一种它插入到了前面 a i + h − n k a_{i + h - nk} ai+hnk的位置上,此时就有
a i + h = a i + h − n k ′ < a i + h ′ < a i ′ = a i a_{i + h} = a'_{i + h - nk} < a'_{i + h} < a'_{i} = a_{i} ai+h=ai+hnk<ai+h<ai=ai,矛盾;
第二种是它被后面的 a i + h + m k a_{i+h+mk} ai+h+mk插入到了当前位置,此时有
a i + h + m k = a i + h ′ a_{i+h+mk} = a'_{i + h} ai+h+mk=ai+h,
a i a_{i} ai不动说明 a i < a i + n k a_{i} < a_{i+nk} ai<ai+nk对任意 n n n成立(否则被插入),
则有 a i + n k > a i = a i ′ > a i + h ′ = a i + h + m k a_{i+nk} > a_{i} = a'_{i} > a'_{i + h} = a_{i+h+mk} ai+nk>ai=ai>ai+h=ai+h+mk,
n = m n = m n=m,得到原来数组不为 h − s o r t h-sort hsort,矛盾。
a i a_{i} ai发生变化的情况类似。

由以上定理可知最后数组为 1 − s o r t 1-sort 1sort子列,即已排序。

复杂度

依赖于 g a p gap gap s e q u e n c e sequence sequence的选择。

时间复杂度:(和Insertion一样)
Best case: O ( n ) O(n) O(n)
Worst case: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)

总结

名称BestWorstStableMemory
Bubble Sort O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)Y O ( 1 ) O(1) O(1)
Selection Sort O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)N O ( 1 ) O(1) O(1)
Insertion Sort O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)Y O ( 1 ) O(1) O(1)
Heap Sort O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)N O ( 1 ) O(1) O(1)
Merge Sort O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)Y O ( n ) O(n) O(n)
QuickSort O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2)N O ( l o g n ) O(logn) O(logn)
Shell Sort O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)N O ( 1 ) O(1) O(1)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值