【问题标题】:Why does Java 6 Arrays#sort(Object[]) change from mergesort to insertionsort for small arrays?为什么 Java 6 Arrays#sort(Object[]) 从合并排序更改为小数组的插入排序?
【发布时间】:2011-10-02 18:20:53
【问题描述】:

如果数组长度小于某个阈值,Arrays.java 中的 Java 6 合并排序实现使用插入排序。该值被硬编码为 7。由于算法是递归的,因此对于大型数组,这最终会发生很多次。规范的 merge-sort algorithm 没有这样做,只是一直使用合并排序,直到列表中只有 1 个元素。

这是优化吗?如果是这样,它应该如何提供帮助?为什么7?插入排序(甚至是 <=7 事物)显着增加了对大型数组进行排序所需的比较次数 - 因此会增加 compareTo() 调用速度较慢的排序的成本。

(x轴为size of array,y轴为# of comparisons,对于INSERTIONSORT_THRESHOLD的不同值)

【问题讨论】:

  • 这张图的来源是什么?您似乎没有任何评论地展示它
  • 我通过对一个对象数组进行排序来制作这个图表,这些对象计算调用 compareTo 的次数并改变 INSERTIONSORT_THRESHOLD。
  • 值得注意的是Java7还有Timsort,这是Tim Peters为python开发的混合merge-insert。 download.java.net/jdk7/docs/api/java/util/…

标签: java algorithm mergesort


【解决方案1】:

我的理解是,这是一个经验得出的值,其中插入排序所需的时间实际上较低,尽管需要(可能)更多的比较次数。之所以如此,是因为在合并排序接近尾声时,数据可能几乎已排序,这使得插入排序表现良好。

【讨论】:

  • 我也猜到了。但是当我运行一些基准测试时,我发现情况并非如此。对于廉价的 compareTo 操作,任何小于 20 的数字都大致相等,而对于昂贵的 compareTo 来说,比较时间占主导地位。
  • Matthew:请注意,昂贵的 compareTo 实现可能不是最常见的情况(请记住,Java 的基类库非常通用,并没有专门针对您的用例)并且通过使用插入对小的子列表进行排序,您还可以节省重复应用 D&C 算法或合并排序的开销。
  • @Matthew Joey 关于 Java BCL 的“通用性”是正确的。另一点需要注意的是,真正昂贵的compareTo() 方法可能应该被修复,因为比较两个对象不需要花费很长时间。如果没有办法避免这种情况(可能是因为对象真的那么复杂),那么可能值得根据相关标准对一组代理对象进行排序(因为对象的 每个 方面很少会被排序时考虑在内。)
  • 明白了——我没有试图解决的现实问题;)但是这个算法只用于对一般对象进行排序——原始数组的处理方式不同——所以 unknown 比较的复杂性应该是一个考虑因素,不是吗?
  • 调用插入排序时,数据几乎排序如何?合并排序只在合并阶段进行排序,递归插入排序调用发生在此之前。
【解决方案2】:

是的,这是故意的。虽然归并排序的 Big-O 小于插入排序等二次排序,但它所做的操作更复杂,因此速度更慢。

考虑对长度为 8 的数组进行排序。除了 7 次合并操作之外,合并排序还会对其自身进行约 14 次递归调用。每个递归调用都会为运行时贡献一些重要的开销。每个合并操作都涉及一个循环,其中必须初始化、递增和比较索引变量,必须复制临时数组等。总而言之,您可以预期超过 300 个“简单”操作。

另一方面,插入排序本质上很简单,使用大约 8^2=64 次操作,速度要快得多。

这样想。当您手动对包含 10 个数字的列表进行排序时,您是否使用归并排序?不,因为你的大脑更擅长做简单的事情,比如插入排序。但是,如果我给你一年的时间对 100,000 个数字的列表进行排序,你可能更倾向于合并排序。

至于幻数 7,它是根据经验得出的最优值。

编辑:在 8 个元素的标准插入排序中,最坏的情况会导致大约 36 次比较。在规范的归并排序中,您有大约 24 次比较。加上方法调用的开销和操作的复杂性,插入排序应该更快。此外,如果您查看平均情况,插入排序的比较次数将远远少于 36 次。

【讨论】:

  • 这种对复杂性的解释在直觉上很有意义 - 虽然我无法具体证明 7 的任何优势,但 >25 确实有所作为。
  • 编辑了我的答案。我不能 100% 确定您的基准测试显示什么,因为您的坐标轴没有真正标记。
  • +1 例如,如果您需要对许多小数组进行排序,这会产生很大的不同。我自己的皮肤也有这种感觉。
【解决方案3】:

插入排序是 n(n-1)/2,归并排序是 n*(log n with base 2 )。

考虑到这一点 -

  1. 对于长度为 5 的数组 => 插入排序 = 10,合并排序为 11.609
  2. 对于长度为 6 的数组 => 插入排序 = 15,合并排序为 15.509
  3. 对于长度为 7 的数组 => 插入排序 = 21,合并排序为 19.651
  4. 对于长度为 8 的数组 => 插入排序 = 28,合并排序为 24

从上面的数据可以看出,直到长度 6,插入排序更快,在 7 之后,合并排序是有效的。

这就解释了为什么使用 7。

【讨论】:

    猜你喜欢
    • 2016-02-01
    • 2021-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 1970-01-01
    相关资源
    最近更新 更多