【问题标题】:How does sorting the smaller (sub)array make quicksort faster?对较小的(子)数组进行排序如何使快速排序更快?
【发布时间】:2014-10-18 09:19:14
【问题描述】:

我听说在快速排序中,最好先在较小的子数组上调用递归。例如,如果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 声称应该是

【问题讨论】:

  • 那篇文章并没有说我可以看到它更快。它只是说如果您使用尾递归,它将最小化堆栈深度。
  • 链接的文章讨论了堆栈的使用。我错过了那里关于速度的讨论吗?如果是,请添加文章中的引用以使您的问题更清楚。
  • 你在维基百科上查过快速排序吗?它很好地分析了 Quicksort 的运行时间(提示它试图减少所需的比较次数)。
  • 想展示您使用的基准吗?另外,您statistically prove 是否声称递归顺序很重要?
  • @MooingDuck 也许我就是这么想的,如果你可以循环一个为什么你不能循环另一个?

标签: algorithm sorting recursion


【解决方案1】:

Java JIT 编译器可以消除尾递归,从而减少堆栈内存的使用。减少堆栈内存可能会降低缓存压力,从而允许更大的分区大小以适应更快的缓存级别。

其他小的改进是减少了函数调用的数量。那些执行的指令数量略有减少。但是,在对不适合缓存的数据进行操作时,指令数的减少通常与性能的相关性非常低。

【讨论】:

【解决方案2】:

也许我在这里遗漏了一些微妙的东西,但是如果您以不同的顺序对您的两个案例进行递归,那么您只是在可能在每个节点上执行一些子子树交换之后先深度遍历递归树,但是它仍然与原始递归树同构,并且基本情况的所有递归深度的集合仍然相同。我可以看到获得可证明的递归深度减少或其他与递归相关的加速的唯一方法是在较小的子阵列上进行递归,然后为较大的子阵列选择一个枢轴(不递归),然后在这两个上递归较大子数组的子情况。这会将您的递归树变成三元递归树而不是二元递归树,二叉树通常应该具有较低的最大深度。

【讨论】:

  • 这没有回答问题。性能的提高并不意味着递归树“更小”。例如,如果首先递归较小的数组具有更好的缓存性能,您将获得相同的递归树,但性能更好。 (这里只是假设,没有声明任何关于缓存的内容)
【解决方案3】:

我最近观看了 Leslie Lamport 的一次演讲,他在演讲中指出,正如最初提出的那样,快速排序不一定是一种递归算法,尽管许多程序员选择递归地实现它。

您可以简单地将它们添加到仍然必须排序的范围队列中,而不是递归到分区中。该算法迭代直到队列为空。是从头部还是尾部推动和拉动范围决定了您是深度优先还是广度优先。

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 队列,那么顺序可能很重要。较大的分区将为队列添加更多的子范围,因此一旦队列已经尽可能小,您宁愿这样做。要使队列更小,您需要先处理较小的子范围。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-25
    • 2016-05-09
    • 1970-01-01
    • 2020-01-08
    相关资源
    最近更新 更多