算法复习——分而治之篇之快速排序
以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!
1. 从归并排序到快速排序
对归并排序不了解的同学,可阅读算法复习——分而治之篇之归并排序
归并排序:简化分解,侧重合并
快速排序:侧重分解,简化合并
2. 数组划分
快速排序的分解
2.1 基本思想
- 任选元素 x x x作为分界线,称为主元(pivot)
-
交换重排,满足 x x x左侧元素小于右侧
2.2 实现方法
-
选取固定主元 x x x(如尾元素)
-
维护两个部分的右端点变量 i i i, j j j
-
考察数组元素 A [ j ] A[j] A[j],只和主元比较
- 若 A [ j ] ≤ x A[j] \leq x A[j]≤x,则交换 A [ j ] A[j] A[j]和 A [ i + 1 ] A[i+1] A[i+1], i i i和 j j j右移
- 若 A [ j ] > x A[j] > x A[j]>x,则 j j j右移
2.3 伪代码
Partition(A, p, r)
输入:数组 A A A,起始位置 p p p,终止位置 r r r
输出:划分位置 q q q
x ← A[r]
i ← p - 1
for j ← p to r - 1 do
if A[j] <= x then
exchange A[i+1] with A[j]
i ← i + 1
end
end
exchange A[i+1] with A[r]
q ← i + 1
return q
3. 快速排序伪代码
初始调用:QuickSort(A, 1, N)
QuickSort(A, p, r)
输入:数组A,起始位置p,终止位置r
输出:有序数组A
if p < r then
q ← Partition(A, p, r)
QuickSort(A, p, q-1)
QuickSort(A, q+1, r)
end
在复杂度分析时,左右部分的子问题规模不确定,该如何分析时间复杂度呢?
4. 快速排序复杂度分析
4.1 最好情况
数组划分后,每次主元都在中间,就是最好情况。时间复杂度 T ( n ) = 2 T ( n 2 ) + O ( n ) = O ( n l o g n ) T(n)=2T(\frac{n}{2})+O(n)=O(n log n) T(n)=2T(2n)+O(n)=O(nlogn)。
4.2 最坏情况
数组划分后,每次主元都在一侧,就是最坏情况。时间复杂度 T ( n ) = T ( n − 1 ) + T ( 0 ) + O ( n ) = O ( n 2 ) T(n)=T(n-1)+T(0)+O(n)=O(n^2) T(n)=T(n−1)+T(0)+O(n)=O(n2)。
5. 随机划分
5.1 反思最差情况
数组划分时选取固定位置主元,可以针对性构造最差情况
5.2 解决方案
数组划分时选取随机位置主元,无法针对性构造最差情况
5.3 伪代码
Randomized-Partition(A, p, r)
输入:数组A,起始位置p,终止位置r
输出:划分位置q
s ← Random(p, r)
exchange A[s] with A[r]
q ← Partition(A, p, r)
return q
6. 随机化快速排序伪代码
Randomized-QuickSort(A, p, r)
输入:数组A,起始位置p,终止位置r
输出:有序数组A
if p < r then
q ← Randomized-Partition(A, p, r)
Randomized-QuickSort(A, p, q-1)
Randomized-QuickSort(A, q+1, r)
end
7. 随机化快速排序复杂度分析
7.1 分析目标:期望复杂度
计算元素期望比较次数 E [ X ] E[X] E[X]。
基于比较的排序算法,实际上影响排序性能和运行时间的就是它的比较次数,因此期望运行时间,就是期望的比较次数。
7.2 符号表示
- z k z_{k} zk:数组 A A A中第 k k k小的元素
- 集合 S i , j S_{i, j} Si,j: { z i , … , z j } \{z_{i}, \dots, z_{j}\} { zi,…,zj}
7.3 推导过程
如果我们在 S 1 , n S_{1,n} S1,n中选取了 z k z_{k} zk作为主元的话,就将数组一分为三,分成了 s 1 , k − 1 s_{1, k-1} s1,k−1、 z k z_{k} zk和 S k + 1 , n S_{k+1, n} Sk+1,n,如下图所示。
我们可以定义随机变量 X i j X_{ij} Xij为 z i z_{i} zi和 z j z_{j} zj比较的次数,则 E [ X ] = E [ ∑ i = 1 n − 1 ∑ j = i + 1 n X i j ] = ∑ i = 1 n − 1 ∑ j = i + 1 n E [ X i j ] E[X]=E[\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}X_{ij}]=\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}E[X_{ij}] E[X]=E[∑i=1n−1∑j=i+1nXij]=∑i=1n−1∑j=i+1nE[Xij]。那么,关键就是计算 X i j X_{ij} Xij的数学期望。
任何两个变量 z i z_{i} zi和 z j z_{j} zj在整个随机化快速排序的过程中,都可以分成三个时期: S i , j S_{i, j} Si,j划分前, S i , j S_{i,j} Si,j划分时, S i , j S_{i, j} Si,j划分后。这如何理解?
假设 i < j i < j i<j。如果在当前这次随机划分时选取的主元 k < i k < i k<i或 k > j k > j k>j,那么经过这次随机划分后, z i z_{i} zi和 z j z_{j} zj依然在同一个数组中,这都属于 S i , j S_{i,j} Si,j划分前 的时期;直到某一个随机划分时选取的主元 i ≤ k ≤ j i \leq k \leq j i≤k≤j,那么经过这次随机划分后, S i , j S_{i,j} Si,j也一定为三, S i , k − 1 S_{i, k-1} Si,k−1在一个数组中, S k + 1 , j S_{k+1, j} Sk+1,j在另一个数组中,那么 z i z_{i} zi和 z j z_{j} zj就被分开了,这属于 S i , j S_{i,j} Si,j划分时 的时期;之后,就都属于 S i , j S_{i,j} Si,j划分后 的时期了。
由于在每一轮随机划分时,每个元素都只与主元比较,因为在 S i , j S_{i,j} Si,j划分前,选取的主元 z k z_{k} zk要么 k < i k < i k<i,要么 k > j k > j k>j,总之 z i z_{i} zi和 z j z_{j} zj一定不是主元,所以在 S i , j S_{i,j} Si,j划分前 z i z_{i} zi和 z j z_{j} zj 不可能发生比较。
在 S i , j S_{i,j} Si,j划分后 , z i z_{i} zi和 z j z_{j} zj就属于不同的数组了,就再也不可能发生比较了。
在 S i , j S_{i,j} Si,j划分时 , z i z_{i} zi和 z j z_{j} zj就有可能发生比较了。还是那句话,在每一轮随机划分时,每个元素都只与主元比较,所以 z i z_{i} zi和 z j z_{j} zj发生比较的可能性 就是 z i z_{i} zi或 z j z_{j} zj被选为主元的可能性 。
综上所述,
E [ X i j ] = P r { 在 S i , j 划 分 时 z i 或 z j 被 选 为 主 元 } = P r { z i 是 主 元 } + P r { z j 是 主 元 } = 1 j − i + 1 + 1 j − i + 1 = 2 j − i + 1 E[X_{ij}] =Pr\{在S_{i, j}划分时z_{i}或z_{j}被选为主元\}\\ =Pr\{z_{i}是主元\}+Pr\{z_{j}是主元\}\\ =\frac{1}{j-i+1}+\frac{1}{j-i+1}\\ =\frac{2}{j-i+1} E[Xij]=Pr{
在Si,j划分时zi或zj被选为主元}=Pr{
zi是主元}+Pr{
zj是主元}=j−i+11+j−i+11=j−i+12
在得到 E [ X i j ] E[X_{ij}] E[Xij]后,就可以将其代入计算 E [ x ] E[x] E[x]:
E [ X ] = ∑ i = 1 n − 1 ∑ j = i + 1 n 2 j − i + 1 = ∑ i = 1 n − 1 ∑ k = 1 n − i 2 k + 1 < ∑ i = 1 n − 1 ∑ k = 1 n 2 k = ∑ i = 1 n − 1 O ( l o g n ) = O ( n l o g n ) E[X] =\sum_{i=1}^{n-1} \sum_{j=i+1}^{n}\frac{2}{j-i+1}\\ =\sum_{i=1}^{n-1}\sum_{k=1}^{n-i}\frac{2}{k+1}\\ <\sum_{i=1}^{n-1}\sum_{k=1}^{n}\frac{2}{k}\\ =\sum_{i=1}^{n-1}O(logn)\\ =O(nlogn) E[X]=i=1∑n−1j=i+1∑nj−i+12=i=1∑n−1k=1∑n−ik+12<i=1∑n−1k=1∑nk2=i=1∑n−1O(logn)=O(nlogn)
其中, k = j − i k=j-i k=j−i,并且用到了调和级数 ∑ k = 1 n 1 k = O ( l o g n ) \sum_{k=1}^{n}\frac{1}{k}=O(logn) ∑k=1nk1=O(logn)。
所以,随机化快速排序的期望时间复杂度是 O ( n l o g n ) O(n log n) O(nlogn)。
8. 排序算法比较
算法名称 | 时间复杂度 |
---|---|
选择排序 | O ( n 2 ) O(n^2) O(n2) |
插入排序 | O ( n 2 ) O(n^2) O(n2) |
归并排序 | O ( n l o g n ) O(nlogn) O(nlogn) |
快速排序 | 最差: O ( n 2 ) O(n^2) O(n2);最好: O ( n l o g n ) O(nlogn) O(nlogn);期望: O ( n l o g n ) O(nlogn) O(nlogn) |
基于比较的排序,时间复杂度下界为 Ω ( n l o g n ) \Omega(nlogn) Ω(nlogn)。