八大排序算法的代码实现和时空复杂度比较。

排序法 平均时间 最差情形 稳定度 额外空间 备注
⭐插入 $O(n^2)$ $O(n^2)$ 稳定 $O(1)$ 大部分已排序时较好
⭐冒泡 $O(n^2)$ $O(n^2)$ 稳定 $O(1)$ $n$小时较好
⭐选择 $O(n^2)$ $O(n^2)$ 不稳定 $O(1)$ $n$小时较好
⭐快排 $O(nlogn)$ $O(n^2)$ 不稳定 $O(nlogn)$ $n$大时较好
⭐堆 $O(nlogn)$ $O(nlogn)$ 不稳定 $O(1)$ $n$大时较好
⭐归并 $O(nlogn)$ $O(nlogn)$ 稳定 $O(1)$ $n$大时较好
基数 $O(logRB)$ $O(logRB)$ 稳定 $O(1)$ $B$是真数(0-9),$R$是基数(个十百)
希尔 $O(nlogn)$ $O(ns) 1<s<2$ 不稳定 $O(1)$ $s$是所选分组

插入

将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

插入排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
  * 从第二个元素开始,插入到前面已经有序的序列中,即找到序列中第一个小于等于当前元素的,将这以后的元素都
  * 向后移一位,然后将当前元素插入到这个位置中
  *
  * @param arr
*/
public void insertionSort(int[] arr) {
    /*
       0 1 2 ...i-1 i i+1 ... n-1
       对于下标 i,[0, i-1]此时已经有序,将i插入到[0, i-1]中
       具体操作是找到i应该插入的下标idx,将[idx, i-1]的元素后移,
       将i元素放到idx+1(最后一个比新元素更大的位置是下标idx,但是循环中idx最后一次还是会往左减一
       因此要放到idx+1处
     */
    int n = arr.length;
    for (int i = 0; i < n; i++) {
        int cur_val = arr[i];
        int idx = i - 1;
        while (idx >= 0 && arr[idx] > cur_val) {
            arr[idx + 1] = arr[idx];
            idx--;
        }
        arr[idx + 1] = cur_val;
    }
}

冒泡

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
  * 一共n个元素,每轮将一个元素冒泡到最后的位置
  * 对于第i轮,需要冒泡一个元素到第n-1-i的位置,因此需要从[0,1],[1,2], ..., [n-1-i-1,n-1-i]每次将较大的元素往后放
  * 为了优化已经有序的情况,可以设置flag记录是否已经有序
  * 如果当轮存在交换则将flag置为false表示未有序,如果flag为true说明前面的元素已经有序了
  *
  * @param arr
*/
    public void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n; i++) {       // 每轮将一个最大的元素沉到最后
            boolean flag = true;            // 当前轮是否有序
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    flag = false;           // 未有序
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;   
                }
            }
            if (flag) break;                // 有序
        }
    }

选择

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

选择排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/**
  * 对于每个位置我们插入其应当有的元素,即对于第0位置的元素,我们找到最小的然后和这个位置的元素进行交换
  *
  * @param arr
*/
public void selectionSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n; i++) {
        int min_index = i;
        for (int j = i; j < n; j++) {
            if (arr[j] < arr[min_index]) {
                min_index = j;
            }
        }
        int temp = arr[i];
        arr[i] = arr[min_index];
        arr[min_index] = temp;
    }
}

快排

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

快速排序

递归版本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void quickSort(int[] arr) {
    quickSortRange(arr, 0, arr.length - 1);
}

public void quickSortRange(int[] arr, int left, int right) {
    if (left < right) {
        int mid = partition(arr, left, right);
        quickSortRange(arr, left, mid - 1);
        quickSortRange(arr, mid + 1, right);
    }

}

private int partition(int[] arr, int left, int right) {
    int pivot = arr[left];
    while (left < right) {
        while (left < right && arr[right] >= pivot) {
            right--;
        }
        arr[left] = arr[right];
        while (left < right && arr[left] <= pivot) {
            left++;
        }
        arr[right] = arr[left];
    }
    arr[left] = pivot;
    return left;
}

非递归版本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
    public void quickSort(int[] nums) {
        stackQuickSort(nums, 0, nums.length - 1);
    }

    public void stackQuickSort(int[] nums, int left, int right) {
        Deque<Integer> stack = new LinkedList<>();
        if (left < right) {
            stack.push(right);
            stack.push(left);
        }
        while (!stack.isEmpty()) {
            int l = stack.pop();
            int r = stack.pop();
            int mid = partition(nums, l, r);
            // 得到mid后划分为 [l, mid - 1] mid [mid + 1, r]
            if (l < mid - 1) {
                stack.push(mid - 1);
                stack.push(l);
            }
            if (r > mid + 1) {
                stack.push(r);
                stack.push(mid + 1);
            }
        }
    }

    private int partition(int[] nums, int left, int right) {
        int pivot = nums[left];
        while (left < right) {
            while (left < right && nums[right] >= pivot) {
                right--;
            }
            nums[left] = nums[right];
            while (left < right && nums[left] <= pivot) {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public void heapSort(int[] arr) {
    int len = arr.length;

    buildMaxHeap(arr, len);

    for (int i = len - 1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0, len);
    }
}

private void buildMaxHeap(int[] arr, int len) {
    for (int i = len / 2 - 1; i >= 0; i--) {
        heapify(arr, i, len);
    }
}

private void heapify(int[] arr, int i, int len) {
    while (true) {
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int largest = i;

        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }

        if (right < len && arr[right] > arr[largest]) {
            largest = right;
        }

        if (largest == i) break;
        swap(arr, i, largest);
        i = largest;
    }
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

归并

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
	* 归并排序
  * @param arr
  * @return
*/
public int[] mergeSort(int[] arr) {
    if (arr == null || arr.length <= 1) return arr;
    int n = arr.length;
    int mid = n / 2;
    int[] left = Arrays.copyOfRange(arr, 0, mid);
    int[] right = Arrays.copyOfRange(arr, mid, n);
    return merge(mergeSort(left), mergeSort(right));
}

private int[] merge(int[] a, int[] b) {
    int[] res = new int[a.length + b.length];
    int i = 0, j = 0, ind = 0;
    while (i < a.length && j < b.length) {
        res[ind++] = (a[i] < b[j]) ? a[i++] : b[j++];
    }
    while (i < a.length) {
        res[ind++] = a[i++];
    }
    while (j < b.length) {
        res[ind++] = b[j++];
    }
    return res;
}

希尔

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/**
	* 希尔排序,减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
	* @param arr
*/
public void shellSort(int[] arr) {
    int n = arr.length;
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // {i, i+gap, i+2gap, ...}
        for (int i = 0; i < n; i++) {
            int cur_val = arr[i];
            int idx = i - gap;
            while (idx >= 0 && arr[idx] > cur_val) {
                arr[idx + gap] = arr[idx];
                idx -= gap;
            }
            arr[idx + gap] = cur_val;
        }
    }
}