快速排序
快速排序是一种最坏情况时间复杂度为 $\Theta(n^2)$ 的排序算法,但是快速排序在实际排序应用中通常是最好的选择,因为它的平均性能非常好,期望时间复杂度是 $\Theta(n\lg n) $。
快速排序的描述
快速排序使用了分治思想:
- 分解:数组 $A[p .. r]$ 被划分为两个(可能为空)子数组 $A[p..q-1]$ 和 $A[q+1..r]$,使得 $A[p..q-1]$ 中的每一个元素都小于等于 $A[q]$,而 $A[q]$ 也小于等于 $A[q+1.. r]$ 中的每个元素。其中计算下标 $q$ 也是划分过程的一部分。
- 解决:通过递归调用快速排序,对两个子数组进行排序。
- 合并:因为子数组都是原址排序的,所以不需要合并操作。
稳定排序:在排序算法中,相同值的两个元素,在输入数组中先出现的数在输出数组中也先出现。例如:冒泡排序,插入排序,基数排序,归并排序。
原址排序:基本上不需要额外辅助的空间,允许少量额外的辅助变量的排序。如选择排序,插入排序,希尔排序,快速排序,堆排序。而归并排序,计数排序,基数排序不是原址排序。
下面的程序实现快速排序:
1 | def QUICKSORT(A, p, r): |
数组的划分
算法的关键部分是 PARTITION 过程,它实现了对子数组的原址重排。
1 | def PARTITION(A, p, r): |

PARTITION 在子数组 $A[p..r]$ 上的时间复杂度为 $\Theta(n) $ 其中,$n = r-p+1$。
快速排序的性能
快速排序的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快速排序的性能与归并排序一样。如果划分是不平衡的,那么快速排序的性能就接近插入排序了。
最坏情况划分
当划分产生两个子问题分别包含了 $n-1$ 个元素和 0 个元素时,快速排序的最坏情况发生。此时算法的运行时间为:
因此可以得到,此时快速排序的时间复杂度为 $\Theta(n^2)$。
最好情况划分
当划分产生两个子问题分别包含了 $n/2$ 个元素和 $n/2 $ 个元素时,快速排序的最好情况发生。此时算法的运行时间为:
因此可以得到,此时快速排序的时间复杂度为 $\Theta(n \lg n )$。
快速排序的随机化版本
我们可以通过在算法中引入随机性,从而使算法对所有的输入都能获得较好的期望性能。
随机化的快速排序对原始快速排序改动很小,只需要从将 $A[r]$ 作为主元改变为从随机一元素作为主元即可:
1 | def RANDOMIZED_QUICKSORT(A, p, r): |
期望为线性时间的选择算法
利用上述 PARTITION/RANDOMIZED_PARTITION 过程,寻找数组中第 $i$ 小的元素,时间复杂度只需 $\Theta(n) $。
1 | def RANDOMIZED_SELECT(A, p, r, i): |