Warum verwendet Collections.sort die Zusammenführungssortierung anstelle von Quicksort?

99

Wir wissen, dass die schnelle Sortierung der schnellste Sortieralgorithmus ist.

Das JDK6 collections.sortverwendet den Merge-Sortieralgorithmus anstelle der schnellen Sortierung. Arrays.sort verwendet jedoch einen schnellen Sortieralgorithmus.

Was ist der Grund, warum Collections.sort die Zusammenführungssortierung anstelle der schnellen Sortierung verwendet?

MayurB
quelle
3
Wenn Sie keinen JDK-Autor zur Beantwortung bringen können, erhalten Sie nur Vermutungen. Keine wirkliche Frage.
Marquis von Lorne
4
@EJP Guter Punkt, aber sicherlich "Nicht konstruktiv" ist der richtige Abschlussgrund. Mir ist klar, was die Frage hier ist.
Duncan Jones
2
Weil die Java-Leute beschlossen haben, es so zu machen. Frag sie. Sie können hier keine legitime Antwort bekommen, denke ich. Und schnelles Sortieren ist nicht das Beste. Es ist nur das Beste für den generischen Gebrauch .
Adam Arold
4
Eine Vermutung: Quicksort ist nicht stabil, Mergesort ist. Für Grundelemente ist eine stabile / nicht stabile Sortierung irrelevant, für Objekte möglicherweise (oder zumindest können Fehler gegen eine instabile Sortierung auftreten).
Parsifal
2
@EJP, Nichts hindert die Absichten von JDK-Autoren daran, öffentlich zu sein. Sobald es öffentlich ist, brauchen wir den Autor nicht mehr, um zu antworten. Es ist tatsächlich möglich, eine Antwort zu erhalten, die mehr als nur zu erraten ist, ohne dass ein JDK-Autor antwortet.
Pacerier

Antworten:

186

Sehr wahrscheinlich von Josh Bloch § :

Ich habe diese Methoden geschrieben, daher bin ich wahrscheinlich qualifiziert zu antworten. Es ist wahr, dass es keinen einzigen besten Sortieralgorithmus gibt. QuickSort weist im Vergleich zu Mergesort zwei wesentliche Mängel auf:

  1. Es ist nicht stabil (wie parsifal bemerkt).

  2. Es garantiert keine n log n Leistung; es kann sich bei pathologischen Eingaben zu einer quadratischen Leistung verschlechtern.

Stabilität ist für primitive Typen kein Thema, da es keinen Begriff von Identität gibt, der sich von (Wert-) Gleichheit unterscheidet. Die Möglichkeit eines quadratischen Verhaltens wurde in der Praxis für die Implementierung von Bentely und McIlroy (oder später für Dual Pivot Quicksort ) als kein Problem angesehen , weshalb diese QuickSort-Varianten für die primitiven Sortierungen verwendet wurden.

Stabilität ist eine große Sache beim Sortieren beliebiger Objekte. Angenommen, Sie haben Objekte, die E-Mail-Nachrichten darstellen, und sortieren sie zuerst nach Datum und dann nach Absender. Sie erwarten, dass sie innerhalb jedes Absenders nach Datum sortiert werden. Dies gilt jedoch nur, wenn die Sortierung stabil ist. Aus diesem Grund haben wir uns für eine stabile Sortierung (Merge Sort) zum Sortieren von Objektreferenzen entschieden. (Technisch gesehen führen mehrere aufeinanderfolgende stabile Sortierungen zu einer lexikografischen Reihenfolge der Schlüssel in umgekehrter Reihenfolge der Sortierungen: Die endgültige Sortierung bestimmt den wichtigsten Unterschlüssel.)

Es ist ein netter Nebeneffekt, dass Merge Sort unabhängig von der Eingabe eine Leistung von n log n (Zeit) garantiert . Natürlich gibt es einen Nachteil: Die schnelle Sortierung ist eine "In-Place" -Sortierung: Sie benötigt nur Protokoll und externen Speicherplatz (um den Aufrufstapel zu verwalten). Zusammenführen, Sortieren erfordert andererseits O (n) Außenraum. Die in Java SE 6 eingeführte TimSort-Variante benötigt wesentlich weniger Speicherplatz (O (k)), wenn das Eingabearray nahezu sortiert ist.

Außerdem ist Folgendes relevant:

Der von java.util.Arrays.sort und (indirekt) von java.util.Collections.sort zum Sortieren von Objektreferenzen verwendete Algorithmus ist ein "modifizierter Mergesort" (bei dem die Zusammenführung weggelassen wird, wenn das höchste Element in der unteren Unterliste kleiner als ist das niedrigste Element in der hohen Unterliste). " Es ist eine relativ schnelle stabile Sortierung, die die Leistung von O (n log n) garantiert und zusätzlichen Platz für O (n) benötigt. Zu seiner Zeit (es wurde 1997 von Joshua Bloch geschrieben) war es eine gute Wahl, aber heute können wir es viel besser machen.

Seit 2003 verwendet Pythons Listensortierung einen Algorithmus namens Timsort (nach Tim Peters, der ihn geschrieben hat). Es handelt sich um ein stabiles, adaptives, iteratives Mergesort, das weit weniger als n log (n) Vergleiche erfordert, wenn es auf teilweise sortierten Arrays ausgeführt wird, und gleichzeitig eine Leistung bietet, die mit einem herkömmlichen Mergesort vergleichbar ist, wenn es auf zufälligen Arrays ausgeführt wird. Wie alle richtigen Mergesorts ist timsort stabil und läuft in O (n log n) Zeit (Worst Case). Im schlimmsten Fall benötigt timsort temporären Speicherplatz für n / 2 Objektreferenzen. im besten Fall benötigt es nur wenig konstanten Platz. Vergleichen Sie dies mit der aktuellen Implementierung, die immer zusätzlichen Platz für n Objektreferenzen benötigt und n log n nur in nahezu sortierten Listen übertrifft.

Timsort wird hier ausführlich beschrieben: http://svn.python.org/projects/python/trunk/Objects/listsort.txt .

Die ursprüngliche Implementierung von Tim Peters ist in C geschrieben. Joshua Bloch hat sie von C nach Java portiert und den resultierenden Code ausgiebig getestet, bewertet und optimiert. Der resultierende Code ist ein Drop-In-Ersatz für java.util.Arrays.sort. Bei hoch geordneten Daten kann dieser Code bis zu 25-mal so schnell ausgeführt werden wie die aktuelle Implementierung (auf der HotSpot-Server-VM). Bei zufälligen Daten sind die Geschwindigkeiten der alten und neuen Implementierungen vergleichbar. Bei sehr kurzen Listen ist die neue Implementierung auch bei zufälligen Daten wesentlich schneller als die alte (da unnötiges Kopieren von Daten vermieden wird).

Siehe auch I Java 7 unter Verwendung von Tim Sort für die Methode Arrays.sort? .

Es gibt keine einzige "beste" Wahl. Wie bei vielen anderen Dingen geht es um Kompromisse.

NPE
quelle