Organisatorisches VL-07: Sortieren II: HeapSort (Datenstrukturen und Algorithmen, SS 2017) Vorlesung: Gerhard Woeginger (Zimmer 4024 im E1) Email: dsal-i1@algo.rwth-aachen.de Webseite: http://algo.rwth-aachen.de/lehre/ss17/dsa.php Nächste Vorlesung: Donnerstag, Mai 11, :15 11:45 Uhr, Aula 1 Gerhard Woeginger SS 2017, RWTH DSAL/SS 2017 VL-07: Sortieren II: HeapSort 1/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 2/28 Einführung Heapaufbau HeapSort Sortieren II / HeapSort Heaps Prioritätswarteschlangen DSAL/SS 2017 VL-07: Sortieren II: HeapSort /28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 4/28
Heaps (1) Heaps (2) Heap (deutsch: Halde, Haufen) Ein Heap ist ein Binärbaum, der Elemente mit Schlüsseln enthält. Die Heap-Bedingung für Max-Heaps fordert: Der Schlüssel eines Knotens ist stets mindestens so gross wie die Schlüssel seiner Kinder. Weiters gilt: Alle Ebenen (mit Ausnahme der untersten) sind komplett gefüllt. Die Blätter befinden sich damit auf einer (oder zwei) Ebene(n). Die Blätter der untersten Ebene sind linksbündig angeordnet. 0 16 1 2 14 4 5 6 8 7 9 7 8 9 2 4 1 16 14 8 7 9 2 4 1 0 1 2 4 5 6 7 8 9 Wurzel = roter Knoten Blätter = blaue Knoten plus die beiden rechten grünen Knoten Innere Knoten = alle Nicht-Blätter DSAL/SS 2017 VL-07: Sortieren II: HeapSort 5/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 6/28 Arrayeinbettung eines Heaps Heaps: Eigenschaften (1) Heaps werden oft in Arrays eingebettet: Arrayeinbettung Das Array a wird wie folgt als Binärbaum aufgefasst: Die Wurzel liegt in a[0]. Das linke Kind von a[i] liegt in a[2*i+1]. Das rechte Kind von a[i] liegt in a[2*i+2]. Vergrössert man den Schlüssel der Wurzel, so bleibt Baum ein Heap. Ab Position n 2 erfüllt jedes Array die Heapbedingung. Durch die möglichst vollständige Füllung der Ebenen werden Löcher im Array vermieden. Vergrössert man den Baum um ein Element, so wird das Array um genau ein Element länger. Ein Heap hat n 2 innere Knoten. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 7/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 8/28
Heaps: Eigenschaften (2) Definition Die Höhe eines Heaps ist die Anzahl von Kanten auf dem Weg von Wurzel zum äusserst linken Blatt. Die Höhe H eines Heaps mit n Elementen erfüllt (a) 2 H n und (b) n 2 H+1 1. Heapaufbau Ergo: H = log 2 n und H O(log n). Es sei l die Anzahl aller Knoten im linken Teilbaum, und es sei r die Anzahl aller Knoten im rechten Teilbaum, Dann gilt r l 2r + 1. Beweis: l 2 H 1 und r 2 H 1 1 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 9/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort /28 Naiver Heapaufbau (1) Naiver Heapaufbau (2) 2 Heapaufbau, naiv 9 4 7 15 14 8 16 2 7 14 8 16 9 4 15 Der Heap wird Reihe für Reihe von oben nach unten aufgebaut, indem ein neues Element möglichst weit links angefügt wird, und dann rekursiv nach oben getauscht wird, solange es grösser als sein Elternknoten ist. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 11/28 1 void bubble ( int E[], int pos ) { 2 while ( pos > 0) { int parent = floor ( ( pos -1) /2 ); 4 if (E[ parent ] > E[ pos ]) { 5 break ; 6 } 7 swap (E[ parent ], E[ pos ]); 8 pos = parent ; 9 } } Heap mit n Elementen hat Höhe H log n. Damit kostet jedes Einfügen H log n Vergleiche. Ergo: Zum naiven Aufbau eines Heaps mit n Elementen benötigt man O(n log n) Vergleiche, und im Worst-Case Ω(n log n) Vergleiche. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 12/28
Heapify (1): Bessere Strategie von [Floyd, 1964] Heapify (2) Betrachte Element E[i] unter der Annahme, dass der rechte und linke Teilbaum darunter bereits Heaps sind. Heapify Problem: E[i] kann kleiner als seine Kinder sein. Ziel: Wir wollen die beiden Teilbäume Heaps zusammen mit E[i] zu einem GesamtHeap verschmelzen. Methode: Wir lassen E[i] langsam in den Heap hineinsinken, wodurch der gesamte Teilbaum mit Wurzel E[i] zum Heap wird. Finde das Maximum der Werte E[i] und seiner beiden Kinder. Ist E[i] das grösste Element, dann ist der gesamte Teilbaum auch ein Heap. Fertig. Andernfalls vertausche E[i] mit dem grössten Element und führe Heapify in diesem Unterbaum weiter aus. 1 void heapify ( int E[], int n, int pos ) { 2 int next = 2 * pos + 1; while ( next < n) { 4 if ( next + 1 < n && 5 E[ next + 1] > E[ next ]) { 6 next = next + 1; 7 } 8 if (E[ pos ] >= E[ next ]) { 9 break ; } 11 swap (E[ pos ], E[ next ]); 12 pos = next ; 1 next = 2 * pos + 1; 14 } 15 } DSAL/SS 2017 VL-07: Sortieren II: HeapSort 1/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 14/28 Heapify (): Beispiel Heapify (4): Laufzeit 0 16 1 2 12 4 5 6 14 7 9 7 8 9 2 8 1 16 12 14 7 9 2 8 1 Heapify vergleicht den Knoten mit den beiden Kindern. zwei Vergleiche Die Worst-Case Komplexität von Heapify in einem Heap mit n Knoten ist daher maximal 2 log 2 n. Falls Heapify auf einen Teilbaum der Höhe h angewandt wird, ist die Worst-Case Komplexität 2h. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 15/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 16/28
BuildHeap (1) BuildHeap (2): Korrektheit Strategie: Wandle Array von unten nach oben (bottom-up) in Heap um. 9 4 7 15 14 2 8 16 1 void BuildHeap ( int E []) { 2 for ( int i = E. length / 2-1; i >= 0; i - -) { heapify (E, E. length, i); 4 } 5 } Nützliche Invariante: Nach jedem Aufruf von heapify(e,e.length,i) sind die Knoten i,..., E.length - 1 schon Wurzeln von Heaps. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 17/28 Der Algorithmus BuildHeap ist korrekt und terminiert. Initialisierung: Jeder Knoten i n/2 ist ein Blatt und damit Wurzel eines trivialen Heaps. Schleifen-Invariante: Zu Beginn der for-schleife ist jeder Knoten i+1,..., E.length die Wurzel eines Heaps. In jedem Schritt sind alle Kinder des betrachteten Knotens i bereits Wurzeln von Heaps (Schleifeninvariante). Die Bedingung für den Aufruf von heapify ist erfüllt. Dekrementierung von i stellt Schleifeninvariante wieder her. Terminierung: Bei i = 0 ist gemäss Schleifeninvariante jeder Knoten 1, 2,..., n die Wurzel eines Heaps. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 18/28 BuildHeap (): Laufzeit Fakten Ein Heap hat maximal n/2 h+1 Knoten mit der Höhe h. Formel für q < 1: h=0 h qh = q/(1 q) 2. Die Worst-Case Komplexität von BuildHeap ist Θ(n) HeapSort Beweis: Die Laufzeit von Heapify für einen Knoten der Höhe h ist 2h. Die Zeitkomplexität von BuildHeap ist daher ( log 2 (n) n ) h 2 h+1 2h 2n 2 h h=0 h=0 = 4n O(n). DSAL/SS 2017 VL-07: Sortieren II: HeapSort 19/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 20/28
HeapSort (1): Algorithmus und Beispiel HeapSort (2): Analyse 41 0 25 6 26 19 69 12 17 4 2 17 8 1 4 41 26 17 25 19 17 8 6 69 12 4 2 1 4 0 1 void heapsort ( int E []) { 2 BuildHeap (E); for ( int i = E. length - 1; i > 0; i - -) { 4 swap (E[0], E[i]); 5 heapify (E, i, 0); 6 } 7 } Die Worst-Case Komplexität von BuildHeap ist Θ(n). Die Worst-Case Komplexität von Heapify in einem Heap mit n Knoten ist maximal 2 log 2 n. Für HeapSort erhalten wir somit: ( n 1 ) W (n) 2 log 2 n + n i=1 = 2 (n 1) log 2 (n) + n O(n log n) Zusätzlicher Speicherplatzbedarf ist konstant (lokale Variablen). DSAL/SS 2017 VL-07: Sortieren II: HeapSort 21/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 22/28 HeapSort (): Zusammenfassung HeapSort sortiert in O(n log n) Zeit. HeapSort verwendet O(1) zusätzlichen Speicherplatz. HeapSort ist ein in-place Algorithmus. HeapSort ist nicht stabil. Prioritätswarteschlangen DSAL/SS 2017 VL-07: Sortieren II: HeapSort 2/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 24/28
Die Prioritätswarteschlange (1) Die Prioritätswarteschlange (2) Prioritätswarteschlange (priority queue) Betrachte Elemente (Datensätze), die mit einem Schlüssel (key) versehen sind. Jeder Schlüssel sei höchstens an ein Element vergeben. Schlüssel werden als Priorität betrachtet. void insert(priorityqueue pq, Element e, int k) fügt das Element e mit dem Schlüssel k in pq ein. Element getmin(priorityqueue pq) gibt das Element mit dem kleinsten Schlüssel zurück; benötigt nicht-leere pq. void delmin(priorityqueue pq) entfernt das Element mit dem kleinsten Schlüssel; benötigt nicht-leere pq. void decrkey(priorityqueue pq, Element e, int k) setzt den Schlüssel von Element e auf k; e muss in pq enthalten sein. k muss ausserdem kleiner als der bisherige Schlüssel von e sein. Mit Heaps ist eine effiziente Implementierung möglich. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 25/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 26/28 Drei Prioritätswarteschlangenimplementierungen Organisatorisches Operation Array sortiertes Array Heap isempty(pq) Θ(1) Θ(1) Θ(1) insert(pq,e,k) Θ(1) Θ(n)* Θ(log n) getmin(pq) Θ(n) Θ(1) Θ(1) delmin(pq) Θ(n)* Θ(1) Θ(log n) decrkey(pq,e,k) Θ(1) Θ(n)* Θ(log n) Nächste Vorlesung: Donnerstag, Mai 11, :15 11:45 Uhr, Aula 1 Webseite: http://algo.rwth-aachen.de/lehre/ss17/dsa.php * Beinhaltet das Verschieben aller Elemente rechts von k. DSAL/SS 2017 VL-07: Sortieren II: HeapSort 27/28 DSAL/SS 2017 VL-07: Sortieren II: HeapSort 28/28