(快速排序的栈深度)7.1节中的QUICKSORT算法包含了两个对其自身的递归调用。在调用PARTITION后,QUICKSORT分别递归调用了左边的子数组和右边的子数组。QUICKSORT中的第二个递归调用并不是必须的。我们可以用一个循环控制结构来代替它。这一技术称为尾递归,好的编译器都提供这一功能。考虑下面这个版本的快速排序,它摸拟了尾递归的情况:
a. 证明:
能正确地对数组
进行排序。
编译器通常使用栈来存储递归执行过程中的相关信息,包括每一次递归调用的参数等。最新调用的信息存在栈的顶部,而第一次调用的信息存在栈的底部。当一个过程被调用时,其相关信息被压入栈中;当它结束时,其信息则被弹出。因为我们假设数组参数是用指针来指示的,所以每次过程调用只需要
的栈空间。栈深度是在一次计算中会用到的栈空间的最大值。
b. 请描述一种场景,使得针对一个包含
个元素数组的TAIL-RECURSIVE-QUICKSORT的栈深度是
。
c. 修改TAIL-RECURSIVE-QUICKSORT的代码,使其最坏情况下栈深度是
,并且能够保持
的期望时间复杂度。
解
a.
TAIL-RECURSIVE-QUICKSORT所做的事情与QUICKSORT完全一样。
在
函数中,在递归调用
后,紧接着递归调用
。即在确定划分子数组后,选递归排序左边的子数组,再递归排序右边的子数组。
而在
函数中,在递归调用
,即排序左边的子数组之后,紧接着让
,再进入下一轮迭代。在下一轮迭代中,再排序右边的子数组。
b.
每次递归调用TAIL-RECURSIVE-QUICKSORT都是针对左边的子数组。如果一个数组已经按单调递增顺序排好序,对该数组调用PARTITION后,所得到的
个子数组中,左边的子数组规模为
,右边的子数组规模为
。左边规模为
的子数组通过递归调用TAIL-RECURSIVE-QUICKSORT来排序。因此,可以得到规模为n的数组的递归栈深度为
,这个递归式的解为
。
c.
可以考虑这样改进:每次递归调用TAIL-RECURSIVE-QUICKSORT都排序规模较小的子数组。
这个算法的最坏情况发生在每次调用PARTITION都产生一个均衡的划分。这种情况下,栈的深度可以表示为
,其解为
。
由于这个算法实质上与QUICKSORT一致,所以它的期望时间复杂度依然为
。
代码链接:https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter07/Problem_7-4/TailRecursiveQuicksort
算法导论 — 思考题7-4 快速排序的栈深度
猜你喜欢
转载自blog.csdn.net/yangtzhou/article/details/88776394
今日推荐
周排行