Exercises
7.3-1 为什么我们分析随机化算法的期望运行时间,而不是最坏运行时间呢?
随机化算法并不会改善最坏情况的运行时间,但是会减少最坏情况发生的概率。
7.3-2 在RANDOMIZED-QUICKSORT的运行过程中,在最坏情况下,随机数生成器RANDOM被调用了多少次?在最好情况下呢?以 Θ \Theta Θ符号的形式给出你的答案?
因为排列 n n n个数最好情况和最坏情况下都需要选取 n − 1 n-1 n−1次主元,所以
最坏情况下: Θ ( n ) \Theta(n) Θ(n)
最好情况下: Θ ( n ) \Theta(n) Θ(n)
7.4-1 证明:在递归式
T
(
n
)
=
m
a
x
(
T
(
q
)
+
T
(
n
−
q
−
1
)
)
+
Θ
(
n
)
T(n)=max(T(q)+T(n-q-1))+\Theta(n)
T(n)=max(T(q)+T(n−q−1))+Θ(n)
中,
T
(
n
)
=
Ω
(
n
2
)
T(n)=\Omega(n^2)
T(n)=Ω(n2)
假设 T ( n ) ≥ c n 2 T(n)\geq cn^2 T(n)≥cn2,则 T ( n ) ≥ m a x ( c q 2 + c ( n − q − 1 ) 2 ) + Θ ( n ) T(n)\geq max(cq^2+c(n-q-1)^2) +\Theta(n) T(n)≥max(cq2+c(n−q−1)2)+Θ(n)
q 2 + ( n − 1 − q ) 2 q^2+(n-1-q)^2 q2+(n−1−q)2的最大值在两端点处取得,我们有 T ( n ) ≥ c ( n − 1 ) 2 + Θ ( n ) = c n 2 − c ( 2 n − 1 ) + Θ ( n ) T(n)\geq c(n-1)^2+\Theta(n)=cn^2-c(2n-1)+\Theta(n) T(n)≥c(n−1)2+Θ(n)=cn2−c(2n−1)+Θ(n)
只要取足够小的正常数c,使得 c ( 2 n − 1 ) ≤ Θ ( n ) c(2n-1)\leq \Theta(n) c(2n−1)≤Θ(n),就有 T ( n ) ≥ c n 2 T(n) \geq cn^2 T(n)≥cn2,即 T ( n ) = Ω ( n 2 ) T(n)=\Omega(n^2) T(n)=Ω(n2)。
7.4-2 证明:在最好情况下,快速排序的运行时间为 Ω ( l g n ) \Omega(lgn) Ω(lgn)。
最好情况下,有递归式: T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n)=2T(n/2)+\Theta(n) T(n)=2T(n/2)+Θ(n)
假设 T ( n ) ≥ c n l g n T(n)\geq cnlgn T(n)≥cnlgn,则 T ( n ) ≥ 2 c n 2 l g n 2 + Θ ( n ) = 2 c n l g n − 2 c n + Θ ( n ) T(n)\geq 2c\frac{n}{2}lg\frac{n}{2}+\Theta(n)=2cnlgn-2cn+\Theta(n) T(n)≥2c2nlg2n+Θ(n)=2cnlgn−2cn+Θ(n)
只要取足够小的正常数c,使得 2 c n < Θ ( n ) 2cn\lt \Theta(n) 2cn<Θ(n),就有 T ( n ) ≥ c n l g n T(n)\geq cnlgn T(n)≥cnlgn,即 T ( n ) = Ω ( n l g n ) T(n)=\Omega(nlgn) T(n)=Ω(nlgn)
7.4-3 证明:在 q = 0 , 1 , ⋯ , n − 1 q=0,1,\cdots,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 − 1 − q ) 2 f(q)=q^2+(n-1-q)^2 f(q)=q2+(n−1−q)2,则有 f ′ ( q ) = 4 q − 2 ( n − 1 ) , f ′ ′ f^\prime(q)=4q-2(n-1),f^{\prime\prime} f′(q)=4q−2(n−1),f′′(q)=4, f ( q ) f(q) f(q)在 ( 0 , n − 1 2 ) (0,\frac{n-1}{2}) (0,2n−1)上单调递减,在 ( 0 , n − 1 2 ) (0,\frac{n-1}{2}) (0,2n−1)上单调增加,在 q = n − 1 2 q=\frac{n-1}{2} q=2n−1上取得极小值,在两端点处取得最大值 f ( 0 ) = f ( n − 1 ) = ( n − 1 ) 2 f(0)=f(n-1)=(n-1)^2 f(0)=f(n−1)=(n−1)2。
7.4-4 证明:RANDOMIZED-QUICKSORT的期望运行时间是 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)。
7.4-5 当输入数据已经“几乎有序”时,插入排序速度很快。在实际应用中,我们可以利用这一特点来提高快速排序的速度。当对一个长度小于 k k k的子数组调用快速排序时,让它不做任何排序就返回。当上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。试证明:这一排序算法的期望复杂度为 O ( n k + n l o g ( n / k ) ) O(nk+nlog(n/k)) O(nk+nlog(n/k))。分别从理论和实践的角度说明我们应该如何选择 k k k。
算法代码如下:
QUICKSORTWITHINSERTIONSORT(A,p,r)
QUICKSORT(A,p,r)
INSERTION-SORT(A,p,r)//整体进行插入排序O(nk)
QUICKSORT(A,p,r)
if r-p+1>k //长度小于k的子数组直接返回
q=PARTITION(A,p,r)
QUICKSORT(A,p,q-1)
QUICKSORT(A,q+1,r)
总体排序时间时快速排序和插入排序总和。快速排序递归树递归到 T ( k ) T(k) T(k)为止,递归树高度此时为 O ( l o g n − l o g k ) O(logn-logk) O(logn−logk),每层划分花费 O ( n ) O(n) O(n),一共 O ( n l o g ( n / k ) ) O(nlog(n/k)) O(nlog(n/k))。插入排序时,每个元素的移动不会超过 k − 1 k-1 k−1次,时间复杂度为 O ( n k ) O(nk) O(nk)。所以总的时间复杂度为 O ( n k + n l o g ( n / k ) ) O(nk+nlog(n/k)) O(nk+nlog(n/k))。
理论上不要超过快速排序的平均时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。实践中, k k k在 [ 1 , l o g n ] [1,logn] [1,logn]之间取值。
Problems
7.4 (快速排序的栈深度)7.1节中的QUICKSORT算法包含了两个对其自身的递归调用。在调用PARTITION后,QUICKSORT分别递归调用了左边的子数组和右边的子数组。QUICKSORT中第二个递归调用不是必须的。我们可以用一个循环空值结构来代替它。这一技术成为尾递归,好的编译器都提供这一功能。考虑下面这个版本的快速排序,它模拟了尾递归的情况:
TAIL-RECURSIVE-QUICKSORT(A,p,r)
while p < r
//Partition and sort left subarray.
q-PARTITION(A,p,r)
TAIL-RECURSIVE-QUICKSORT(A,p,q-1)
p=q+1
a.证明:TAIL-RECURSIVE-QUICKSORT(A,1,A.length)能正确地对数组A进行排序。编译器通常使用栈来存储递归执行过程中的信息,包括每一次递归调用的参数等。最新调用的信息存在栈的顶部,而第一次调用的信息存在栈的底部。当一个过程被调用时,其相关信息被压入栈中;当它结束时,其信息被弹出。因为我们假设数组参数是用指针来指示的,所以每次过程调用只需要 O ( 1 ) O(1) O(1)的栈空间。栈深度是在一次计算中会用到的栈空间的最大值。
b.请描述一种场景,使得针对一个包含 n n n个元素数组的TAIL-RECURSIVE-QUICKSORT的栈深度是 Θ ( n ) \Theta(n) Θ(n)。
输入序列是 A = < 1 , 2 , 3 , 4 , ⋯ , n > A=<1,2,3,4,\cdots,n> A=<1,2,3,4,⋯,n>时,栈深度是 Θ ( n ) \Theta(n) Θ(n)
c.修改TAIL-RECURSIVE-QUICKSORT的代码,使其最坏情况下栈深度是 Θ ( l g n ) \Theta(lgn) Θ(lgn),并且能够保持 O ( n l g n ) O(nlgn) O(nlgn)的期望时间复杂度。