为什么Collections.sort使用Mergesort但Arrays.sort不使用?


94

我正在使用JDK-8(x64)。对于Arrays.sort(原始),我在Java文档中发现了以下内容:

排序算法是Vladimir Yaroslavskiy,Jon Bentley和Joshua Bloch编写的Dual-Pivot Quicksort

对于Collections.sort(对象),我找到了“ Timsort”:

此实现是一个稳定的,自适应的,迭代的mergesort ...该实现将指定的列表转储到数组中,对数组进行排序,然后遍历列表,从数组中的相应位置重置每个元素。

如果Collections.sort使用数组,为什么不调用Arrays.sort或使用双轴QuickSort?为什么要使用Mergesort


8
那是基元数组的javadoc-对象数组是使用meregsort排序的。
assylias 2015年

2
mergesort始终会给nlogn,而quicksort有时可能会给nlogn2一般数组大小不大,但是集合很容易达到数百万个条目,因此冒nlogn2的风险不值得PS nlogn2我的意思是n的平方
Kumar Saurabh

快速排序的O(n ^ 2)是最坏的情况。实际上,速度更快
James Wierzba

但是您在制作api时不能忽略这些原因
Kumar Saurabh 2015年

2
此链接非常相关。
qartal

Answers:


99

该API保证了Quicksort不提供的稳定排序。但是,当按原始值的自然顺序对原始值进行排序时,您不会注意到差异,因为原始值没有身份。因此,Quicksort可以用于基本数组,并在认为效率更高时使用¹

对于对象,您可能会注意到,当具有不同标识的对象根据其equals实现或提供的内容被视为相等时,将Comparator更改其顺序。因此,Quicksort不是一个选择。所以一个变种归并时,当前的Java版本使用TimSort。这适用于Arrays.sortCollections.sort,尽管使用Java 8,它List本身也可以覆盖排序算法。


¹的效率优势快速排序就地完成时,需要较少的内存。但是,它在最坏的情况下具有惊人的性能,无法利用数组中的预排序数据运行,而TimSort可以做到。

因此,排序算法在版本之间进行了重新设计,同时保留在现在具有误导性的class中DualPivotQuicksort。而且,文档没有跟上,这表明,在不必要的情况下,在规范中命名内部使用的算法通常是个坏主意。

当前情况(包括Java 8到Java 11)如下:

  • 通常,原始数组的排序方法仅在某些情况下才使用Quicksort。对于较大的阵列,它们将尝试像TimSort一样首先识别经过预排序的数据,并在运行次数未超过特定阈值时将其合并。否则,它们将退回到Quicksort,但实现方式将退回到小范围的插入排序,这不仅会影响小数组,还会影响快速排序的递归。
  • sort(char[],…)sort(short[],…)添加另一种特殊情况,对长度超过特定阈值的数组使用计数排序
  • 同样,sort(byte[],…)将使用Counting sort,但阈值要小得多,这与文档形成了最大的对比,因为sort(byte[],…)从不使用Quicksort。它仅对小数组使用插入排序,否则对计数排序使用

1
嗯,有趣的是,Collections.sort Javadoc声明:“保证这种排序是稳定的”,但是由于它委托给List.sort(可以由列表实现覆盖),因此对于所有列表,Collections.sort都无法真正保证稳定的排序实现。还是我想念什么?而且List.sort不需要稳定的排序时间。
2015年

11
@Puce:这只是意味着现在要保证这种保证的责任落在那些实施压倒性List.sort方法的人的手中。Collections.sort永远无法保证每个List实现都能正常工作,因为它无法保证,例如,List不会虚假地更改其内容。这一切都归结到的担保Collections.sort只适用于正确的List实现(正确Comparatorequals实现)。
Holger 2015年

1
@Puce:但是,您是对的,Javadoc在这两种方法中都没有同样明确地说明此约束,但至少是Collections.sort将委托给的最新文档状态List.sort
Holger 2015年

@Puce:有很多这样的示例,其中重要的属性不是类型的一部分,而是仅在文档中提及(因此,编译器未对其进行检查)。Java的类型系统太弱了,无法表达任何有趣的属性。(在这方面,它与动态类型的语言没有太大区别,在文档中也定义了属性,这取决于程序员,以确保它们没有受到侵犯。)实际上,它甚至更进一步:您注意到了吗?这Collections.sort不,即使在它的类型签名提的是输出的排序?
约尔格W¯¯米塔格

1
在具有更具表现力的类型系统的语言中,返回类型Collections.sort将类似于“与输入具有相同类型和长度的集合,其属性为:1)输入中存在的每个元素也出现在输出中,2 )对于输出中的每对元素,左元素不大于右元素; 3)对于输出中的每对等元素,输入中的左元素索引小于右元素”或类似的东西那。
约尔格W¯¯米塔格

20

我不了解文档,但是java.util.Collections#sortJava 8(HotSpot)的实现是这样的:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

List#sort具有以下实现:

@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

因此,最后,在幕后Collections#sort使用Arrays#sort(对象元素的)。此实现使用合并排序或tim排序。


16

根据Javadoc,仅使用Quicksort对原始数组进行排序。对象数组也通过Mergesort排序。

因此,Collections.sort似乎使用与Arrays.sort for Objects相同的排序算法。

另一个问题是为什么原始数组使用的排序算法与对象数组使用的排序算法不同?


2

如许多答案所述。

Arrays.sort使用Quicksort来对原始集合进行排序,因为不需要稳定性(您不知道或不在乎是否在排序中交换了两个相同的int)

Arrays.sort使用MergeSort或更具体地说是Timsort来对对象集合进行排序。需要稳定性。Quicksort不能提供稳定性,Timsort可以提供稳定性。

Collections.sort委托给Arrays.sort,这就是为什么您看到引用MergeSort的javadoc的原因。


1

合并排序时,快速排序有两个主要缺点:

  • 当涉及到非原始时,它是不稳定的。
  • 它不能保证n log n性能。

对于原始类型,稳定性不是问题,因为没有等同于(值)相等性的身份概念。

排序任意对象时,稳定性至关重要。合并排序可以保证n log n(时间)性能,无论输入什么,这都是一个很好的附带好处。这就是为什么选择合并排序以提供稳定的排序(合并排序)来对对象引用进行排序的原因。


1
您是什么意思“不稳定”?
阿伦·古达
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.