算法导论第七章练习参考答案

Exercises

7.3-1 为什么我们分析随机化算法的期望运行时间,而不是最坏运行时间呢?

随机化算法并不会改善最坏情况的运行时间,但是会减少最坏情况发生的概率。

7.3-2 在RANDOMIZED-QUICKSORT的运行过程中,在最坏情况下,随机数生成器RANDOM被调用了多少次?在最好情况下呢?以 Θ \Theta 符号的形式给出你的答案?

因为排列 n n 个数最好情况和最坏情况下都需要选取 n 1 n-1 次主元,所以

最坏情况下: Θ ( n ) \Theta(n)

最好情况下: Θ ( n ) \Theta(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 ) = Ω ( n 2 ) T(n)=\Omega(n^2)

假设 T ( n ) c n 2 T(n)\geq cn^2 ,则 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)

q 2 + ( n 1 q ) 2 q^2+(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)

只要取足够小的正常数c,使得 c ( 2 n 1 ) Θ ( n ) c(2n-1)\leq \Theta(n) ,就有 T ( n ) c n 2 T(n) \geq cn^2 ,即 T ( n ) = Ω ( n 2 ) T(n)=\Omega(n^2)

7.4-2 证明:在最好情况下,快速排序的运行时间为 Ω ( l g n ) \Omega(lgn)

最好情况下,有递归式: T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n)=2T(n/2)+\Theta(n)

假设 T ( n ) c n l g n T(n)\geq 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)

只要取足够小的正常数c,使得 2 c n < Θ ( n ) 2cn\lt \Theta(n) ,就有 T ( n ) c n l g n T(n)\geq cnlgn ,即 T ( n ) = Ω ( n l g n ) T(n)=\Omega(nlgn)

7.4-3 证明:在 q = 0 , 1 , , n 1 q=0,1,\cdots,n-1 区间内,当 q = 0 q=0 q = n 1 q=n-1 时, q 2 + ( n q 1 ) 2 q^2+(n-q-1)^2 取得最大值。

f ( q ) = q 2 + ( n 1 q ) 2 f(q)=q^2+(n-1-q)^2 ,则有 f ( q ) = 4 q 2 ( n 1 ) , f f^\prime(q)=4q-2(n-1),f^{\prime\prime} (q)=4, f ( q ) f(q) ( 0 , n 1 2 ) (0,\frac{n-1}{2}) 上单调递减,在 ( 0 , n 1 2 ) (0,\frac{n-1}{2}) 上单调增加,在 q = n 1 2 q=\frac{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)

7.4-5 当输入数据已经“几乎有序”时,插入排序速度很快。在实际应用中,我们可以利用这一特点来提高快速排序的速度。当对一个长度小于 k k 的子数组调用快速排序时,让它不做任何排序就返回。当上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。试证明:这一排序算法的期望复杂度为 O ( n k + n l o g ( n / k ) ) O(nk+nlog(n/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) 为止,递归树高度此时为 O ( l o g n l o g k ) O(logn-logk) ,每层划分花费 O ( n ) O(n) ,一共 O ( n l o g ( n / k ) ) O(nlog(n/k)) 。插入排序时,每个元素的移动不会超过 k 1 k-1 次,时间复杂度为 O ( n k ) O(nk) 。所以总的时间复杂度为 O ( n k + n l o g ( n / k ) ) O(nk+nlog(n/k))

理论上不要超过快速排序的平均时间复杂度 O ( n l o g n ) O(nlogn) 。实践中, k k [ 1 , l o g n ] [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) 的栈空间。栈深度是在一次计算中会用到的栈空间的最大值。

b.请描述一种场景,使得针对一个包含 n n 个元素数组的TAIL-RECURSIVE-QUICKSORT的栈深度是 Θ ( n ) \Theta(n)

输入序列是 A = < 1 , 2 , 3 , 4 , , n > A=<1,2,3,4,\cdots,n> 时,栈深度是 Θ ( n ) \Theta(n)

c.修改TAIL-RECURSIVE-QUICKSORT的代码,使其最坏情况下栈深度是 Θ ( l g n ) \Theta(lgn) ,并且能够保持 O ( n l g n ) O(nlgn) 的期望时间复杂度。

发布了5 篇原创文章 · 获赞 0 · 访问量 145

猜你喜欢

转载自blog.csdn.net/suchvaliant/article/details/105718596