it-swarm.com.de

Quicksort: Auswahl des Pivots

Bei der Implementierung von Quicksort müssen Sie unter anderem einen Pivot auswählen. Aber wenn ich mir den Pseudocode wie den folgenden anschaue, ist es 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 festzustellen, ob unterschiedliche Szenarien unterschiedliche Strategien erfordern oder nicht.

103

Durch die Auswahl eines zufälligen Pivots wird die Wahrscheinlichkeit minimiert, dass Sie auf den ungünstigsten Fall O (n) stoßen2) Leistung (bei nahezu sortierten oder nahezu rücksortierten Daten würde immer zuerst oder zuletzt gewählt). Die Auswahl des mittleren Elements wäre in den meisten Fällen ebenfalls akzeptabel.

Wenn Sie dies selbst implementieren, gibt es Versionen des Algorithmus, die direkt funktionieren (d. H. Ohne zwei neue Listen zu erstellen und diese dann zu verketten).

81
Kip

Das hängt von Ihren Anforderungen ab. Durch die zufällige Auswahl eines Pivots wird es schwieriger, einen Datensatz zu erstellen, der die Leistung von O (N ^ 2) generiert. Der 'Median-of-Three'-Wert (erster, letzter, mittlerer) dient auch dazu, Probleme zu vermeiden. Achten Sie jedoch auf die relative Leistung von Vergleichen. Wenn Ihre Vergleiche kostspielig sind, führt Mo3 mehr Vergleiche durch, als Sie zufällig auswählen (ein einzelner Pivot-Wert). Der Vergleich von Datenbankeinträgen kann kostspielig sein.


Update: Kommentare in Antwort ziehen.

mdkess behauptet:

'Median von 3' ist NICHT die erste letzte Mitte. Wählen Sie drei zufällige Indizes und nehmen Sie den mittleren Wert davon. Der springende Punkt ist, sicherzustellen, dass Ihre Auswahl der Pivots nicht deterministisch ist - wenn dies der Fall ist, können Daten für den schlimmsten Fall recht einfach generiert werden.

Auf die ich geantwortet habe:

  • Analyse des Hoare-Find-Algorithmus mit der Median-of-Three-Partition (1997) von P. Kirschenhofer, H. Prodinger, C. Martínez, unterstützt Ihre Behauptung (dieser 'Median-of-Three' sind drei zufällige Elemente).

  • Unter portal.acm.org ist ein Artikel beschrieben, der von Hannu Erkiö, veröffentlicht im Computer Journal, Bd. 27, Nr. 3, 1984, über 'The Worst Case Permutation for Median-of-Three Quicksort' handelt . [Update 2012-02-26: Habe den Text für den Artikel . Abschnitt 2 'Der Algorithmus' beginnt: ' Durch Verwendung des Medians des ersten, mittleren und letzten Elements von A [L: R] können effiziente Partitionen in Teile von ziemlich gleicher Größe erreicht werden praktischste Situationen. 'Es handelt sich also um den First-Middle-Last-Mo3-Ansatz.]

  • Ein weiterer interessanter kurzer Artikel ist von M. D. McIlroy, "Ein Killer-Gegner für Quicksort" , veröffentlicht in Software-Practice and Experience, Vol. 3, No. 29 (0), 1–4 (0 1999). Es wird erklärt, wie man fast jeden Quicksort quadratisch macht.

  • AT & T Bell Labs Tech Journal, Oktober 1984 "Theorie und Praxis bei der Konstruktion einer funktionierenden Sortierroutine" besagt, dass Hoare eine Aufteilung um den Median mehrerer zufällig ausgewählter Linien vorschlug. [...] Sedgewick empfahl, den Median der ersten [...] zu wählen. ..] letzte [...] und mittlere ". Dies weist darauf hin, dass beide Techniken für den "Median von drei" in der Literatur bekannt sind. (Update 23.11.2014: Der Artikel ist anscheinend abrufbar unter IEEE Xplore oder ab Wiley - 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 sie entschieden sich für ein adaptives Verfahren Partitionierungsalgorithmus, der teilweise auf der Größe des Datensatzes basiert. Es wird viel über Kompromisse für verschiedene Ansätze diskutiert.

  • Eine Google-Suche nach "Median-of-Three" eignet sich gut für die weitere Verfolgung.

Danke für die Information; Ich hatte bisher nur den deterministischen „Median von drei“ erlebt.

55

Heh, ich habe gerade diese Klasse unterrichtet.

Es gibt verschiedene Möglichkeiten.
Einfach: Wählen Sie das erste oder letzte Element des Bereichs aus. (schlecht bei teilweise sortierten Eingaben) Besser: Wählen Sie den Artikel in der Mitte des Bereichs. (besser bei teilweise sortierten Eingaben)

Wenn Sie jedoch ein beliebiges Element auswählen, besteht die Gefahr, dass das Array der Größe n schlecht in zwei Arrays der Größen 1 und n-1 aufgeteilt wird. Wenn Sie das oft genug tun, läuft Ihre Quicksortierung Gefahr, O (n ^ 2) zu werden.

Eine Verbesserung, die ich gesehen habe, ist der mittlere Wert (erster, letzter, mittlerer). Im schlimmsten Fall kann es immer noch nach 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 (ein statistisch guter Dreh- und Angelpunkt für teilweise sortierte Daten).

Wenn Sie immer noch auf Probleme stoßen, gehen Sie den Mittelweg.

17
Chris Cudmore

Wählen Sie niemals einen festen Pivot - dies kann angegriffen werden, um die Worst-Case-O (n ^ 2) -Laufzeit Ihres Algorithmus auszunutzen, die nur nach Problemen fragt. Die Worst-Case-Laufzeit von Quicksort tritt auf, wenn die Partitionierung ein Array mit 1 Element und ein Array mit n-1 Elementen ergibt. Angenommen, Sie wählen das erste Element als Ihre Partition. Wenn jemand Ihrem Algorithmus ein Array in absteigender Reihenfolge zuführt, ist Ihr erster Pivot der größte, sodass sich alles andere im Array links davon befindet. Wenn Sie dann wiederkehren, wird das erste Element wieder das größte sein, also stellen Sie noch einmal alles links davon und so weiter.

Eine bessere Technik ist die Median-of-3-Methode, bei der Sie drei Elemente nach dem Zufallsprinzip auswählen und die Mitte auswählen. Sie wissen, dass das Element, das Sie auswählen, nicht das erste oder das letzte sein wird, aber nach dem zentralen Grenzwertsatz ist die Verteilung des mittleren Elements normal, was bedeutet, dass Sie in Richtung der Mitte tendieren (und daher , n lg n Zeit).

Wenn Sie unbedingt die Laufzeit von O(nlgn) für den Algorithmus garantieren möchten, wird die Methode 5-Spalten zum Ermitteln des Medians eines Arrays in der Zeit O(n) ausgeführt bedeutet, dass die Wiederholungsgleichung für die schnelle Sortierung im schlimmsten Fall T(n) = O(n) (finde den Median) + O(n) ist. (Partition) + 2T (n/2) (links und rechts rekursiv) Nach dem Hauptsatz ist dies O (n lg n). Der konstante Faktor wird jedoch sehr groß sein, und wenn die Leistung im schlimmsten Fall Ihr Hauptanliegen ist, verwenden Sie stattdessen eine Zusammenführungssortierung, die im Durchschnitt nur ein wenig langsamer als die Quicksortierung ist und O(nlgn) Zeit garantiert (und wird viel schneller sein als diese lahme mittlere Quicksorte).

Erklärung des Median-of-Medians-Algorithmus

9
mindvirus

Versuche nicht, zu schlau zu werden und kombiniere Schwenkstrategien. Wenn Sie den Median von 3 mit einem zufälligen Pivot kombinieren, indem Sie den Median des ersten, letzten und eines zufälligen Index in der Mitte auswählen, sind Sie immer noch anfällig für viele Verteilungen, die einen Median von 3 quadratisch senden (also ist es tatsächlich schlimmer als einfacher zufälliger Pivot)

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.

6
paperhorse

Es ist einfacher, die Quicksorte auf diese Weise in drei Abschnitte zu unterteilen

  1. Datenelementfunktion austauschen oder austauschen
  2. Die Partitionsfunktion
  3. Verarbeiten der Partitionen

Es ist nur geringfügig ineffektiver als eine lange Funktion, aber es ist viel einfacher zu verstehen.

Code folgt:

/* This selects what the data type in the array to be sorted is */

#define DATATYPE long

/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */

void swap (DATATYPE *x, DATATYPE *y){  
  DATATYPE Temp;

  Temp = *x;        // Hold current x value
  *x = *y;          // Transfer y to x
  *y = Temp;        // Set y to the held old x value
};


/* This is the partition code */

int partition (DATATYPE list[], int l, int h){

  int i;
  int p;          // pivot element index
  int firsthigh;  // divider position for pivot element

  // Random pivot example shown for median   p = (l+h)/2 would be used
  p = l + (short)(Rand() % (int)(h - l + 1)); // Random partition point

  swap(&list[p], &list[h]);                   // Swap the values
  firsthigh = l;                                  // Hold first high value
  for (i = l; i < h; i++)
    if(list[i] < list[h]) {                 // Value at i is less than h
      swap(&list[i], &list[firsthigh]);   // So swap the value
      firsthigh++;                        // Incement first high
    }
  swap(&list[h], &list[firsthigh]);           // Swap h and first high values
  return(firsthigh);                          // Return first high
};



/* Finally the body sort */

void quicksort(DATATYPE list[], int l, int h){

  int p;                                      // index of partition 
  if ((h - l) > 0) {
    p = partition(list, l, h);              // Partition list 
    quicksort(list, l, p - 1);        // Sort lower partion
    quicksort(list, p + 1, h);              // Sort upper partition
  };
};
1
Uglybb

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 gleichmäßig 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 dies das schnellste Element ist, auf das zugegriffen werden kann. Wenn die Liste 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 nur die erste auswählen, wird dies die Sache nur verschlimmern. Wenn Sie das mittlere Element in einer Liste auswählen, müssen Sie es bei jedem Partitionsschritt schrittweise durchgehen und eine O(N/2) - Operation hinzufügen, die logN-mal ausgeführt wird, um die Summe zu bilden Zeit O (1,5 N * log N) und das ist, wenn wir wissen, wie lange die Liste dauert, bevor wir beginnen - normalerweise tun wir das nicht, also müssten wir den ganzen Weg durchgehen, um sie zu zählen, und dann den halben Weg durchgehen Um die Mitte zu finden, gehen Sie ein drittes Mal durch, um die eigentliche Partition zu erstellen: O (2.5N * log N)

1
James Curran

Es hängt ganz davon ab, wie Ihre Daten sortiert werden. Wenn Sie glauben, dass es ein Pseudo-Zufall ist, ist es am besten, eine zufällige Auswahl zu treffen oder die Mitte zu wählen.

1
Joe Phillips

Ich empfehle die Verwendung des mittleren Index, da dieser leicht berechnet werden kann.

Sie können es durch Runden berechnen (array.length/2).

0
Milesman34

Im Durchschnitt ist der Median von 3 gut für kleine n. Der Median von 5 ist etwas besser für ein größeres n. Der Ninther, der der "Median von drei Medianen von drei" ist, ist für sehr große n sogar besser.

Je höher Sie mit dem Sampling sind, desto besser wird es, wenn n ansteigt. Die Verbesserung verlangsamt sich jedoch dramatisch, wenn Sie die Samples erhöhen. Und Sie haben den Aufwand für das Abtasten und Sortieren von Proben.

0
S0lo

Die Komplexität der schnellen Sortierung variiert stark mit der Auswahl des Pivot-Werts. 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 aus. 2. Vergleichen Sie diese drei Zahlen und finden Sie die Zahl, die größer als eins und kleiner als der andere ist, d. H. 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 verringert sich die Komplexität auf O (nlog (n)).

0
vivek

Idealerweise sollte der Drehpunkt der mittlere Wert im gesamten Array sein. Dies verringert die Wahrscheinlichkeit, dass die Leistung im ungünstigsten Fall erzielt wird.

0
Faizan