In einem Standard - Algorithmen Kurs werden wir gelehrt , dass quicksort ist im Durchschnitt und im schlimmsten Fall. Gleichzeitig werden andere Sortieralgorithmen untersucht, die im schlimmsten Fall (wie Mergesort und Heapsort ) und im besten Fall sogar eine lineare Zeit (wie Bubblesort ) sind, jedoch einige zusätzliche Speicheranforderungen haben.O ( n 2 ) O ( n log n )
Nach einem kurzen Blick auf einige weitere Laufzeiten ist es selbstverständlich, dass Quicksort nicht so effizient sein sollte wie andere.
Bedenken Sie auch, dass die Schüler in grundlegenden Programmierkursen lernen, dass Rekursion im Allgemeinen nicht sehr gut ist, da sie zu viel Speicher usw. beanspruchen kann. Daher (und auch wenn dies kein wirkliches Argument ist) lässt dies den Schluss zu, dass Quicksort möglicherweise nicht geeignet ist Wirklich gut, weil es ein rekursiver Algorithmus ist.
Warum übertrifft dann Quicksort in der Praxis andere Sortieralgorithmen? Hat es mit der Struktur realer Daten zu tun ? Hat es mit der Funktionsweise des Speichers in Computern zu tun? Ich weiß, dass manche Erinnerungen viel schneller sind als andere, aber ich weiß nicht, ob dies der wahre Grund für diese kontraintuitive Leistung ist (im Vergleich zu theoretischen Schätzungen).
Update 1: Eine kanonische Antwort besagt, dass die Konstanten im des Durchschnittsfalls kleiner sind als die Konstanten in anderen -Algorithmen. Allerdings muss ich noch eine angemessene Begründung dafür finden, mit präzisen Berechnungen anstatt nur intuitiven Ideen.O ( n log n )
In jedem Fall scheint der wirkliche Unterschied, wie einige Antworten vermuten lassen, auf der Speicherebene zu liegen, wo Implementierungen die interne Struktur von Computern ausnutzen, indem zum Beispiel der Cache-Speicher schneller ist als der RAM-Speicher. Die Diskussion ist bereits interessant, aber ich würde gerne mehr Details zum Speichermanagement sehen, da es den Anschein hat, dass die Antwort damit zu tun hat.
Update 2: Es gibt mehrere Webseiten, die einen Vergleich von Sortieralgorithmen anbieten, von denen einige ausgefallener sind als andere (insbesondere sorting-algorithms.com ). Abgesehen davon, dass es sich um eine nette visuelle Hilfe handelt, beantwortet dieser Ansatz meine Frage nicht.
quelle
Antworten:
Kurze Antwort
Das Argument der Cache-Effizienz wurde bereits ausführlich erläutert. Darüber hinaus gibt es ein eigentümliches Argument, warum Quicksort schnell ist. Bei der Implementierung wie bei zwei „Kreuzungszeigern“, z. B. hier , haben die inneren Schleifen einen sehr kleinen Körper. Da dies der am häufigsten ausgeführte Code ist, zahlt sich dies aus.
Lange Antwort
Als erstes,
Der Average Case existiert nicht!
Da es sich in der Praxis häufig um Extremfälle handelt, die nur selten auftreten, wird eine Durchschnittsfallanalyse durchgeführt. Bei einer durchschnittlichen Fallanalyse wird jedoch von einer gewissen Verteilung der Eingaben ausgegangen ! Typisch für die Sortierung ist das Zufallspermutationsmodell (stillschweigend bei Wikipedia angenommen).
Warum -Notation?O
Das Verwerfen von Konstanten bei der Analyse von Algorithmen geschieht aus einem Hauptgrund: Wenn ich an exakten Laufzeiten interessiert bin, benötige ich (relative) Kosten aller beteiligten Grundoperationen (auch ohne Berücksichtigung von Caching-Problemen, Pipelining in modernen Prozessoren ...). Die mathematische Analyse kann zählen, wie oft jeder Befehl ausgeführt wird. Die Ausführungszeiten einzelner Befehle hängen jedoch von den Prozessordetails ab, z.
Es gibt zwei Möglichkeiten:
Reparieren Sie ein Maschinenmodell.
Dies geschieht in Don Knuths Buchreihe "Die Kunst der Computerprogrammierung" für einen künstlichen "typischen" Computer, den der Autor erfunden hat. In Band 3 finden Sie genaue durchschnittliche Fallergebnisse für viele Sortieralgorithmen, z
Diese Ergebnisse zeigen, dass Quicksort am schnellsten ist. Dies ist jedoch nur auf Knuths künstlichem Computer bewiesen. Dies bedeutet nicht unbedingt, dass Sie Ihren x86-PC verwenden. Beachten Sie auch , dass die Algorithmen beziehen sich unterschiedlich für kleine Eingänge:
[ Quelle ]
Analysieren Sie abstrakte Grundoperationen .
Bei der vergleichsbasierten Sortierung handelt es sich in der Regel um Swaps und Schlüsselvergleiche . In Robert Sedgewicks Büchern, zB „Algorithmen“ , wird dieser Ansatz verfolgt. Sie finden dort
Wie Sie sehen, können Algorithmen nicht ohne weiteres als exakte Laufzeitanalyse verglichen werden, die Ergebnisse sind jedoch unabhängig von Maschinendetails.
Andere Eingabeverteilungen
Wie oben erwähnt, beziehen sich Durchschnittsfälle immer auf eine bestimmte Eingabeverteilung, sodass andere als zufällige Permutationen in Betracht gezogen werden können. Zum Beispiel wurde für Quicksort mit gleichen Elementen geforscht und es gibt einen schönen Artikel über die Standardsortierfunktion in Java
quelle
Es gibt mehrere Punkte, die in Bezug auf diese Frage gemacht werden können.
Quicksort ist normalerweise schnell
Quicksort ist normalerweise schneller als die meisten Sorten
Der Grund für diese Cache-Effizienz liegt darin, dass die Eingabe linear gescannt und die Eingabe linear partitioniert wird. Dies bedeutet, dass wir das Beste aus jeder Cache-Last machen können, indem wir jede Zahl, die wir in den Cache laden, lesen, bevor wir diesen Cache gegen einen anderen austauschen. Insbesondere ist der Algorithmus nicht Cache-fähig, was eine gute Cache-Leistung für jede Cache-Ebene ergibt, was ein weiterer Gewinn ist.
Die Cache - Effizienz konnte weiter auf verbessert werdenO(nBlogMB(nB)) M k
Quicksort ist normalerweise schneller als Mergesort
Bei diesem Vergleich geht es ausschließlich um konstante Faktoren (wenn wir den typischen Fall betrachten). Insbesondere besteht die Wahl zwischen einer suboptimalen Auswahl des Pivots für Quicksort und der Kopie der gesamten Eingabe für Mergesort (oder der Komplexität des Algorithmus, der zur Vermeidung dieses Kopiervorgangs erforderlich ist). Es stellt sich heraus, dass Ersteres effizienter ist: Es gibt keine Theorie dahinter, es ist einfach schneller.
Beachten Sie zum Schluss, dass Quicksort etwas empfindlich gegenüber Eingaben ist, die zufällig in der richtigen Reihenfolge vorliegen. In diesem Fall können einige Auslagerungen übersprungen werden. Mergesort hat keine derartigen Optimierungen, was Quicksort im Vergleich zu Mergesort auch etwas schneller macht.
Verwenden Sie die Sortierung, die Ihren Anforderungen entspricht
Fazit: Kein Sortieralgorithmus ist immer optimal. Wählen Sie, was Ihren Bedürfnissen entspricht. Wenn Sie einen Algorithmus benötigen, der in den meisten Fällen am schnellsten ist, und es Ihnen nichts ausmacht, dass er in seltenen Fällen etwas langsam ist und Sie keine stabile Sortierung benötigen, verwenden Sie Quicksort. Verwenden Sie andernfalls den Algorithmus, der Ihren Anforderungen besser entspricht.
quelle
In einem der Programmier-Tutorials an meiner Universität haben wir die Schüler gebeten, die Leistung von QuickSort, Mergesort und Einfügesortierung mit der in Python integrierten list.sort (genannt Timsort ) zu vergleichen. Die experimentellen Ergebnisse haben mich zutiefst überrascht, da die integrierte list.sort-Funktion selbst bei Instanzen, bei denen es leicht zu einem Absturz von Quicksort und Mergesort kam, eine wesentlich bessere Leistung als andere Sortieralgorithmen erbrachte. Es ist also verfrüht zu folgern, dass die übliche Implementierung von Quicksort die beste in der Praxis ist. Aber ich bin mir sicher, dass es eine viel bessere Implementierung von QuickSort oder einer Hybridversion davon gibt.
Dies ist ein netter Blog-Artikel von David R. MacIver , der Timsort als eine Form von adaptivem Mergesort erklärt.
quelle
list.sort
profitiert es von einer von Profis optimierten eingebauten Funktion. Bei einem faireren Vergleich wären alle Funktionen mit gleichem Aufwand in derselben Sprache geschrieben.Ich denke, einer der Hauptgründe, warum QuickSort im Vergleich zu anderen Sortieralgorithmen so schnell ist, ist, dass es Cache-freundlich ist. Wenn QS ein Segment eines Arrays verarbeitet, greift es auf Elemente am Anfang und Ende des Segments zu und bewegt sich in Richtung der Mitte des Segments.
Wenn Sie also anfangen, greifen Sie auf das erste Element im Array zu und ein Teil des Speichers ("Speicherort") wird in den Cache geladen. Und wenn Sie versuchen, auf das zweite Element zuzugreifen, befindet es sich (höchstwahrscheinlich) bereits im Cache, sodass es sehr schnell ist.
Andere Algorithmen wie Heapsort funktionieren nicht so, sie springen viel im Array, was sie langsamer macht.
quelle
Andere haben bereits gesagt, dass die asymptotische durchschnittliche Laufzeit von Quicksort (in der Konstante) besser ist als die anderer Sortieralgorithmen (in bestimmten Einstellungen).
Beachten Sie, dass es viele Varianten von Quicksort gibt (siehe zB Sedgewicks Dissertation). Sie funktionieren bei verschiedenen Eingabeverteilungen unterschiedlich (einheitlich, fast sortiert, fast umgekehrt sortiert, viele Duplikate, ...), und andere Algorithmen sind für einige möglicherweise besser.
quelle
ps: um genau zu sein, ist es aufgabenabhängig, besser als andere Algorithmen zu sein. Für einige Aufgaben ist es möglicherweise besser, andere Sortieralgorithmen zu verwenden.
Siehe auch:
Vergleich von Quick-Sort mit anderen Sortieralgorithmen
Vergleich der Heap-Sortierung mit anderen Sortieralgorithmen
quelle
Der zweite Grund ist, dass es eine
in-place
Sortierung durchführt und in Umgebungen mit virtuellem Speicher sehr gut funktioniert.UPDATE :: (Nach den Kommentaren von Janoma und Svick)
Um dies besser zu veranschaulichen, möchte ich ein Beispiel mit Merge Sort (da Merge Sort der nächste weit verbreitete Sortieralgorithmus nach Quick Sort ist, denke ich) geben und Ihnen sagen, woher die zusätzlichen Konstanten kommen (nach meinem besten Wissen und warum ich denke) Schnelle Sortierung ist besser):
Betrachten Sie die folgende Sequenz:
Wenn Sie genau hinschauen, wie die letzte Phase abläuft, werden die ersten 12 mit 8 verglichen, und die 8 ist kleiner, sodass sie zuerst ausgeführt wird. Jetzt ist 12 WIEDER im Vergleich zu 21 und 12 geht weiter und so weiter und so fort. Wenn Sie die endgültige Zusammenführung vornehmen, dh 4 Elemente mit 4 anderen Elementen, entstehen viele EXTRA-Vergleiche als Konstanten, die bei der schnellen Sortierung NICHT anfallen. Dies ist der Grund, warum eine schnelle Sortierung bevorzugt wird.
quelle
in-place
, wird kein zusätzlicher Speicher benötigt.Meine Erfahrung mit realen Daten ist, dass Quicksort eine schlechte Wahl ist . Quicksort funktioniert gut mit zufälligen Daten, aber reale Daten sind meistens nicht zufällig.
Im Jahr 2008 habe ich einen hängenden Softwarefehler gefunden, der auf die Verwendung von Quicksort zurückzuführen war. Eine Weile später schrieb ich einfache Implementierungen von Insertion Sort, QuickSort, Heap Sort und Merge Sort und testete diese. Meine Zusammenführungssortierung hat alle anderen bei der Arbeit an großen Datenmengen übertroffen.
Seitdem ist Merge Sort mein bevorzugter Sortieralgorithmus. Es ist elegant. Es ist einfach zu implementieren. Es ist eine stabile Sorte. Es degeneriert nicht wie Quicksort zu quadratischem Verhalten. Ich wechsle zu Insertion Sort, um kleine Arrays zu sortieren.
Bei vielen Gelegenheiten habe ich gedacht, dass eine bestimmte Implementierung überraschend gut für Quicksort geeignet ist, nur um herauszufinden, dass es sich tatsächlich nicht um Quicksort handelt. Manchmal wechselt die Implementierung zwischen Quicksort und einem anderen Algorithmus und manchmal wird überhaupt kein Quicksort verwendet. Beispielsweise verwenden die qsort () - Funktionen von GLibc die Sortierung nach Zusammenführung. Nur wenn die Zuweisung des Arbeitsbereichs fehlschlägt, wird auf die vorhandene QuickSort zurückgegriffen, die ein Codekommentar als "den langsameren Algorithmus" bezeichnet .
Bearbeiten: Programmiersprachen wie Java, Python und Perl verwenden ebenfalls die Zusammenführungssortierung oder genauer eine Ableitung wie Timsort oder die Zusammenführungssortierung für große Mengen und die Einfügungssortierung für kleine Mengen. (Java verwendet auch Dual-Pivot-QuickSort, das schneller ist als einfaches QuickSort.)
quelle
1 - Schnelle Sortierung ist vorhanden (außer einer konstanten Menge wird kein zusätzlicher Speicher benötigt.)
2 - Schnelles Sortieren ist einfacher zu implementieren als andere effiziente Sortieralgorithmen.
3 - Schnelles Sortieren hat kleinere konstante Faktoren in seiner Laufzeit als andere effiziente Sortieralgorithmen.
Update: Für die Zusammenführungssortierung müssen Sie einige "Zusammenführungsvorgänge" ausführen, für die zusätzliche Arrays erforderlich sind, um die Daten vor der Zusammenführung zu speichern. aber in der schnellen Art tun Sie nicht. Deshalb gibt es eine schnelle Sortierung. Es gibt auch einige zusätzliche Zusammenführungsvergleiche, die die konstanten Faktoren bei der Zusammenführungssortierung erhöhen.
quelle
Unter welchen Bedingungen ist ein bestimmter Sortieralgorithmus tatsächlich der schnellste?
3) Besteht die zugrunde liegende Datenstruktur aus verknüpften Elementen? Ja -> benutze immer "in place merge sort". Es gibt sowohl einfach zu implementierende, festgelegte Größen als auch adaptive (oder auch natürliche) Bottom-up-Zusammenführungsarten für verknüpfte Datenstrukturen, und da sie niemals das Kopieren der gesamten Daten in jedem Schritt erfordern und auch keine Rekursionen erfordern, sind sie es auch schneller als alle anderen allgemeinen vergleichsbasierten Sortierungen, sogar schneller als die schnelle Sortierung.
5) Kann die Größe der zugrunde liegenden Daten an eine kleine bis mittlere Größe gebunden werden? zB ist n <10.000 ... 100.000.000 (abhängig von der zugrunde liegenden Architektur und Datenstruktur)? Ja -> Bitonische Sortierung oder Batcher Odd Even Mergesort verwenden. Gehe zu 1)
Implementierungshinweise für quicksort:
2) Es gibt Bottom-up-iterative Varianten von Quicksort, aber AFAIK, sie haben die gleichen asymptotischen Raum- und Zeitgrenzen wie die Top-down-Varianten, mit den zusätzlichen Nachteilen, dass sie schwierig zu implementieren sind (z. B. das explizite Verwalten einer Warteschlange). Ich habe die Erfahrung gemacht, dass diese für praktische Zwecke niemals in Betracht gezogen werden sollten.
Implementierungshinweise für Mergesort:
1) Bottom-Up-Mergesort ist immer schneller als Top-Down-Mergesort, da keine Rekursionsaufrufe erforderlich sind.
2) Die sehr naive Zusammenführungssortierung kann beschleunigt werden, indem ein Doppelpuffer verwendet und der Puffer umgeschaltet wird, anstatt die Daten nach jedem Schritt aus dem zeitlichen Array zurück zu kopieren.
3) Bei vielen realen Daten ist die adaptive Zusammenführung viel schneller als eine Zusammenführung mit fester Größe.
Aus dem, was ich geschrieben habe, geht hervor, dass Quicksort oft nicht der schnellste Algorithmus ist, außer wenn die folgenden Bedingungen zutreffen:
1) Es gibt mehr als "wenige" mögliche Werte
2) Die zugrunde liegende Datenstruktur ist nicht verknüpft
3) Wir brauchen keine stabile Bestellung
4) Die Daten sind groß genug, dass die geringfügig suboptimale asymptotische Laufzeit eines bitonischen Sortierers oder eines Batcher Odd Even Mergesort einsetzt
5) Die Daten sind nicht fast sortiert und bestehen nicht aus größeren, bereits sortierten Teilen
6) Wir können von mehreren Stellen gleichzeitig auf die Datensequenz zugreifen
ps: Jemand muss mir bei der Formatierung des Textes helfen.
quelle
Bei den meisten Sortierungsmethoden müssen die Daten in kurzen Schritten verschoben werden (z. B. führt das Zusammenführen der Sortierung Änderungen lokal durch, führt dann dieses kleine Datenelement zusammen und führt dann ein größeres zusammen ...). Infolgedessen benötigen Sie viele Datenbewegungen, wenn die Daten weit vom Ziel entfernt sind.
quelle