我听说在quicksort中最好先在较小的子阵列上调用递归。例如,如果5
是数据透视表并且数据被排序为4,1,3,5,7,6
,那么最好先对子数组7,6
进行排序,因为它包含两个元素,其中4,1,3
包含三个元素。
The source of all knowledge给出了快速排序的伪代码
quicksort(A, i, k):
if i < k:
p := partition(A, i, k)
quicksort(A, i, p - 1)
quicksort(A, p + 1, k)
因此,首先实现在较小数组上递归的算法看起来像
quicksort(A, i, k):
if i < k:
p := partition(A, i, k)
if(p-i > k-p)
/*difference from start point to pivot is greater than
difference from pivot to end point*/
quicksort(A, p + 1, k)
quicksort(A, i, p - 1)
else
quicksort(A, i, p - 1)
quicksort(A, p + 1, k)
我用Java编写了这样的代码,它确实看起来更快,但为什么呢?起初我认为它与尾递归优化有关,但随后意识到由于Java不支持它,这是错误的。
为什么要先将较小的子代码中的代码排序更快? This article声称它应该是
答案 0 :(得分:1)
也许我在这里遗漏了一些微妙的东西,但是如果你以不同的顺序对你的两个案例进行递归,那么你可能只是在潜在地执行一些子子树的交换之后首先遍历递归树。在每个节点但它仍然与原始递归树同构,并且基本情况的所有递归深度的集合仍然是相同的。我可以看到获得可证明的递归深度减少或其他类型的递归相关加速的唯一方法是做一些事情,比如你在较小的子阵列上递归,然后为较大的子阵列选择一个枢轴(没有递归)然后递归到两个较大子阵列的子类。这会将您的递归树转换为三元递归树而不是二进制,通常应该具有较低的最大深度。
答案 1 :(得分:0)
Java JIT编译器可以消除尾递归,从而减少堆栈内存的使用。减少堆栈内存可能会降低缓存压力,从而允许更大的分区大小以适应更快的缓存级别。
其他微小的改进是减少了函数调用的次数。那些执行的指令数量略有减少。但是,当操作不适合缓存的数据时,指令数减少通常与性能的相关性非常低。
答案 2 :(得分:0)
我最近观看了Leslie Lamport的演讲,他指出Quicksort最初提出的并不一定是一种递归算法,即使很多程序员选择以递归方式实现它。
您可以简单地将它们添加到仍必须排序的范围队列中,而不是递归到分区中。算法迭代直到队列为空。无论是从头部还是尾部推动和拉动范围,都会决定您是先进度还是先进先出。
quicksort(a, begin, end) {
queue<range> q;
push q(range(begin, end));
while (!q.empty()) {
range = q.pop_front();
pivot = partition(a, range.begin, range.end);
if (pivot > range.begin) q.push_back(range(range.begin, mid));
if (pivot + 1 < range.end) q.push_back(range(mid + 1, range.end));
}
}
递归实现只是将堆栈用作LIFO队列,因此自然地以深度优先的方式进行,我并不认为下一步处理哪一方很重要。队列长度仍然受递归深度的限制。
但是如果你以广泛的第一种方式工作,通过使用伪代码中所示的FIFO队列,则顺序可能很重要。较大的分区将为队列添加更多的子范围,因此一旦队列已经尽可能小,您就更愿意这样做。要使队列更小,您需要先处理较小的子范围。