Bei der Implementierung von Quicksort müssen Sie unter anderem einen Pivot auswählen. Aber wenn ich mir Pseudocode wie den folgenden ansehe, ist nicht klar, wie ich den Pivot wählen soll. Erstes Element der Liste? Etwas anderes?
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array
if x ≤ pivot then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))
Kann mir jemand helfen, das Konzept der Auswahl eines Pivots zu verstehen und ob unterschiedliche Szenarien unterschiedliche Strategien erfordern oder nicht.
algorithm
sorting
pseudocode
quicksort
Jacob T. Nielsen
quelle
quelle
Antworten:
Wenn Sie einen zufälligen Pivot auswählen, wird die Wahrscheinlichkeit minimiert, dass Sie auf die O (n 2 ) -Leistung im ungünstigsten Fall stoßen (immer die erste oder letzte Auswahl führt zu einer Worst-Case-Leistung für nahezu sortierte oder nahezu umgekehrt sortierte Daten). Die Wahl des mittleren Elements wäre in den meisten Fällen ebenfalls akzeptabel.
Wenn Sie dies selbst implementieren, gibt es auch Versionen des Algorithmus, die direkt funktionieren (dh ohne zwei neue Listen zu erstellen und diese dann zu verketten).
quelle
Das hängt von Ihren Anforderungen ab. Durch die zufällige Auswahl eines Pivots wird es schwieriger, einen Datensatz zu erstellen, der eine O (N ^ 2) -Leistung generiert. 'Median-of-Three' (erster, letzter, mittlerer) ist auch ein Weg, um Probleme zu vermeiden. Achten Sie jedoch auf die relative Leistung von Vergleichen. Wenn Ihre Vergleiche kostspielig sind, führt Mo3 mehr Vergleiche durch als die zufällige Auswahl (eines einzelnen Pivot-Werts). Der Vergleich von Datenbankeinträgen kann kostspielig sein.
Update: Kommentare in die Antwort ziehen.
mdkess behauptete:
Worauf ich geantwortet habe:
Die Analyse von Hoares Suchalgorithmus mit Median-of-Three-Partition (1997) von P. Kirschenhofer, H. Prodinger, C. Martínez unterstützt Ihre Behauptung (der „Median-of-Three“ besteht aus drei zufälligen Elementen).
Auf portal.acm.org ist ein Artikel beschrieben , der sich mit "Die Worst-Case-Permutation für den Median-of-Three-Quicksort" von Hannu Erkiö befasst und im Computer Journal, Band 27, Nr. 3, 1984, veröffentlicht wurde. [Update 2012-02- 26: Habe den Text für den Artikel . Abschnitt 2 'Der Algorithmus' beginnt: ' Mit dem Median des ersten, mittleren und letzten Elements von A [L: R] können in den meisten praktischen Situationen effiziente Partitionen in Teile von ziemlich gleicher Größe erzielt werden. 'Daher wird der erste-mittlere-letzte Mo3-Ansatz diskutiert.]
Ein weiterer interessanter interessanter Artikel ist von MD McIlroy, "A Killer Adversary for Quicksort" , veröffentlicht in Software-Practice and Experience, Vol. 3, No. 29 (0), 1–4 (0 1999). Es wird erklärt, wie sich fast jeder Quicksort quadratisch verhält.
AT & T Bell Labs Tech Journal, Oktober 1984 "Theorie und Praxis bei der Konstruktion einer funktionierenden Sortierroutine" besagt "Hoare schlug vor, um den Median mehrerer zufällig ausgewählter Linien zu partitionieren. Sedgewick empfahl [...], den Median der ersten [. ..] letzte [...] und mittlere ". Dies weist darauf hin, dass beide Techniken für den "Median-of-Three" in der Literatur bekannt sind. (Update 23.11.2014: Der Artikel scheint bei IEEE Xplore oder bei Wiley erhältlich zu sein - wenn Sie Mitglied sind oder bereit sind, eine Gebühr zu zahlen.)
'Engineering a Sort Function' von JL Bentley und MD McIlroy, veröffentlicht in Software Practice and Experience, Band 23 (11), November 1993, geht auf eine ausführliche Diskussion der Probleme ein und wählte einen adaptiven Partitionierungsalgorithmus, der teilweise auf dem basiert Größe des Datensatzes. Es gibt viele Diskussionen über Kompromisse für verschiedene Ansätze.
Eine Google-Suche nach "Median-of-Three" funktioniert ziemlich gut für die weitere Verfolgung.
Danke für die Auskunft; Ich war zuvor nur auf den deterministischen 'Median-of-Three' gestoßen.
quelle
Heh, ich habe gerade diese Klasse unterrichtet.
Es gibt mehrere Möglichkeiten.
Einfach: Wählen Sie das erste oder letzte Element des Bereichs aus. (schlecht bei teilweise sortierter Eingabe) Besser: Wählen Sie den Artikel in der Mitte des Bereichs aus. (besser bei teilweise sortierter Eingabe)
Bei Auswahl eines beliebigen Elements besteht jedoch die Gefahr, dass das Array der Größe n schlecht in zwei Arrays der Größe 1 und n-1 aufgeteilt wird. Wenn Sie dies oft genug tun, läuft Ihr Quicksort Gefahr, O (n ^ 2) zu werden.
Eine Verbesserung, die ich gesehen habe, ist der Auswahlmedian (erster, letzter, mittlerer); Im schlimmsten Fall kann es immer noch zu O (n ^ 2) gehen, aber wahrscheinlich ist dies ein seltener Fall.
Für die meisten Daten ist es ausreichend, die erste oder letzte auszuwählen. Wenn Sie jedoch feststellen, dass Sie häufig auf Worst-Case-Szenarien stoßen (teilweise sortierte Eingabe), besteht die erste Option darin, den zentralen Wert auszuwählen (was ein statistisch guter Dreh- und Angelpunkt für teilweise sortierte Daten ist).
Wenn Sie immer noch auf Probleme stoßen, gehen Sie den Medianweg.
quelle
Wählen Sie niemals einen festen Drehpunkt - dieser kann angegriffen werden, um die O (n ^ 2) -Laufzeit Ihres Algorithmus im ungünstigsten Fall auszunutzen, die nur nach Problemen fragt. Die Worst-Case-Laufzeit von Quicksort tritt auf, wenn die Partitionierung zu einem Array mit 1 Element und einem Array mit n-1 Elementen führt. Angenommen, Sie wählen das erste Element als Partition. Wenn jemand Ihrem Algorithmus ein Array in absteigender Reihenfolge zuführt, ist Ihr erster Pivot der größte, sodass alles andere im Array links davon verschoben wird. Wenn Sie dann wiederkehren, ist das erste Element wieder das größte, also setzen Sie noch einmal alles links davon und so weiter.
Eine bessere Technik ist die Median-of-3-Methode, bei der Sie drei Elemente zufällig auswählen und die Mitte auswählen. Sie wissen, dass das Element, das Sie auswählen, nicht das erste oder das letzte ist, aber nach dem zentralen Grenzwertsatz ist die Verteilung des mittleren Elements normal, was bedeutet, dass Sie zur Mitte (und damit) tendieren , n lg n Zeit).
Wenn Sie unbedingt die O (nlgn) -Laufzeit für den Algorithmus garantieren möchten, wird die Spalten-zu-5-Methode zum Ermitteln des Medians eines Arrays in O (n) -Zeit ausgeführt, was bedeutet, dass die Wiederholungsgleichung für Quicksort im schlimmsten Fall verwendet wird sei T (n) = O (n) (finde den Median) + O (n) (Partition) + 2T (n / 2) (rekursiere links und rechts). Nach dem Hauptsatz ist dies O (n lg n) . Der konstante Faktor wird jedoch sehr groß sein. Wenn die Leistung im ungünstigsten Fall Ihr Hauptanliegen ist, verwenden Sie stattdessen eine Zusammenführungssortierung, die im Durchschnitt nur ein wenig langsamer als Quicksort ist und die O (nlgn) -Zeit garantiert (und viel schneller ist) als dieser lahme Median Quicksort).
Erklärung des Median-of-Medians-Algorithmus
quelle
Versuchen Sie nicht, zu schlau zu werden und Pivot-Strategien zu kombinieren. Wenn Sie den Median von 3 mit dem zufälligen Pivot kombiniert haben, indem Sie den Median des ersten, letzten und eines zufälligen Index in der Mitte ausgewählt haben, sind Sie immer noch anfällig für viele der Verteilungen, die einen Median von 3 quadratisch senden (also ist er tatsächlich schlechter als einfacher zufälliger Drehpunkt)
ZB ist eine Pfeifenorgelverteilung (1,2,3 ... N / 2..3,2,1) zuerst und zuletzt beide 1 und der Zufallsindex ist eine Zahl größer als 1, wobei der Median 1 ergibt ( entweder zuerst oder zuletzt) und Sie erhalten eine extrem unausgeglichene Partitionierung.
quelle
Dabei ist es einfacher, den Quicksort in drei Abschnitte zu unterteilen
Es ist nur geringfügig ineffizienter als eine lange Funktion, aber viel einfacher zu verstehen.
Code folgt:
quelle
Es hängt ganz davon ab, wie Ihre Daten zunächst sortiert sind. Wenn Sie glauben, dass es pseudozufällig ist, wählen Sie am besten entweder eine zufällige Auswahl oder die Mitte.
quelle
Wenn Sie eine zufällig zugängliche Sammlung (wie ein Array) sortieren, ist es im Allgemeinen am besten, das physische mittlere Element auszuwählen. Wenn das Array fertig sortiert (oder fast sortiert) ist, sind die beiden Partitionen nahezu gleich und Sie erhalten die beste Geschwindigkeit.
Wenn Sie etwas mit nur linearem Zugriff sortieren (z. B. eine verknüpfte Liste), wählen Sie am besten das erste Element aus, da es das schnellste Element ist, auf das zugegriffen werden kann. Wenn die Liste hier jedoch bereits sortiert ist, sind Sie fertig - eine Partition ist immer null und die andere hat alles, was die schlechteste Zeit ergibt.
Wenn Sie jedoch für eine verknüpfte Liste etwas anderes als die erste auswählen, wird die Sache nur noch schlimmer. Wenn Sie das mittlere Element in einer Liste auswählen, müssen Sie es bei jedem Partitionsschritt schrittweise durchlaufen. Fügen Sie eine O (N / 2) -Operation hinzu, die logN-mal ausgeführt wird, wodurch die Gesamtzeit O (1,5 N * log N) ergibt. und das ist, wenn wir wissen, wie lang die Liste ist, bevor wir anfangen - normalerweise tun wir das nicht, also müssten wir den ganzen Weg durchlaufen, um sie zu zählen, dann auf halbem Weg, um die Mitte zu finden, dann durch a drittes Mal, um die eigentliche Partition durchzuführen: O (2.5N * log N)
quelle
Idealerweise sollte der Pivot der mittlere Wert im gesamten Array sein. Dies verringert die Wahrscheinlichkeit einer Worst-Case-Leistung.
quelle
Die Komplexität der schnellen Sortierung hängt stark von der Auswahl des Pivot-Werts ab. Wenn Sie beispielsweise immer das erste Element als Drehpunkt auswählen, wird die Komplexität des Algorithmus so schlecht wie O (n ^ 2). Hier ist eine intelligente Methode zur Auswahl des Pivot-Elements: 1. Wählen Sie das erste, mittlere und letzte Element des Arrays. 2. Vergleichen Sie diese drei Zahlen und finden Sie die Zahl, die größer als eine und kleiner als die andere ist, dh der Median. 3. Machen Sie dieses Element als Pivot-Element.
Durch Auswahl des Pivots nach dieser Methode wird das Array in fast zwei Hälften geteilt, und daher reduziert sich die Komplexität auf O (nlog (n)).
quelle
Im Durchschnitt ist der Median von 3 gut für kleine n. Der Median von 5 ist etwas besser für größere n. Der neunte, der "Median von drei Medianen von drei" ist, ist für sehr große n sogar noch besser.
Je höher Sie mit dem Sampling sind, desto besser werden Sie, wenn n zunimmt, aber die Verbesserung verlangsamt sich dramatisch, wenn Sie das Sampling erhöhen. Und Sie müssen die Proben entnehmen und sortieren.
quelle
Ich empfehle die Verwendung des mittleren Index, da dieser leicht berechnet werden kann.
Sie können es durch Runden berechnen (array.length / 2).
quelle
In einer wirklich optimierten Implementierung sollte die Methode zur Auswahl des Pivots von der Arraygröße abhängen. Bei einem großen Array lohnt es sich, mehr Zeit für die Auswahl eines guten Pivots aufzuwenden. Ohne eine vollständige Analyse würde ich vermuten, dass "Mitte von O (log (n)) Elementen" ein guter Anfang ist, und dies hat den zusätzlichen Vorteil, dass kein zusätzlicher Speicher benötigt wird: Verwenden von Tail-Call auf der größeren Partition und In Bei der Partitionierung verwenden wir in fast jeder Phase des Algorithmus denselben zusätzlichen O-Speicher (log (n)).
quelle