Exercises
7.3-1 为什么我们分析随机化算法的期望运行时间,而不是最坏运行时间呢?
随机化算法并不会改善最坏情况的运行时间,但是会减少最坏情况发生的概率。
7.3-2 在RANDOMIZED-QUICKSORT的运行过程中,在最坏情况下,随机数生成器RANDOM被调用了多少次?在最好情况下呢?以Θ\ThetaΘ符号的形式给出你的答案?
因为排列nnn个数最好情况和最坏情况下都需要选取n−1n-1n−1次主元,所以
最坏情况下:Θ(n)\Theta(n)Θ(n)
最好情况下:Θ(n)\Theta(n)Θ(n)
7.4-1 证明:在递归式
T(n)=max(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)=Ω(n2)T(n)=\Omega(n^2)T(n)=Ω(n2)
假设T(n)≥cn2T(n)\geq cn^2T(n)≥cn2,则T(n)≥max(cq2+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)
q2+(n−1−q)2q^2+(n-1-q)^2q2+(n−1−q)2的最大值在两端点处取得,我们有T(n)≥c(n−1)2+Θ(n)=cn2−c(2n−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(2n−1)≤Θ(n)c(2n-1)\leq \Theta(n)c(2n−1)≤Θ(n),就有T(n)≥cn2T(n) \geq cn^2T(n)≥cn2,即T(n)=Ω(n2)T(n)=\Omega(n^2)T(n)=Ω(n2)。
7.4-2 证明:在最好情况下,快速排序的运行时间为Ω(lgn)\Omega(lgn)Ω(lgn)。
最好情况下,有递归式:T(n)=2T(n/2)+Θ(n)T(n)=2T(n/2)+\Theta(n)T(n)=2T(n/2)+Θ(n)
假设T(n)≥cnlgnT(n)\geq cnlgnT(n)≥cnlgn,则T(n)≥2cn2lgn2+Θ(n)=2cnlgn−2cn+Θ(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,使得2cn<Θ(n)2cn\lt \Theta(n)2cn<Θ(n),就有T(n)≥cnlgnT(n)\geq cnlgnT(n)≥cnlgn,即T(n)=Ω(nlgn)T(n)=\Omega(nlgn)T(n)=Ω(nlgn)
7.4-3 证明:在q=0,1,⋯ ,n−1q=0,1,\cdots,n-1q=0,1,⋯,n−1区间内,当q=0q=0q=0或q=n−1q=n-1q=n−1时,q2+(n−q−1)2q^2+(n-q-1)^2q2+(n−q−1)2取得最大值。
令f(q)=q2+(n−1−q)2f(q)=q^2+(n-1-q)^2f(q)=q2+(n−1−q)2,则有f′(q)=4q−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−12)(0,\frac{n-1}{2})(0,2n−1)上单调递减,在(0,n−12)(0,\frac{n-1}{2})(0,2n−1)上单调增加,在q=n−12q=\frac{n-1}{2}q=2n−1上取得极小值,在两端点处取得最大值f(0)=f(n−1)=(n−1)2f(0)=f(n-1)=(n-1)^2f(0)=f(n−1)=(n−1)2。
7.4-4 证明:RANDOMIZED-QUICKSORT的期望运行时间是Ω(nlgn)\Omega(nlgn)Ω(nlgn)。
7.4-5 当输入数据已经“几乎有序”时,插入排序速度很快。在实际应用中,我们可以利用这一特点来提高快速排序的速度。当对一个长度小于kkk的子数组调用快速排序时,让它不做任何排序就返回。当上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。试证明:这一排序算法的期望复杂度为O(nk+nlog(n/k))O(nk+nlog(n/k))O(nk+nlog(n/k))。分别从理论和实践的角度说明我们应该如何选择kkk。
算法代码如下:
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(logn−logk)O(logn-logk)O(logn−logk),每层划分花费O(n)O(n)O(n),一共O(nlog(n/k))O(nlog(n/k))O(nlog(n/k))。插入排序时,每个元素的移动不会超过k−1k-1k−1次,时间复杂度为O(nk)O(nk)O(nk)。所以总的时间复杂度为O(nk+nlog(n/k))O(nk+nlog(n/k))O(nk+nlog(n/k))。
理论上不要超过快速排序的平均时间复杂度O(nlogn)O(nlogn)O(nlogn)。实践中,kkk在[1,logn][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.请描述一种场景,使得针对一个包含nnn个元素数组的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的代码,使其最坏情况下栈深度是Θ(lgn)\Theta(lgn)Θ(lgn),并且能够保持O(nlgn)O(nlgn)O(nlgn)的期望时间复杂度。
本文深入探讨了快速排序算法的期望运行时间分析,包括随机化算法的优势、最坏与最好情况下的运行时间评估,以及通过引入插入排序改进快速排序效率的方法。同时,文章还讨论了快速排序的栈深度问题,并提出了一种优化方案。

1032

被折叠的 条评论
为什么被折叠?



