Algorithmen und Datenstrukturen Fachhochschule Regensburg Algorithmen und Datenstrukturen AVL-Bäume 1 Übung 43_2 Algorithmen und Datenstrukturen Name: ________________________ Lehrbeauftragter: Prof. Sauer Vorname: _____________________ Abb. 1: Darstellung eines AVL-Baums mit Balancierungsangeben (Höhendifferenzen) 2 1. Einfügen von Knoten im AVL-Baum 3 Ein binärer Suchbaum hat die AVL-Eigenschaft, wenn sich in jedem Knoten sich die Höhen der beiden Teilbäume höchstens um 1 unterscheiden. Diese Last („Balance“) muß in einem Knoten gespeichert sein. Es genügt aber als Maß für die Unsymmetrie eine Höhendifferenz festzuhalten, die nur die Werte –1 (linkslastig), 0 (gleichlastig) und +1 (rechtslastig) annehmen kann. Die AVL-Eigenschaft ist verletzt, wenn diese Höhendifferenz +2 bzw. –2 ist. Der Knoten, der diesen Wert erhalten hat, ist der Knoten „alpha“, dessen Unausgeglichenheit auf einen der folgenden 4 Fälle zurückzuführen ist: 1. Einfügen in den linken Teilbaum, der vom linken Nachkommen des Knoten „alpha“ bestimmt ist. 2. Einfügen in den rechten Teilbaum, der vom linken Nachkommen des Knoten „alpha“ bestimmt ist. 3. Einfügen in den linken Teilbaum, der vom rechten Nachkommen des Knoten „alpha“ bestimmt ist. 4. Einfügen in den rechten Teilbaum, der vom rechten Nachkommen des Knoten „alpha“ bestimmt ist Fall 1 und Fall 4 bzw. Fall 2 und Fall 3 sind Spiegelbilder, zeigen also das gleiche Verhalten. Fall1 kann durch einfache Rotation behandelt werden und ist leicht zu bestimmen, daß das Einfügen „außerhalb“ (links – links bzw. rechts – rechts im Fall 4) stattfindet. Fall 2 kann durch doppelte Rotation behandelt werden und ist ebenfalls leicht zu bestimmen, da das Einfügen „innerhalb“ (links –rechts bzw. rechts – links) erfolgt. Die einfache Rotation Die folgende Darstellung beschreibt den Fall 1 vor und nach der Rotation: 1 Vgl. Skriptum 4.3.2, http://fbim.fh-regensburg.de/~saj39122/bruhi/index.html vgl. AVLTreeTest.java, AVLTree.java, 3 vgl. Skriptum, 4.3.2 2 1 Algorithmen und Datenstrukturen k2 k1 k1 k2 Z Y X Y Z X Die folgende Darstellung beschreibt Fall 4 vor und nach der Rotation : k1 k2 k2 k1 X Y X Y Z Z Doppelrotation Die einfache Rotation führt in den Fällen 2 und 3 nicht zum Erfolg. Fall 2 muß durch eine Doppelrotation (links – rechts) behandelt werden. k3 k2 k1 k1 k3 D k2 B A A B C 2 C D Algorithmen und Datenstrukturen Auch Fall 3 muß durch Doppelrotation behandelt werden k1 k3 k2 k1 k2 A k3 D B C A B D C Implementierung 4 Zum Einfügen eines Knoten mit dem Datenwert „x“ in einen AVL-Baum, wird „x“ rekursiv in den betoffenen Teilbaum eingesetzt. Falls die Höhe dieses Teilbaums sich nicht verändert, ist das Einfügen beendet. Liegt Unausgeglichenheit vor, dann ist einfache oder doppelte Rotation (abhängig von „x“ und den Daten des betroffenen Teilbaums) nötig. AVL-Baumknoten Er enthält für jeden Knoten eine Angabe zur Höhe(ndifferenz) 5 seiner Teilbäume. // Baumknoten fuer AVL-Baeume struct AvlNode { Comparable element; AvlNode *left; AvlNode *right; int height; // Konstruktor AvlNode( const Comparable & theElement, AvlNode *lt, AvlNode *rt, int h = 0 ) : element( theElement ), left( lt ), right( rt ), height( h ) { } }; Der AVL-Baum Bei jedem Schritt ist festzustellen, ob die Höhe des Teilbaums, in dem ein Element eingefügt wurde, zugenommen hat. /* Rückgabe der Hoehe des Knotens t oder -1, wenn NULL */ int height( AvlNode *t ) const { return t == NULL ? -1 : t->height; } Die Methode „insert“ führt das Einfügen eines Baumknoten in den Avl-Baum aus: /* * Interne Methode zum Einfuegen eines Baumknoten in einen Teilbaum. * x ist das einzufuegende Datenelement. * t ist der jeweilige Wurzelknoten. */ void insert( const Comparable& x, AvlNode * & t ) { 4 5 pr43_2 vgl. Skriptum, 4.3.2 3 Algorithmen und Datenstrukturen if( t == NULL ) t = new AvlNode( x, NULL, NULL ); else if( x < t->element ) { insert( x, t->left ); if( height( t->left ) - height( t->right ) == 2 ) if( x < t->left->element ) rotateWithLeftChild( t ); else doubleWithLeftChild( t ); } else if( t->element < x ) { insert( x, t->right ); if( height( t->right ) - height( t->left ) == 2 ) if( t->right->element < x ) rotateWithRightChild( t ); else doubleWithRightChild( t ); } else ; // Duplikat, ueber gehen t->height = max( height( t->left ), height( t->right ) ) + 1; } Einfache Rotation RotationMitLinksNachf dreht den Linken Teilbaum der folgenden Darstellung nach rechts. k2 k1 k1 k2 Z Y X Y Z X /* * Rotation Binaerbaumknoten mit linkem Nachfolger. * Fuer AVL-Baeume ist dies eine einfache Rotation (Fall 1). * Aktualisiert Angaben zur Hoehe, dann Rueckgabe der neuen Wurzel. */ void rotateWithLeftChild( AvlNode * & k2 ) { AvlNode *k1 = k2->left; k2->left = k1->right; k1->right = k2; k2->height = max( height( k2->left ), height( k2->right ) ) + 1; k1->height = max( height( k1->left ), k2->height ) + 1; k2 = k1; } /* * Rotation Binaerbaumknoten mit rechtem Nachfolger. * Fuer AVL-Baeume ist dies eine einfache Rotation (Fall 4). * Aktualisiert Angaben zur Hoehe,, danach Rueckgabe der neuen Wurzel. */ 4 Algorithmen und Datenstrukturen void rotateWithRightChild( AvlNode * & k1 ) { AvlNode *k2 = k1->right; k1->right = k2->left; k2->left = k1; k1->height = max( height( k1->left ), height( k1->right ) ) + 1; k2->height = max( height( k2->right ), k1->height ) + 1; k1 = k2; } Doppelrotation “doppelrotationMitLinksNachf” führt die folgende Umstellung der Baumknoten aus: k3 k2 k1 k1 k3 D k2 B A C A B C /* * Doppelrotation der Binaerbaumknoten: : erster linker Nachfolgeknoten * mit seinem rechten Nachfolger; danach Knoten k3 mit neuem linken * Nachfolgerknoten. * Fuer AVL-Baeume ist dies eine doppelte Rotation (Fall 2) * Aktualisiert Angaben zur Hoehe,, danach Rueckgabe der neuen Wurzel. */ void doubleWithLeftChild( AvlNode * & k3 ) { rotateWithRightChild( k3->left ); rotateWithLeftChild( k3 ); } /* * Doppelrotation der Binaerbaumknoten: : erster linker Nachfolgeknoten * mit seinem rechten Nachfolger; danach Knoten k3 mit neuem linken * Nachfolgerknoten. * Fuer AVL-Baeume ist dies eine doppelte Rotation (Fall 2) * Aktualisiert Angaben zur Hoehe,, danach Rueckgabe der neuen Wurzel. */ void doubleWithRightChild( AvlNode * & k1 ) { rotateWithLeftChild( k1->right ); rotateWithRightChild( k1 ); } 5 D Algorithmen und Datenstrukturen 2. Löschen von Knoten im AVL-Baum Das Löschen eines Knotens (Schlüssel x) mit 2 Kindern kann auf das Löschen von Blatt-/Randknoten zurückgeführt werden. Für innere Knoten gilt: - „x“ wird ersetzt durch den kleinsten Schlüssel y im rechten Teilbaum von x (bzw. größten Schlüssel im linken Teilbaum). Führt zur Änderung des Balancierungsfaktors für v (Höhe des linken Teilbaums von v hat sich um 1 reduziert). x y ⇒ v v Bl Bl z y B ry B ry Br Br z Bis auf Symmetrie treten nur 3 Fälle auf. Fall 1: 0 v h+1 1 h+1 Löschen in Bl ⇒ h h h-1 Bl Bl h Br Br In diesem Fall pflanzt sich Höhenerniedrigung nicht fort, da in der Wurzel das AVL-Kriterium erfüllt bleibt, d.h. kein Rebalancieren erforderlich. Fall 2: -1 h+1 0 h Löschen in Bl ⇒ h h-1 Bl h-1 Br Bl Br h-1 Die Höhenerniedrigung von Bl pflanzt sich hier zur Wurzel fort. Sie kann auf diesem Pfad eine Rebalancierung auslösen. 6 Algorithmen und Datenstrukturen Fall 3: -1 h+1 -2 h+1 Löschen in Br ⇒ h h-1 h Br Bl h-2 Br Bl Für die Behandlung dieser Situation ist der linke Teilbaum Bl genauer zu betrachten. Dabei ergeben sich 3 Unterfälle: Fall 3a: -2 x4 1 x2 h+1 h+1 ⇒ 0 x2 -1 x4 B5 h-2 h-1 B1 h-1 h-1 h-1 B3 0 x2 h h-2 B5 h-2 B5 B3 B1 - Rechtsrotation führt zur Erfüllung des AVL-Kriteriums - Teilbaum behält ursprüngliche Höhe - Keine weiteren Rebalancierungen erforderlich Fall 3b: -2 x4 h+1 ⇒ -1 x2 0 x4 B5 h-2 h-1 B1 h-1 h-2 B1 h-2 B3 B3 - Rechtsrotation reduziert Höhe des gesamten Teilbaum von h+1 nach h - Höhenreduzierung pflanzt sich auf dem Pfad zur Wurzel hin fort und kann zu weiteren Rebalancierungen führen. 7 Algorithmen und Datenstrukturen -2 h+1 x6 +1 x2 0 x4 h h h-2 0 x2 B7 0 x4 0 x6 h-2 h-2 B1 B3 B5 B7 B1 h-2 h-2 B3 B5 - Doppelrotation - ggf. fortgesetzte Rebalancierungen Rebalancierungsschritte beim Löschen 1. Suche im Löschpfad nähesten Vater mit Balancierungsfaktor +/-2 2. Führe ggf. Rotation im gegenüberliegenden Teilbaum dieses Vaters auf. Im Gegensatz zum Einfügevorgang kann hier eine Rotation wiederum auf dem Pfad zur Wurzel auslösen, da sie in gewissen Fällen auf eine Höhenerniedrigung des transformierten Teilbaums führt. Die Anzahl der Rebalancierungsschritte ist jedoch durch die Höhe h des Baums begrenzt. 3. Höhe von AVL-Bäumen Balancierte Bäume werden als Kompromiß zwischen ausgeglichenen und natürlichen Suchbäumen eingeführt, wobei „logarithmischer Suchaufwand“ im schlechtesten Fall gefordert wurde. Für die h +1 − 1 . Daraus erhält man zur maximale Anzahl von Knoten n gilt folgende Ungleichung: n ≤ 2 Abschätzung der Höhe: h ≤ 2 ⋅ log 2 n + 1 . Das bedeutet, dass die Höhe eines AVL-Baums von der Wurzel bis zu einem beliebigen Knoten höchstens 2 ⋅ log 2 n + 1 ist 4. Komplexität von Opeartionen aujf AVL-Bäumen - Komplexität für Einfügen und Löschen in binären Suchbäumen: O (h) . -- im AVL- Baum gilt: h = O (log n) -- Notwendige Rotationen sind in konstanter Zeit auszuführen - Ausbalancieren nach Einfügen erfordert höchstens eine (einfache oder doppelte) Rotation. Komplexität von insert: O (log n) + O (1) = O (log n) - Ausbalancieren nach Löschen erfordert höchstens h (einfache und doppelte) Rotationen. Komplexität von remove: O (log n) + O (log n) = O (log n) - Einfügen und Löschen in AVL-Bäumen haben im schlechtesten Fall eine Komplexität von O(logn) - Zusammenfassung: worst-case Komplexität O (log n) für Suchen, Einfügen und Löschen. 8