笔记
本节给出快速排序运行时间的详细分析。
(1) 最坏情况运行时间
假设
T
(
n
)
T(n)
T(n)是最坏情况下QUICKSORT的运行时间,那么
T
(
n
)
T(n)
T(n)满足以下递归式
因为调用PARTITION生成的2个子数组的长度加起来为
n
−
1
n-1
n−1,因此上式中参数
q
q
q的变化范围是
0
n
−
1
0 ~ n-1
0 n−1。我们用代入法来证明
T
(
n
)
=
Θ
(
n
2
)
T(n) = Θ(n^2)
T(n)=Θ(n2)。
先证明
T
(
n
)
=
O
(
n
2
)
T(n) = O(n^2)
T(n)=O(n2)。假设
T
(
n
)
≤
c
n
2
T(n) ≤ cn^2
T(n)≤cn2,其中
c
c
c为一个常数。
我们先看初始情况
n
=
1
n = 1
n=1。此时有
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
T(1) = 2T(0) + Θ(1)
T(1)=2T(0)+Θ(1)。
T
(
0
)
T(0)
T(0)为常数时间,显然只要
c
c
c足够大,就能使得
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
≤
c
•
1
2
T(1) = 2T(0) + Θ(1) ≤ c•1^2
T(1)=2T(0)+Θ(1)≤c•12成立。
现在进行数学归纳。假设
T
(
n
)
≤
c
n
2
T(n) ≤ cn^2
T(n)≤cn2对
1
,
2
,
…
,
n
−
1
1, 2, …, n-1
1,2,…,n−1都成立。于是有
在
q
∈
[
0
,
n
−
1
]
q ∈ [0, n-1]
q∈[0,n−1]范围内,表达式
q
2
+
(
n
−
q
−
1
)
2
q^2+(n-q-1)^2
q2+(n−q−1)2在
q
=
0
q = 0
q=0或
q
=
n
−
1
q = n-1
q=n−1时取得最大值,并且最大值为
(
n
−
1
)
2
(n-1)^2
(n−1)2。因此
如果
c
c
c足够大,使得
c
(
2
n
−
1
)
c(2n-1)
c(2n−1)大于
Θ
(
n
)
Θ(n)
Θ(n),就可以使得
T
(
n
)
≤
c
n
2
T(n) ≤ cn^2
T(n)≤cn2成立。
综上所述,只要选取足够大的
c
c
c,就可以使得
T
(
n
)
≤
c
n
2
T(n) ≤ cn^2
T(n)≤cn2对所有
n
n
n的取值都成立。因此,
T
(
n
)
=
O
(
n
2
)
T(n) = O(n^2)
T(n)=O(n2)成立。
我们同样可以证明
T
(
n
)
=
Ω
(
n
2
)
T(n) = Ω(n^2)
T(n)=Ω(n2)(见练习7.4-1)。因此,快速排序的最坏情况运行时间为
Θ
(
n
2
)
Θ(n^2)
Θ(n2)。
(2) 期望运行时间
快速排序的运行时间实际上取决于元素之间比较的次数,我们假设总的比较次数为
X
X
X。我们假设一个数组
Z
Z
Z中的元素从小到大依次为
z
1
,
z
2
,
…
,
z
n
z_1, z_2, …, z_n
z1,z2,…,zn,并用
Z
i
j
Z_{ij}
Zij表示
z
i
z_i
zi与
z
j
z_j
zj之间的元素集合
z
i
,
z
i
+
1
,
…
,
z
j
{z_i, z_{i+1}, …, z_j}
zi,zi+1,…,zj。我们要考察任意
2
2
2个元素
z
i
z_i
zi与
z
j
z_j
zj什么时候会进行比较。
首先我们可以断言,每一对元素至多比较一次。因为在PARTITION调用过程中,每个元素只会与选出来的划分主元进行比较,并且比较结束后,这个被选出来的划分主元就会被放置到正确的位置,在之后递归调用PARTITION过程中,这个划分元素就不会再参与比较了。
我们用
X
i
j
X_{ij}
Xij表示元素
z
i
z_i
zi与
z
j
z_j
zj的比较次数。根据上面的分析,
X
i
j
X_{ij}
Xij是一个随机变量,并且只可能有
2
2
2个取值:
0
0
0和
1
1
1。换言之,
X
i
j
X_{ij}
Xij是一个指示器随机变量。
由随机变量
X
i
j
X_{ij}
Xij,我们可以很容易得到总的比较次数
我们要计算快速排序的期望运行时间,也就是要计算总的比较次数
X
X
X的期望值
E
[
X
]
E[X]
E[X],于是有
其中
P
r
Pr
Pr{
z
i
z_i
zi与
z
j
z_j
zj进行比较} 是
z
i
z_i
zi与
z
j
z_j
zj进行比较的概率。
我们现在来分析任意
2
2
2个元素
z
i
z_i
zi与
z
j
z_j
zj会进行比较的概率。我们假设数组中每个元素都是互异的。如果在包含
Z
i
j
Z_{ij}
Zij中的所有元素的一次PARTITION调用中,一旦满足
z
i
<
x
<
z
j
z_i < x < z_j
zi<x<zj的一个元素
x
x
x被选择为划分主元,那么
z
i
z_i
zi和
z
j
z_j
zj就会划分到2个不同的子数组中,
z
i
z_i
zi和
z
j
z_j
zj就再也没有机会进行比较了。相反地,如果在包含
Z
i
j
Z_{ij}
Zij中的所有元素的一次PARTITION调用中,
z
i
z_i
zi被选为划分主元,那么
z
i
z_i
zi就会和
z
j
z_j
zj比较;同样,如果
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在
Z
i
j
Z_{ij}
Zij中被首先选为划分主元时,
z
i
z_i
zi和
z
j
z_j
zj才会进行比较。
Z
i
j
Z_{ij}
Zij中的元素都会等可能地被首先选为划分主元,所以每个元素被选择的概率为
1
/
(
j
−
i
+
1
)
1/(j-i+1)
1/(j−i+1)。于是有
于是我们可以得到
我们令
k
=
j
−
i
k = j-i
k=j−i,将上式做一下变换。
由此我们得到,快速排序的期望运行时间的上界为
O
(
n
l
g
n
)
O(n{\rm lg}n)
O(nlgn)。在7.2节,我们得到结论:快速排序的最好情况运行时间为
Θ
(
n
l
g
n
)
Θ(n{\rm lg}n)
Θ(nlgn),即快速排序的运行时间的下界为
Ω
(
n
l
g
n
)
Ω(n{\rm lg}n)
Ω(nlgn)。因此,我们可以断言,快速排序的期望时间复杂度为
Θ
(
n
l
g
n
)
Θ(n{\rm lg}n)
Θ(nlgn)。
练习
7.4-1 证明:在递归式
中,
T
(
n
)
=
Ω
(
n
2
)
T(n) = Ω(n^2)
T(n)=Ω(n2)。
解
假设
T
(
n
)
≥
c
n
2
T(n) ≥ cn^2
T(n)≥cn2,其中
c
c
c为一个常数。我们用数学归纳法来证明。
(1) 初始情况n = 1
此时有
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
T(1) = 2T(0) + Θ(1)
T(1)=2T(0)+Θ(1)。
T
(
0
)
T(0)
T(0)为常数时间,显然只要
c
c
c足够小,就能使得
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
≥
c
•
1
2
T(1) = 2T(0) + Θ(1) ≥ c•1^2
T(1)=2T(0)+Θ(1)≥c•12成立。
(2) 归纳过程
假设
T
(
n
)
≥
c
n
2
T(n) ≥ cn^2
T(n)≥cn2对
1
,
2
,
…
,
n
−
1
1, 2, …, n-1
1,2,…,n−1都成立,于是有
显然,只要
c
c
c足够小,就能使得
c
(
2
n
−
1
)
小
于
Θ
(
n
)
c(2n-1)小于Θ(n)
c(2n−1)小于Θ(n),也就可以使得
T
(
n
)
≥
c
n
2
T(n) ≥ cn^2
T(n)≥cn2成立。
综上所述,只要选取足够小的
c
c
c,就能使得
T
(
n
)
≥
c
n
2
T(n) ≥ cn^2
T(n)≥cn2对所有
n
n
n的所有取值都成立。因此,
T
(
n
)
=
Ω
(
n
2
)
T(n) = Ω(n^2)
T(n)=Ω(n2)成立。
7.4-2 证明:在最好情况下,快速排序的运行时间为
Ω
(
n
l
g
n
)
Ω(n{\rm lg}n)
Ω(nlgn)。
解
在最好情况下,快速排序的运行时间
T
(
n
)
T(n)
T(n)满足以下递归式
假设
T
(
n
)
≥
c
n
l
g
n
T(n) ≥ cn{\rm lg}n
T(n)≥cnlgn,其中
c
c
c为一个常数。我们用数学归纳法来证明。
(1) 初始情况n = 1
此时有
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
T(1) = 2T(0) + Θ(1)
T(1)=2T(0)+Θ(1)。显然无论
c
c
c取何值,都能使得
T
(
1
)
=
2
T
(
0
)
+
Θ
(
1
)
≥
c
•
1
•
l
g
1
=
0
T(1) = 2T(0) + Θ(1) ≥ c•1•{\rm lg}1 = 0
T(1)=2T(0)+Θ(1)≥c•1•lg1=0成立。
(2) 归纳过程
假设
T
(
n
)
≥
c
n
l
g
n
T(n) ≥ cn{\rm lg}n
T(n)≥cnlgn对
1
,
2
,
…
,
n
−
1
1, 2, …, n-1
1,2,…,n−1都成立,于是有
定义函数
f
(
q
)
=
q
l
g
q
+
(
n
−
q
−
1
)
l
g
(
n
−
q
−
1
)
f(q)=q{\rm lg}q+(n-q-1){\rm lg}(n-q-1)
f(q)=qlgq+(n−q−1)lg(n−q−1),其中自变量
q
q
q的取值范围为
[
0
,
n
−
1
]
[0, n-1]
[0,n−1],我们要求这个函数的最小值。对
f
(
q
)
f(q)
f(q)求导,得到
当
q
=
(
n
−
1
)
/
2
q = (n-1)/2
q=(n−1)/2时,有
f
′
(
q
)
=
0
f'(q)=0
f′(q)=0;当
q
<
(
n
−
1
)
/
2
q < (n-1)/2
q<(n−1)/2时,有
f
′
(
q
)
<
0
f'(q)<0
f′(q)<0;而当
q
>
(
n
−
1
)
/
2
q > (n-1)/2
q>(n−1)/2时,有
f
′
(
q
)
>
0
f'(q)>0
f′(q)>0。因此,
f
(
q
)
f(q)
f(q)在
q
=
(
n
−
1
)
/
2
q = (n-1)/2
q=(n−1)/2时取得最小值,最小值为
(
n
−
1
)
l
g
(
(
n
−
1
)
/
2
)
=
(
n
−
1
)
l
g
(
n
−
1
)
−
(
n
−
1
)
(n-1){\rm lg}((n-1)/2)=(n-1){\rm lg}(n-1)-(n-1)
(n−1)lg((n−1)/2)=(n−1)lg(n−1)−(n−1)。于是有
为了让
T
(
n
)
≥
c
n
l
g
n
T(n) ≥ cn{\rm lg}n
T(n)≥cnlgn成立。我们令
c
(
n
−
1
)
l
g
(
n
−
1
)
−
c
(
n
−
1
)
+
Θ
(
n
)
≥
c
n
l
g
n
c(n-1){\rm lg}(n-1)-c(n-1)+Θ(n)≥cn{\rm lg}n
c(n−1)lg(n−1)−c(n−1)+Θ(n)≥cnlgn。将这个不等式变换一下,得到
由于
n
l
g
n
+
n
−
1
−
(
n
−
1
)
l
g
(
n
−
1
)
>
0
n{\rm lg}n+n-1-(n-1){\rm lg}(n-1)>0
nlgn+n−1−(n−1)lg(n−1)>0,所以上面的不等式可以求解得到
上式说明,只要
c
c
c足够小,就能够使得
T
(
n
)
≥
c
(
n
−
1
)
l
g
(
n
−
1
)
−
c
(
n
−
1
)
+
Θ
(
n
)
≥
c
n
l
g
n
T(n)≥c(n-1){\rm lg}(n-1)-c(n-1)+Θ(n)≥cn{\rm lg}n
T(n)≥c(n−1)lg(n−1)−c(n−1)+Θ(n)≥cnlgn成立。
综上所述,只要选取足够小的
c
c
c,就能使得
T
(
n
)
≥
c
n
l
g
n
T(n) ≥ cn{\rm lg}n
T(n)≥cnlgn对所有
n
n
n的取值都成立。因此,
T
(
n
)
=
Ω
(
n
l
g
n
)
T(n) = Ω(n{\rm lg}n)
T(n)=Ω(nlgn)成立。
7.4-3 证明:在
q
=
0
,
1
,
…
,
n
−
1
q = 0, 1, …, n-1
q=0,1,…,n−1区间内,当
q
=
0
q = 0
q=0或
q
=
n
−
1
q = n-1
q=n−1时,
q
2
+
(
n
−
q
−
1
)
2
q^2+(n-q-1)^2
q2+(n−q−1)2取得最大值。
解
定义函数
f
(
q
)
=
q
2
+
(
n
−
q
−
1
)
2
=
2
q
2
–
2
(
n
−
1
)
q
+
(
n
−
1
)
2
f(q) = q^2+(n-q-1)^2 = 2q^2 – 2(n-1)q + (n-1)^2
f(q)=q2+(n−q−1)2=2q2–2(n−1)q+(n−1)2。这是一个二次函数,它的曲线是一个开口向上的抛物线。我们知道,抛物线
y
=
a
x
2
+
b
x
+
c
y = ax^2 + bx + c
y=ax2+bx+c的顶点的
x
x
x坐标为
−
b
/
2
a
-b/2a
−b/2a。于是,
f
(
q
)
f(q)
f(q)的顶点横坐标为
f
(
q
)
f(q)
f(q)在顶点
(
n
−
1
)
/
2
(n-1)/2
(n−1)/2处取得最小值,并且
(
n
−
1
)
/
2
(n-1)/2
(n−1)/2正好位于区间
[
0
,
n
−
1
]
[0, n-1]
[0,n−1]的正中间。根据抛物线的对称性,可以得出
f
(
q
)
f(q)
f(q)在
q
=
0
q = 0
q=0或
q
=
n
−
1
q = n-1
q=n−1时取得最大值。
7.4-4 证明:RANDOMIZED-QUICKSORT期望运行时间是
Ω
(
n
l
g
n
)
Ω(n{\rm lg}n)
Ω(nlgn)。
略
7.4-5 当输入数据已经“几乎有序”时,插入排序速度很快。在实际应用中,我们可以利用这一特点来提高快速排序的速度。当对一个长度小于
k
k
k的子数组调用快速排序时,让它们不做任何排序就返回。当上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。试证明:这一排序算法的期望时间复杂度为
O
(
n
k
+
n
l
g
(
n
/
k
)
)
O(nk+n{\rm lg}(n/k))
O(nk+nlg(n/k))。分别从理论和实践的角度说明我们应该如何选择
k
k
k?
解
该题如果要严格证明很有难度,只做简单的分析。
该算法分为快速排序阶段和插入排序阶段。为简化分析,假设快速排序阶段后,留下的还未排序的子数组长度都为
k
−
1
k-1
k−1。实际上,快速排序阶段有可能会得到长度小于
k
−
1
k-1
k−1的子数组,但如果要考虑这些情况的话,分析太过复杂了。
下面对2个阶段分别进行分析。
(1) 快速排序阶段
我们回想一下本节对快速排序的分析,比较次数的期望值为
∑
i
=
1
n
−
1
∑
j
=
i
+
1
n
[
2
/
(
j
−
i
+
1
)
]
∑_{i=1}^{n-1}∑_{j=i+1}^{n}[2/(j-i+1)]
∑i=1n−1∑j=i+1n[2/(j−i+1)]。在累加式中,下标
j
j
j从i+1开始,到
n
n
n结束。
对于本题的排序方案,在快速排序阶段,递归调用PARTITION划分到所有子数组的规模等于
k
−
1
k-1
k−1为止。于是在快速排序阶段,只需要考虑长度不小于
k
k
k的子数组。因此,在累加式中,下标
j
j
j应当从
i
+
k
−
1
i+k-1
i+k−1开始,到
n
n
n结束。同时,下标
i
i
i应当从
1
1
1开始,到
n
−
k
+
1
n-k+1
n−k+1结束。于是,比较次数的期望值为
故快速排序阶段的期望时间复杂度为
O
(
n
l
g
(
n
/
k
)
)
O(n{\rm lg}(n/k))
O(nlg(n/k))。
(2) 插入排序阶段
插入排序阶段虽然还是对整个数组执行插入排序,但是实际上可以看作是分别对快速排序阶段留下的每一个长度为
k
−
1
k-1
k−1的子数组执行插入排序,因为子数组与子数组之间的顺序已经是正确的,只有子数组内部是还未经排序的。
长度为
k
−
1
k-1
k−1的子数组不超过
n
/
(
k
−
1
)
n/(k-1)
n/(k−1)个,对每个子数组执行插入排序的期望时间复杂度为
O
(
(
k
−
1
)
2
)
O((k-1)^2)
O((k−1)2)。因此,对所有长度为
k
−
1
k-1
k−1的子数组进行插入排序的期望时间复杂度为
综合以上两部分,得到本题提出的排序算法的期望时间复杂度为
O
(
n
k
+
n
l
g
(
n
/
k
)
)
O(nk+n{\rm lg}(n/k))
O(nk+nlg(n/k))。
从理论上来说,
k
k
k的选择与两种排序算法时间复杂度中的常量因子有关。如果快速排序时间复杂度中的常量因子相比插入排序常量因子越大,那么
k
k
k也应当越大。反之,
k
k
k应当越小。
实际应用中,可以通过实验来确定
k
k
k应当如何选择。这部分实验后期再补上。
7.4-6 考虑对PARTITION过程做这样的修改:从数组
A
A
A中随机选出三个元素,并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分。求以
α
α
α的函数形式表示的、最坏划分比例为
α
:
(
1
−
α
)
α:(1-α)
α:(1−α)的近似概率,其中
0
<
α
<
1
0 < α < 1
0<α<1。
解
我们不妨设定
α
α
α的取值范围为
0
<
α
≤
1
/
2
0 < α ≤ 1/2
0<α≤1/2。
1
/
2
<
α
<
1
1/2 < α < 1
1/2<α<1实际上是对称的情况。
如果数组已经排好序,可以按照元素的大小顺序将数组分为
3
3
3个部分,如下图所示。
如果PARTITION过程选择的划分主元位于第②部分,那么产生的划分比
α
:
(
1
−
α
)
α:(1-α)
α:(1−α)更好;而如果选择的划分主元位于第①部分和第③部分,那么产生的划分比
α
:
(
1
−
α
)
α:(1-α)
α:(1−α)更坏。因此,我们需要求在数组中任取
3
3
3个元素,中位数位于第②部分的概率。而中位数于第②部分,又可分为3种情况。
(1) 从第②部分任取3个元素
从第②部分
(
1
−
2
α
)
n
(1-2α)n
(1−2α)n个元素中任取3个,有以下这么多种取法。
(2) 从第②部分任取2个元素,从第①部分或第③部分任取1个元素
从第②部分
(
1
−
2
α
)
n
(1-2α)n
(1−2α)n个元素中任取2个,并且从第①部分和第③部分
2
α
n
2αn
2αn个元素中任取1个,有以下这么多种取法。
(3) 从第①部分、第②部分和第③部分中各任取1个元素
从第①部分
α
n
αn
αn个元素中任取1个,从第②部分
(
1
−
2
α
)
n
(1-2α)n
(1−2α)n个元素中任取1个,再从第③部分
α
n
αn
αn个元素中任取1个,有以下这么多种取法。
而从整个数组中任取3个元素,一共有以下这么多种取法。
因此,中位数位于第②部分的概率
p
p
p为
这个概率与数组的规模
n
n
n有关。通常我们更关注
n
n
n比较大的情况。我们计算
n
n
n趋近于
∞
∞
∞时,概率
p
p
p的极限,有
(注意:求
n
→
∞
n→∞
n→∞时
p
p
p的极限,只需要将分子和分母中的低阶项删掉,保留高阶项即可。)
所以对规模
n
n
n足够大的数组,采用三数取中法,中位数位于第②部分的概率接近于
1
−
6
α
2
+
4
α
3
1-6α^2+4α^3
1−6α2+4α3。这也是应用三数取中法得到的数组划分好于
α
:
(
1
−
α
)
α:(1-α)
α:(1−α)的近似概率。
我们可以侧面检验一下这个结果的正确性。将
α
=
1
/
2
α = 1/2
α=1/2代入
p
p
p的极限,得到
这说明“数组划分好于
1
/
2
:
1
/
2
1/2:1/2
1/2:1/2(即完全平衡划分)”是不可能出现的。显然,这符合“数组划分的最好情况就是完全的平衡划分”这一事实。
我们再做一点分析,将“三数取中法”与原始方法进行比较。原始方法是随机选择一个划分元素,只有当这个元素位于第②部分时,才能得到一个好于
α
:
(
1
−
α
)
α:(1-α)
α:(1−α)的划分,这种情况出现的概率为
我们比较
p
p
p与
q
q
q的大小,有
p
−
q
=
1
−
6
α
2
+
4
α
3
−
(
1
−
2
α
)
=
2
α
(
1
−
α
)
(
1
−
2
α
)
p-q=1-6α^2+4α^3-(1-2α)=2α(1-α)(1-2α)
p−q=1−6α2+4α3−(1−2α)=2α(1−α)(1−2α)。显然,在
0
<
α
≤
1
/
2
0 < α ≤ 1/2
0<α≤1/2取值范围内,有
2
α
(
1
−
α
)
(
1
−
2
α
)
≥
0
2α(1-α)(1-2α)≥0
2α(1−α)(1−2α)≥0。因此,“三数取中法”较原始方法有更高的可能性得到更好的划分。