Vorlesung Datenstrukturen Heaps Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 469 Prioritätswarteschlange Problem Häufig ist das Prinzip einer einfachen Warteschlangen-Datenstruktur nicht ausreichend. Beispiele Mautstellen, Bevorzugung behinderter Personen, Prozessmanagement in Betriebssystemen, Greedy-Algorithmen, ... Abhilfe Verwendung einer modifizierten Variante der Schlangendatenstruktur, einer sogenannten Prioritätswarteschlange (engl. priority queue). Prioritätswarteschlange Elemente werden in Abhängigkeit von ihrer Priorität und ihrer Position aus der Warteschlange entnommen. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 470 Realisierung Intuitive Realisierung Prioritätswarteschlangen können ebenfalls mit Hilfe von Listen implementiert werden. Wir unterscheiden hierbei zwei Varianten: 1. Verwendung einer Schlangen-Datenstruktur mit Einfügen neuer Elemente am Ende der Liste. 2. Geordnetes Einfügen neuer Elemente in die Liste gemäß ihrer Priorität. Nachteil der ersten Variante Die Entnahme (dequeue) des Elements mit der höchsten Priorität läuft in O(n), da dieses Element erst gesucht werden muss. Nachteil von Variante 2 Das Einfügen (enqueue) eines Elements gemäß seiner Priorität läuft in O(n), da die Einfügeposition nur durch sequentielles Durchlaufen der Liste bestimmt werden kann. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 471 Optimierung Konsequenz Beide Varianten haben totale Operationskosten von O(n) Verbesserungsvorschläge Es gibt einige Verbesserungsvorschläge dieser naiven Listenimplementierung. So ergänzt z.B. Hendriksen (1977) die herkömmliche Listenimplementierung um ein Feld von Zeigern (Prinzip Indexierung), um schneller den Elementbereich in der Liste zu bestimmen, in den ein neues Element einzufügen ist. Ergebnis Die Operationskosten der verbesserten Listenimplementierungen sinken im Schnitt auf O(√n). Frage Lässt sich das Laufzeitverhalten noch weiter verbessern? Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 472 Heaps Definition Ein Heap ist eine besondere Form eines binären Baums und es gilt, dass • der Wert eines Knotens in einer Ordnungsrelation zu den Werten seiner Kinder steht • er perfekt balanciert ist und die Blätter der letzten Ebene linksbündig vollständig sind Ordnungsrelation In Abhängigkeit der Beziehung zwischen einem Knoten und seinen Kindern unterscheiden wir zwei Heapvarianten: • MinHeap - Der Wert eines Knotens ist kleiner oder gleich den Werten seiner Kinder. • MaxHeap - Der Wert eines Knotens ist größer oder gleich den Werten seiner Kinder. Achtung Es wird keine Aussage über eine direkte Beziehung zwischen den Kindknoten getroffen! Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 473 Implementierung von Heaps Konsequenz der Heap-Definition Alle Blätter befinden sich auf den letzten beiden Ebenen und bis auf die letzte Ebene sind alle Ebenen vollständig gefüllt, d.h. jede innere Ebene enthält doppelt so viele Elemente wie die Vorgängerebene. Des Weiteren befinden sich keine „Lücken“ zwischen den Blättern der letzten Ebene. Auf diesem Grund können Heaps mit Hilfe von Feldern dargestellt werden. Darstellung als Feld Die Eltern-Kind-Beziehung eines MaxHeaps in einem Feld maxHeap der Länge N kann wie folgt definiert werden: maxHeap[i] ≥ maxHeap[2*i+1] für 0 ≤ i < (N–1) / 2 (Relation zu linkem Kind) maxHeap[i] ≥ maxHeap[2*i+2] für 0 ≤ i < (N–2) / 2 (Relation zu rechtem Kind) Transformation in Felddarstellung Die Level-Order-Traversierung eines als Binärbaum realisierten Heaps liefert die korrekte Reihenfolge der Feldelemente. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 474 Operationen auf Heaps Anwendungsbereiche Heaps sind prädestiniert für Anwendungsgebiete, bei denen schnell entweder das kleinste (MinHeap) oder das größte Element (MaxHeap) einer Datenmenge bestimmt werden muss. Heaps sind aufgrund der fehlenden Relation zwischen Kindknoten nicht geeignet für die Suche nach einem beliebigen Element. Konsequenz für Operationen Wir beschränken uns aufgrund der Anwendungsbereiche neben dem Lesen des Wurzelelements auf das Einfügen eines neuen Elements in einen Heap (heapEnqueue) und das Entfernen des Wurzelelements (heapDequeue). Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 475 heapEnqueue 1. Einfügen eines Elements Um ein Element el einem Heap hinzuzufügen, fügen wir el (am Ende des Feldes) als letztes Blatt hinzu. 2. Rekonstruktion der Heap-Eigenschaft Nach dem Einfügen von el ist in der Regel die Heap-Eigenschaft verletzt. Diese muss wiederhergestellt werden, indem el gemäß der Ordnungsrelation solange mit seinem jeweiligen Vaterknoten vertauscht wird, bis sich el an der richtigen Position im Heap befindet. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 476 heapDequeue Prinzip Wir entfernen das Wurzelelement aus dem Heap und ersetzen es durch den letzten Blattknoten. Auch hier muss die Heap-Eigenschaft wiederhergestellt werden. Dies geschieht, indem der neue Wurzelknoten solange abwärts in Richtung Blattebene verschoben wird, bis sich der Knoten gemäß der Ordnungsrelation an der richtigen Position im Heap befindet. Allerdings stehen jetzt zwei Kindknoten zur Auswahl: Auswahlstrategie bei MinHeap Ist der abwärts zu verschiebende Knoten größer als beide Kindknoten, dann vertausche ihn mit dem kleineren Kind (das danach als Elternknoten des größeren Kindes fungiert). Auswahlstrategie bei MaxHeap Ist der abwärts zu verschiebende Knoten kleiner als beide Kindknoten, dann vertausche ihn mit dem größeren Kind (das danach als Elternknoten des kleineren Kindes fungiert). Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 477 Abwärtsbewegung bei MaxHeap void moveDown(Elementtyp h[], int parent, int N) { // parent = (Teilbaum)-Wurzelindex, N = Elementanzahl des Feldes h int largest = 2 * parent + 1; // Kindknoten initialisieren while (largest < N ) { if ( largest < N-1 && h[largest] < h[largest+1] ) largest++; // zweites Kind ist größer als erster Kindknoten if ( h[parent] < h[largest] ) { // Heapeigenschaft verletzt? Elementtyp temp = h[parent]; // Knoten tauschen h[parent] = h[largest]; h[largest] = temp; parent = largest; largest = 2 * parent + 1; } else largest = N; // Schleifenabbruch wenn Heap korrekt } } [nach Drozdek] Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 478 Anordnung eines Feldes als Heap (1) Verfahren nach Williams (Top-Down) 1. Wir schaffen Platz für ein neues Feld mit genauso viel Elementen wie das zu konvertierende Feld. Dieses neue Feld repräsentiert zunächst einen leeren Heap. 2. Wir fügen nacheinander alle Elemente des Ausgangsfeldes mittels heapEnqueue in den Heap (das neue Feld) ein Komplexität Die Laufzeit des Algorithmus für n Elemente beträgt im Worst Case O(n log n). Allerdings haben Untersuchungen gezeigt (Hayward und McDiarmid 1991), dass der Algorithmus im Average Case in Linearzeit, also in O(n) läuft. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 479 Anordnung eines Feldes als Heap (2) Verfahren nach Floyd (Bottom-Up) Das Grundprinzip besteht in der Bildung kleiner Heaps, die nach und nach zu größeren Heaps verschmolzen werden. Ausgangspunkt Für alle Blätter ist die Heapeigenschaft erfüllt. Algorithmus: Beginnend mit dem letzten Nichtblattknoten wird jeder Nichtblattknoten, der die Heapeigenschaft verletzt, solange in Richtung der Blattknoten verschoben bis die Heapeigenschaft wieder erfüllt ist. Komplexität Die Komplexität dieses Algorithmus beträgt grundsätzlich, d.h. selbst im worst case O(n). Im Average Case sind jedoch Top-Down- und Bottom-Up-Verfahren vergleichbar. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 480 Verwendung von Heaps (1) Heaps als Prioritätswarteschlangen Mit Heaps lassen sich Prioritätswarteschlangen effizient realisieren, da sich das Element mit dem höchsten Wert (der höchsten Priorität) automatisch in der Wurzel eines MaxHeaps befinden würde. Komplexität Da Heaps perfekt balanciert sind, lässt sich ein Blatt von der Wurzel aus in O(log n) Schritten erreichen. Entnehmen der Wurzel aus Heap (= dequeue der Prioritätswarteschlange) benötigt maximal O(log n) Schritte um die Heapeigenschaft zu rekonstruieren. Einfügen eines Elements in den Heap (= enqueue der Prioritätswarteschlange) benötigt maximal O(log n) Schritte um die Heapeigenschaft zu rekonstruieren. Konsequenz Die zwei wesentlichen Operationen der Prioritätswarteschlange laufen in O(log n) und sind damit wesentlich effizienter als die Listenimplementierung mit O(n) und auch effizienter als die optimierten Varianten mit O(√n). Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 481 Verwendung von Heaps (2) Sortieren Wir entnehmen via heapDequeue solange das Minimum bzw. Maximum aus einem Heap und fügen dieses (ggf. umgekehrt) sequentiell in ein Feld ein, bis der Heap leer ist. Heapsort für MaxHeap 1. Generiere einen Heap aus einem Feld h mit N Elementen 2. for (int i = N-1; i >= 1; i--) { // Tausche das Maximum des Heaps mit h[i] // Stelle Heapeigenschaft für den Heap mit i Elementen (Feld h von 0 bis i–1) wieder her } Komplexität • Generieren des Heaps aus Array ➔ O(n), n-maliger Tausch des Maximums O(n) • Wiederherstellung der Heapeigenschaft ➔ O(n log n) • Gesamtkomplexität O(n log n) Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 482 Heapsort void heapSort(Elementtyp h[], int N) {// h = Feld mit N Elementen for (int i = N/2-1; i >= 0; i--) // Erzeugen des Heaps moveDown(h, i, N); for (int i = N-1; i >= 1; i--) { Elementtyp temp = h[i]; // Maximum aus Heap mit letztem h[i] = h[0]; // Feldelement tauschen h[0] = temp; moveDown(h, 0, i); } // Heapwiederherstellung für // nächstkleineren Heap } Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 483 Zusammenfassung Heaps sind geeignet • für das schnelle Auffinden in O(1) des Minimums (MinHeap) oder Maximums (MaxHeap) • für das schnelle Entnehmen der Wurzel und das schnelle Einfügen eines neuen Knotens in den Heap, da beide Operationen in O(log n) laufen Heaps sind nicht geeignet • für das Auffinden beliebiger Elemente im Baum, da Komplexität O(n) Anwendung Heaps sind ideal für eine effiziente Realisierung von Prioritätswarteschlangen (z.B. für DijkstraAlgorithmus) und u.a. als Hilfsmittel für Greedy-Algorithmen und das Heapsort-Verfahren. Besonderheit Heaps können mit Feldern realisiert werden ➔ Aber potenzielles Problem mit starrer Feldgröße bei dynamischen Operationen wie Einfügen und Löschen. Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 484 Ende der Vorlesung Dr. Frank Seifert Vorlesung Datenstrukturen - Sommersemester 2016 Folie 485