什么是 排序(sorting) 算法?给定一个 n 个元素的数组:
[A0,A1,...,An−1]
排序算法返回这个序列重新排列的结果
[A0,A1,...,An−1]
满足 A0 ≤ A1 ≤... ≤ An − 1(从小到大排序)或 A0≥A1≥...≥An−1(从大到小排序)。
比如,对于一个数组 [5, 2, 3, 7, 6][5,2,3,7,6],如果实现从小到大排序的算法会返回 [2, 3, 5, 6, 7]。如果实现从大到小排序的算法会返回 [7, 6, 5, 3, 2]。
插入排序:
插入排序的基本思想是:将数组分为已排序的前半部分和待排序的后半部分,每次把待排序部分的第一个元素,插入到已排序部分的对应位置中,直到全部记录都插入到已排序部分中。
- 稳定性:稳定排序;
- 时间复杂度:O(n^2)。
选择排序:
选择排序的思想是:将数组分为已排序的前半部分和待排序的后半部分,每趟从待排序区域选取最小的元素,将其放到已排序区域的最后。因为每趟可以让待排序区域的元素数量减少一个,所以总共需要 n-1 趟操作就可以将整个数组排序完成。
- 稳定性:非稳定排序;
- 时间复杂度:O(n^2)。
冒泡排序:
冒泡排序的基本思想为:假如待排序数组的长度为 nn,从前往后两两比较相邻元素的关键字,若 ai−1>ai,则交换它们,直到数组比较完成。每趟交换以后最后一个元素一定是最大的,不再参与下一趟交换。也就是对于第 i 趟交换,只需要比较到 an−i 即可。直到一趟比较内没有进行交换,算法结束。
- 稳定性:稳定排序;
- 时间复杂度:O(n^2)。
归并排序:
有序数组合并
将两个有序数组a
和b
,合并为一个新的有序数组c
:
方法很简单,用两个变量p1
和p2
分别表示两个数组未被取出的元素中最小元素的下标,初始为 0。然后每次取出a[p1]
和b[p2]
中较小的那个放进新的数组里。如果取出的是a[p1]
,那p1
就自增 1;反之,p2
就自增 1。
如果有数组被取空了,那就把另一个数组剩下的都放进来就可以了。
归并排序
归并排序的基本思想是分治,每次把待排序的区间分成两半,递归地处理。等到左右两部分都是有序后,再进行归并操作让整个数组有序。
- 稳定性:稳定排序;
- 时间复杂度:O(nlogn)。
快速排序:
快速排序是目前应用最广泛的排序算法之一,它的基本思想也是基于分治。每次从待排序区间选取一个元素(选取第一个)作为基准,所有比基准小的元素都在基准的左边,而所有比基准大的元素都在基准的右边。之后分别对基准记录的左边和右边两个区间递归地进行快速排序。
使用p1
和p2
作为扫描的指针,分别从左往右和从右往左扫,直到他们相遇为止。每一轮找到右边第一个小于基准的位置,和左边第一个大于基准的位置,把这两个位置的数交换。
最终循环结束时,p1=p2,并且 [l,p1] 区间内元素都小于等于基准, [p1,r] 区间内的元素都大于等于基准。把基准元素和a[p1]
交换,然后递归处理左边区间和右边区间。
下图是进行一轮快速排序的图示:
- 稳定性:非稳定排序;
- 平均时间复杂度:O(nlogn);
- 最坏时间复杂度:O(n^2)(数组最初有序时)。
排序算法汇总:
算法/类型 | 插入排序 | 选择排序 | 冒泡排序 | 归并排序 | 快速排序 | 桶排序 | 堆排序 |
---|---|---|---|---|---|---|---|
稳定性 | 稳定 | 非稳定 | 稳定 | 稳定 | 非稳定 | 稳定 | 非稳定 |
平均时间复杂度 | O(n2) | )O(n2) | O(n2) | O(nlogn) | O(nlogn) | O(n+m) | O(nlogn) |
注:
- 快速排序的最坏时间复杂度:O(n2)(数组最初有序时)。
- 目前我们需要记住每种排序算法的稳定性和时间复杂度。