Heapsort

Heapsort
Der Heapsort-Algorithmus beim Sortieren eines Arrays aus permutierten Werten. Der Algorithmus besteht aus zwei Schritten; im vorbereitenden Schritt wird das Array zu einem binären Heap umgeordnet, dessen Baumstruktur vor dem eigentlichen Sortierschritt kurz eingeblendet wird.

Heapsort oder Haldensortierung ist ein 1964 von Robert W. Floyd und J. W. J. Williams entwickeltes, relativ schnelles Sortierverfahren. Es handelt sich um eine Verbesserung von Selectionsort. Seine Komplexität ist bei einem Array der Länge n in der Landau-Notation ausgedrückt  \mathcal{O}(n \cdot \log (n)) . Heapsort arbeitet zwar in-place, ist jedoch nicht stabil.

Inhaltsverzeichnis

Heaps

Die Voraussetzung dafür, dass man ein Array von sortierbaren Werten mit Heapsort sortieren kann, ist, dass dieses einen binären Heap (auch "Halde" oder "Haufen") repräsentiert. Ist dies nicht der Fall, so muss man es zuerst in einen Heap überführen.

Man beachte, dass für die zweite Hälfte jedes Arrays die Heap-Eigenschaft bereits erfüllt ist, denn jeder Knoten in der zweiten Hälfte des Arrays entspricht im Heap einem Blatt, hat also keinen Nachfolgeknoten, dessen Wert größer sein könnte als der eigene (im Folgenden wird der Einfachheit halber der Knoten mit dem größten Wert der „größte Knoten“ genannt).

Überführung in einen Max-Heap

Um ein Array in einen Heap zu überführen, beginnt man deshalb in der Mitte des Arrays. Man „versickert“ nun sukzessive alle davor liegenden Knoten, bis das erste Element versickert wurde. Versickern (auch: absinken) heißt, dass man einen Knoten mit dem größeren seiner beiden Nachfolgeknoten vertauscht, falls dieser größer ist als er selbst, und damit so lange fortfährt, bis er keinen Nachfolgeknoten mehr hat, der größer ist als er selbst.

Um die Rechnung zu vereinfachen, wird im Folgenden vorausgesetzt, dass das erste Element des Arrays den Index 1 hat. Die Nachfolger eines Knotens mit dem Index i liegen dann an den Positionen 2·i und 2·i+1.

Beispiel

Repräsentation des Arrays [H|E|A|P|S|O|R|T] als Binärbaum
In einen Max-Heap überführter Binärbaum, entspricht [T|S|R|P|H|O|A|E]

Man möchte ein Array mit dem Inhalt [H|E|A|P|S|O|R|T] in einen Max-Heap überführen, wobei gilt: A<B<C<…<Y<Z.

1 2 3 4 5 6 7 8
H|E A|P S O R|T    Wir beginnen links von der Mitte des Wortes, d. h. bei P:
 |   |^      |^    sein Nachfolger im Baum ist T. Da T>P ist, tauschen wir beide.
H|E A|T S O R|P    Wir fahren mit dem davor liegenden Knoten A fort. Seine Nachfolger sind
 |  ^|    ^ ^|     O und R. Es gilt sowohl O>A als auch R>O,
 |   |       |        also tauschen wir R und A.
H|E R|T S O A|P    Dann vergleichen wir E mit seinen Nachfolgern T und S.
 |^  |^ ^    |     Es gilt T>S und T>E. Deshalb müssen wir T und E vertauschen.
H|T R|E S O A|P    Wir müssen nun E weiter versickern, denn der neue
 |   |^      |^    Nachfolger von E ist P, und P>E.
H|T R|P S O A|E    Nun vergleichen wir H mit seinen
^|^ ^|       |     Nachfolgern T und R. T>R und T>H.
T|H R|P S O A|E    Wir versickern das H weiter.
 |^  |^ ^    |     S>P und S>H.
T|S R|P H O A|E    Wir haben das Array nun in einen Max-Heap überführt.
(Hilfslinien deuten die Baumebenen an.)

Prinzip der Sortierung (Max-Heap)

Der eigentliche Sortieralgorithmus nutzt die Tatsache aus, dass die Wurzel eines Heaps stets der größte Knoten ist. Da im fertig sortierten Array der größte Wert ganz rechts stehen soll, vertauscht man das erste mit dem letzten Arrayelement. Das Element am Ende des Arrays ist nun an der gewünschten Position und bleibt dort. Den Rest des Arrays muss man wieder in einen Max-Heap überführen, indem man das erste Element versickern lässt. Anschließend vertauscht man das erste mit dem vorletzten Element, d. h. die beiden größten Werte sind wie gewünscht am Ende des Arrays. Man versickert das nun erste Element, usw.

Beispiel für Heapsort mit Zahlen

Ein Beispiel für Heapsort an einer Zahlenfolge

Der gezeigte Heap ist ein Max-Heap und hätte als Ergebnis keine Absteigende sondern eine aufsteigende Reihe. Siehe z.B Im Referenzbuch "Algorithmen und Datenstrukturen" von Saake und Sattler!

Beispiel für Heapsort mit Buchstaben

Ein weiteres Beispiel mit Buchstaben : Wir sortieren den oben erhaltenen Heap [T|S|R|P|H|O|A|E].

1 2 3 4 5 6 7 8
T S R P H O A E     Wir vertauschen das erste Element mit dem letzten.
^             ^
E S R P H O A|T     Das T ist nun an der korrekten Position.
^ ^ ^               Nun müssen wir das E versickern. E < S und S > R
S E R P H O A|T     P>H und P>E.
  ^   ^ ^
S P R E H O A|T     Wir versickern E nicht weiter, denn T liegt bereits
^           ^       jenseits des Heaps im fertig sortierten Bereich.
                    Wir haben also wieder einen korrekten Heap und können
                    S und A vertauschen, womit auch das S sortiert ist.
A P R E H O|S T     Jetzt müssen wir das A versickern.
^ ^ ^               R>P und R>A.
R P A E H O|S T     Da das S bereits korrekt liegt, vergleichen wir
    ^     ^         nur A und O. O>A.
R P O E H A|S T     Die Heap-Eigenschaft für das linke Teilarray ist wieder
^         ^         erfüllt. Wir vertauschen R und A.
A P O E H|R S T     Wir versickern A.
^ ^ ^               P>O und P>A.
P A O E H|R S T     Wir versickern A weiter. H>E und H>A.
  ^   ^ ^
P H O E A|R S T     Wir vertauschen P und A.
^       ^
A H O E|P R S T     Wir versickern A.
^ ^ ^               O>H und O>A.
O H A E|P R S T     Wir vertauschen O und E.
^     ^
E H A|O P R S T     Wir versickern E.
^ ^ ^               H>A und H>E.
H E A|O P R S T     Wir vertauschen H und A.
^   ^
A E|H O P R S T     Wir versickern A. E>A.
^ ^
E A|H O P R S T     Wir vertauschen E und A.
^ ^
A|E H O P R S T     Das Array ist jetzt fertig sortiert.

Laufzeit

Man kann zeigen, dass der Aufbau des Heaps, in Landau-Notation ausgedrückt, in  \mathcal{O}(n) Schritten ablaufen kann. In einem großen, zufällig verteilten Datenfeld (100 bis 10^10 Datenelemente) sind durchschnittlich mehr als 4 aber weniger als 5 signifikante Sortieroperationen pro Element nötig (2,5 Datenvergleiche und 2,5 Zuweisungen). Dies liegt daran, dass ein zufälliges Element mit exponentiell zunehmender Wahrscheinlichkeit einen geeigneten Vaterknoten findet (60%, 85%, 93%, 97%, ...). Den Worst Case stellen mit  \mathcal{O}(n \cdot \log (n)) weitgehend vorsortierte Daten dar, weil der Heapaufbau de facto eine schrittweise vollständige Invertierung der Sortierreihenfolge darstellt. Der günstigste, aber unwahrscheinliche, Fall ist ein bereits umgekehrt sortiertes Datenfeld (1 Vergleich pro Element, keine Zuweisung). Gleiches gilt, wenn fast alle Daten identisch sind.

Das Versickern eines Elements von der Wurzel her benötigt im ungünstigsten Fall (Worst Case)  \mathcal{O}(\log (n)) Schritte. Dies ist bei exakt inverser Reihenfolge der Fall. Durchschnittlicher und ungünstigster Fall unterscheiden sich jedoch praktisch nur unwesentlich:  \mathcal{O}(\log (n/2)) zu  \mathcal{O}(\log (n)) . Günstig ist nur ein Feld, dessen Elemente fast alle den gleichen Wert haben. Sind aber nur weniger als ca. 80% der Daten identisch, dann entspricht die Laufzeit bereits dem durchschnittlichen Fall. Eine vorteilhafte Anordnung von Daten mit mehreren verschiedenen Werten ist prinzipbedingt unmöglich, da dies der Heapcharakteristik widerspricht.

Auf heterogenen Daten - vorsortiert oder nicht - dominiert das Versickern mit wenigstens über 60% der Zeit, meistens über 80%. Somit garantiert Heapsort eine Gesamtlaufzeit von  \mathcal{O}(n \cdot \log (n)) . Auch im besten Fall wird eine Laufzeit von  \mathcal{O}(n \cdot \log (n)) benötigt.[1][2]

Im Durchschnitt ist Heapsort nur dann schneller als Quicksort, wenn Vergleiche auf den zu sortierenden Daten sehr aufwendig sind und gleichzeitig eine für Quicksort ungünstige Datenanordnung besteht (z.B. viele gleiche Elemente). In der Praxis ist bei unsortierten oder teilweise vorsortierten Daten Quicksort oder Introsort um einen konstanten Faktor (2 bis 5) schneller als Heapsort. Dies wird jedoch kontrovers diskutiert und es gibt Analysen die Heapsort vorne sehen, sowohl aus Implementierungs- wie auch aus informationstheoretischen Überlegungen. [3][4] Allerdings spricht das Worst-Case-Verhalten von  \mathcal{O}(n \cdot \log (n)) gegenüber  \mathcal{O}(n^2 ) bei Quicksort für Heapsort. Introsort ist dagegen in fast allen Fällen schneller als Heapsort, lediglich in entarteten Fällen 20 % bis 30 % langsamer.

Varianten

Bottom-Up-Heapsort

Die wichtigste Variante des Heapsort-Algorithmus ist das Bottom-Up-Heapsort, das häufig fast die Hälfte der nötigen Vergleichsoperationen einsparen kann und sich folglich besonders da rentiert, wo Vergleiche aufwendig sind. (Siehe dort auch die wissenschaftlich entscheidenden Referenzen zu Heapsort.)

Smoothsort

Normales Heapsort sortiert bereits weitgehend vorsortierte Felder nicht schneller als andere. Die größten Elemente müssen immer erst ganz nach vorn an die Spitze des Heaps wandern, bevor sie wieder nach hinten kopiert werden. Smoothsort ändert das, indem es im Prinzip die Reihenfolge des Heaps umdreht. Dabei ist allerdings beträchtlicher Aufwand nötig, um den Heapstatus beim Sortieren aufrechtzuerhalten.

Ternäre Heaps

Eine andere Optimierung verwendet statt binären ternäre Heaps. Diese Heaps beruhen statt auf Binärbäumen auf Bäumen, bei denen jeder vollständig besetzte Knoten 3 Kinder hat. Damit kann die Zahl der Vergleichsoperationen um etwa 3–4 % reduziert werden (bei einigen Tausend bis einigen Millionen Elementen). Außerdem sinkt der sonstige Aufwand deutlich; insbesondere müssen durch den flacheren Heap knapp ein Drittel weniger Elemente beim Versickern verschoben werden.

In einem binären Heap kann ein Element mit 2n Vergleichen um n Ebenen abgesenkt werden und hat dabei durchschnittlich 3/2·(2n-1) Indizes übersprungen. In einem ternären Heap kann ein Element mit 3m Vergleichen um m Ebenen abgesenkt werden und hat dabei durchschnittlich 3m-1 Indizes übersprungen. Wenn bei gleicher Reichweite der relative Aufwand x := 3m/2n ist, dann gilt

\frac{3}{2}\left(2^n-1\right)=3^{\frac{2n}{3}x}-1, alsox=\frac{3}{2n}\log_3\left(\frac{3}{2}2^n-\frac{1}{2}\right)=_{n\to\infty}\frac{3}{2}\log_3 2 \approx 0{,}946

Bei n=18 (realistisch bei knapp 1 Million Elementen) ergibt sich 0,977; die 1 wird oberhalb von n=10 unterschritten. In der Praxis ist die Ersparnis etwas größer, u.a. deswegen, weil ternäre Bäume mehr Blätter haben und deshalb beim Aufbau des Heaps weniger Elemente versickert werden müssen.

Insgesamt kann die Sortierung mit ternären Heaps bei großen Feldern (mehrere Millionen Elemente) und einfacher Vergleichsoperation 20–30 % Zeit einsparen. Bei kleinen Feldern (bis etwa tausend Elemente) lohnen sich ternäre Heaps nicht oder sind sogar langsamer.

n-äre Heaps

Bei noch breiteren Bäumen bzw. flacheren Heaps steigt die Zahl der nötigen Vergleichsoperationen wieder an. Trotzdem können sie vorteilhaft sein, weil der sonstige Aufwand (vor allem der für das Kopieren von Elementen) weiter sinkt und geordneter auf die Elemente zugegriffen wird, was die Effizienz von Caches erhöhen kann. Sofern bei großen Feldern nur ein einfacher numerischer Vergleich durchzuführen ist und Schreiboperationen relativ langsam sind, können 8 oder mehr Kinder pro Knoten optimal sein.

Einfache Beispielsimplementierung mit Heaps beliebiger Arität (ab 2) in C99:

int heapsort( int * data, int n )    // zu sortierendes Feld und seine Länge
{
  const int WIDTH= 8;                // Anzahl der Kinder pro Knoten
  int val, parent, child, w, max, i;
  int m= (n + (WIDTH - 2)) / WIDTH;  // erstes Blatt im Baum
  int count= 0;                      // Zähler für Anzahl der Vergleiche
 
  while ( 1 )
  {
    if ( m ) {                       // Teil 1: Konstruktion des Heaps
      parent= --m;
      val= data[parent];             // zu versickernder Wert
    }
    else
    if ( --n ) {                     // Teil 2: eigentliche Sortierung
      val= data[n];                  // zu versickernder Wert vom Heap-Ende
      data[n]= data[0];              // Spitze des Heaps hinter den Heap in
      parent= 0;                     //   den sortierten Bereich verschieben
    }
    else                             // Heap ist leer; Sortierung beendet
      break;
 
    while ( (child= parent * WIDTH + 1) < n )  // erstes Kind;
    {                                          // Abbruch am Ende des Heaps
      w= n - child < WIDTH ?
         n - child : WIDTH;          // Anzahl der vorhandenen Geschwister
 
      count += w;
 
      for ( max= 0, i= 1; i < w; ++i )         // größtes Kind suchen
        if ( data[child+i] > data[child+max] )
          max= i;
 
      child += max;
 
      if ( data[child] <= val )      // kein Kind mehr größer als der
        break;                       //   zu versickernde Wert
 
      data[parent]= data[child];     // größtes Kind nach oben rücken
      parent= child;                 // in der Ebene darunter weitersuchen
    }
 
    data[parent]= val;               // versickerten Wert eintragen
  }
 
  return count;
}

Zu Vergleichszwecken gibt die Funktion die Anzahl der durchgeführten Vergleiche zurück.

Siehe auch

Einzelnachweise

  1. Russel Schaffer, Robert Sedgewick: The analysis of heapsort. In: Journal of Algorithms, Volume 15, Issue 1. Juli 1993. S. 76–100, ISSN 0196-6774
  2. Bela Bollobas, Trevor I. Fenner, Alan M. Frieze: On the best case of heapsort. In: Journal of Algorithms, Volume 20, Issue 2. März 1996. S. 205–217. ISSN 0196-6774
  3. Paul Hsieh (2004): Sorting revisited.. www.azillionmonkeys.com. Abgerufen am 26. April 2010.
  4. David MacKay (1. Dezember 2005): Heapsort, Quicksort, and Entropy. www.aims.ac.za/~mackay. Archiviert vom Original am 7. Juni 2007. Abgerufen am 26. April 2010.

Weblinks

Wikibooks Wikibooks: Heapsort – Implementierungen in der Algorithmensammlung
 Commons: Heap sort – Sammlung von Bildern, Videos und Audiodateien

Wikimedia Foundation.

Игры ⚽ Поможем написать реферат

Schlagen Sie auch in anderen Wörterbüchern nach:

  • Heapsort —   [dt. »Sortierung von Haufen«], ein 1964 von James W. J. Williams entwickelter Algorithmus, mit dem beliebige Daten in auf oder absteigender Reihenfolge sortiert werden. Er basiert auf dem 1962 von Robert W. Floyd veröffentlichten Treesort… …   Universal-Lexikon

  • Heapsort — Infobox Algorithm class=Sorting algorithm A run of the heapsort algorithm sorting an array of randomly permuted values. In the first stage of the algorithm the array elements are reordered to satisfy the heap property. Before the actual sorting… …   Wikipedia

  • Heapsort — Animación mostrando el funcionamiento del heapsort. El ordenamiento por montículos (heapsort en inglés) es un algoritmo de ordenamiento no recursivo, no estable, con complejidad computacional Θ(nlog n) Este algoritmo consiste en almacenar… …   Wikipedia Español

  • Heapsort — …   Википедия

  • Heapsort — El ordenamiento por montículos (Heap sort en inglés) es un algoritmo de ordenación no recursivo, no estable, con complejidad computacional O(n log n). Este algoritmo consiste en almacenar todos los elementos del vector a ordenar en un …   Enciclopedia Universal

  • Heapsort — Heap|sort [ hi:psɔ:t] das; s, s <zu gleichbed. engl. heap »Haufen« u. to sort »sortieren; trennen«> schnelles internes Sortierverfahren (EDV) …   Das große Fremdwörterbuch

  • Bottom-Up-Heapsort — BottomUp Heapsort ist ein Sortieralgorithmus, der u. a. 1990 von Ingo Wegener vorgestellt wurde und im Durchschnitt besser als Quicksort arbeitet, falls man Vergleichsoperationen hinreichend stark gewichtet. Es ist eine Variante von Heapsort, die …   Deutsch Wikipedia

  • BottomUp-HeapSort — ist ein Sortieralgorithmus, der u. a. 1990 von Ingo Wegener vorgestellt wurde und im Durchschnitt besser als Quicksort arbeitet, falls man Vergleichsoperationen hinreichend stark gewichtet. Es ist eine Variante von Heapsort, die vor allem zur… …   Deutsch Wikipedia

  • BottomUp-Heapsort — ist ein Sortieralgorithmus, der u. a. 1990 von Ingo Wegener vorgestellt wurde und im Durchschnitt besser als Quicksort arbeitet, falls man Vergleichsoperationen hinreichend stark gewichtet. Es ist eine Variante von Heapsort, die vor allem zur… …   Deutsch Wikipedia

  • Haldensortierung — Der Heapsort Algorithmus beim Sortieren eines Arrays aus permutierten Werten. Der Algorithmus besteht aus zwei Schritten; im vorbereitenden Schritt wird das Array zu einem binären Heap umgeordnet, dessen Baumstruktur vor dem eigentlichen… …   Deutsch Wikipedia

Share the article and excerpts

Direct link
Do a right-click on the link above
and select “Copy Link”