web-dev-qa-db-ja.com

Collections.sortはMergesortを使用するのにArrays.sortは使用しないのはなぜですか?

JDK-8(x64)を使用しています。 Arrays.sort(プリミティブ)の場合、Javaのドキュメントで次を見つけました。

ソートアルゴリズムは、デュアルピボットQuicksort Vladimir Yaroslavskiy、Jon Bentley、およびJoshua Blochによるものです。

Collections.sort(オブジェクト)の場合、この「Timsort」が見つかりました。

この実装は、安定性、適応性、反復性ですmergesort ...この実装指定されたリストを配列にダンプし、配列をソートします配列内の対応する位置。

Collections.sortが配列を使用する場合、なぜArrays.sortを呼び出すか、デュアルピボットQuickSortを使用しないのですか? Mergesortを使用する理由

80
Quest Monger

APIは、 Quicksort が提供しないstableソートを保証します。ただし、primitive valuesを自然な順序で並べ替えると、プリミティブ値には同一性がないため、違いに気付かないでしょう。したがって、 Quicksort はプリミティブ配列に使用でき、より効率的であると考えられる場合に使用されます¹。

equals実装または提供されたComparatorに従って等しいとみなされる異なるIDを持つオブジェクトの順序が変わると、オブジェクトに気付くかもしれません。したがって、 Quicksort はオプションではありません。そのため、 MergeSort のバリアントが使用され、現在のJavaバージョンはTimSortを使用します=。これはArrays.sortCollections.sortの両方に適用されますが、Java 8では、List自体がソートアルゴリズムをオーバーライドする場合があります。


¹ Quicksort の効率の利点は、インプレースで実行するときに必要なメモリが少ないことです。ただし、劇的な最悪のケースのパフォーマンスがあり、配列内の事前に並べ替えられたデータの実行を活用することはできません TimSort が行います。

したがって、ソートアルゴリズムはバージョンからバージョンへと作り直され、今では誤解を招く名前のクラスDualPivotQuicksortのままです。また、ドキュメントは追いついていませんでした。これは、一般的には、仕様で内部的に使用されるアルゴリズムに名前を付ける必要がない場合、それは悪い考えであることを示しています。

現在の状況(Java 8からJava 11を含む)は次のとおりです。

  • 一般的に、プリミティブ配列のソート方法は、特定の状況下でのみ Quicksort を使用します。より大きな配列の場合、 TimSort のように、事前に並べ替えられたデータの実行を最初に識別しようとし、実行数が特定のしきい値を超えない場合にそれらをマージします。そうでなければ、それらは Quicksort にフォールバックしますが、小さな範囲では Insertion sort にフォールバックする実装では、小さな配列だけに影響しますが、クイックソートの再帰にも影響します。
  • sort(char[],…)およびsort(short[],…)は、長さが特定のしきい値を超える配列に対して Counting sort を使用する別の特殊なケースを追加します
  • 同様に、sort(byte[],…)Counting sort を使用しますが、sort(byte[],…)はQuicksortを使用しないため、ドキュメントとの最大のコントラストを作成する非常に小さなしきい値を使用します。小さな配列には Insertion sort を使用し、それ以外の場合は Counting sort を使用します。
85
Holger

ドキュメントについては知りませんが、Java 8(HotSpot)でのJava.util.Collections#sortの実装は次のようになります。

@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 (オブジェクト要素の)を使用します。この実装では、マージソートまたはティムソートが使用されます。

19
Luiggi Mendoza

Javadocによると、プリミティブ配列のみがQuicksortを使用してソートされます。オブジェクト配列もMergesortでソートされます。

そのため、Collections.sortは、オブジェクトのArrays.sortと同じソートアルゴリズムを使用しているようです。

別の質問は、プリミティブ配列に対してオブジェクト配列とは異なるソートアルゴリズムが使用される理由ですか?

15
Puce

答えの多くで述べたように。

安定性は必要ないため、QuicksortはArrays.sortでプリミティブコレクションをソートするために使用されます(2つの同一のintがソートでスワップされたかどうかはわかりません)

MergeSortまたはより具体的にはTimsortは、オブジェクトのコレクションをソートするためにArrays.sortによって使用されます。安定性が必要です。 Quicksortは安定性を提供しませんが、Timsortは提供します。

Collections.sortはArrays.sortにデリゲートするため、MergeSortを参照するjavadocが表示されます。

2
cogitoboy

クイックソートには、マージソートに関して2つの大きな欠点があります。

  • プリミティブではないものの、安定していません。
  • N log nのパフォーマンスを保証するものではありません。

(値)の平等とは異なるアイデンティティの概念がないため、プリミティブ型では安定性は問題ではありません。

任意のオブジェクトを並べ替える場合、安定性は重要です。 Merge Sortは、入力が何であれ、n log n(時間)のパフォーマンスを保証するという素晴らしい副次的な利点です。これが、オブジェクト参照をソートする安定したソート(マージソート)を提供するためにマージソートが選択されている理由です。

1
Krutik