将较大的小子文件放入堆栈的策略确保堆栈上的每个条目不超过其下方条目大小的一半,
这并不完全正确。假设您要对一个 100 个元素的数组进行排序,并且第一个枢轴正好位于中间。然后你有一个堆栈
49
50
然后将 49 个元素的部分从堆栈中弹出,分区,然后将这两个部分压入堆栈。假设这次选择的枢轴不太好,有20个元素不大于枢轴。然后你会得到堆栈
20
28
50
并且每个堆栈条目是下面一个的一半以上。
但这不可能永远持续下去,我们有
在整个排序过程中,如果栈层k
被占用,其大小最多为total_size / (2^k)
.
这在排序开始时显然是正确的,因为那时堆栈上只有一个元素,位于第 0 级,即整个 size 数组total_size
。
现在,假设所述属性在进入循环 ( while(!stack.empty())
) 时保持不变。
s
从栈层弹出一个长度为的子数组m
。如果s <= 1
,则在下一次循环迭代之前不做任何其他事情,并且不变量继续成立。否则,如果s >= 2
, 分区之后,有两个新的子数组要被压入堆栈,并且s-1
元素在一起。这两个中较小的有一个尺寸smaller_size <= (s-1)/2
,而较大的有一个尺寸larger_size <= s-1
。堆栈级别m
将被两者中的较大者占用,我们有
larger_size <= s-1 < s <= total_size / (2^m)
smaller_size <= (s-1)/2 < s/2 <= total_size / (2^(m+1))
分别用于堆栈级别m
。m+1
在循环体的末尾。不变量适用于下一次迭代。
lg total_size + 1
由于堆栈上最多有一个大小为 0 的子数组(然后在下一次迭代中立即弹出),因此占用的堆栈级别永远不会超过。
关于
作者所说的“没有结束递归删除,我们不能保证堆栈大小对于快速排序来说会很小”是什么意思?
在递归实现中,您可以进行深度递归,并且当堆栈帧未重用于结束调用时,您可能需要线性堆栈空间。考虑一个愚蠢的枢轴选择,总是选择第一个元素作为枢轴,以及一个已经排序的数组。
[0,1,2,3,4]
分区,枢轴进入位置 0,较小的子数组为空。对较大 subarray 的递归调用[1,2,3,4]
分配了一个新的堆栈帧(因此现在有两个堆栈帧)。同样的原理,下一次递归调用子数组[2,3,4]
分配第三个堆栈帧,等等。
如果一个有结束递归删除,即堆栈帧被重用,一个与上面的手动堆栈相同的保证。