© U.-P. Schroeder, Uni Paderborn 13 Rekonfigurierende binäre Suchbäume Binärbäume, die zufällig erzeugt wurden, weisen für die wesentlichen Operationen Suchen, Einfügen und Löschen einen logarithmischen Aufwand auf. Damit kann man sich zufrieden geben. Muß man jedoch mit der Möglichkeit rechnen, daß der Baum in nicht zufälliger, sondern z.B. in sortierter Reihenfolge gefüllt wird, so droht eine Degeneration zur Liste und damit zu linearer Komplexität für die Hauptoperationen. Man kann Degeneration vermeiden, wenn man die Degeneration durch explizite Umordnungen im Baum verhindert, d.h. die Teilbäume möglichst ausgewogen hält (Lastausgleich) Diese Umordnungen sollen effizient sein, d.h. man möchte mit sowenig Operationen wie nötig soviel Ausgleich wie möglich erzielen. Für diese Rekonfiguration von Binärbäumen sind die sogenannten Rotationen als Elementaroperationen vorgesehen 13-1 © U.-P. Schroeder, Uni Paderborn 13.1 Rotationen Rotationen können an beliebigen Knoten eines binären Suchbaumes stattfinden, sofern die betroffenen Teilbäume existieren a b a b A a C b B C A B Einfachrotation linksherum (a) und rechtsherum (b): {A} a {B} b {C} • Eine Linksrotation am Knoten a (Bezugsknoten) erfordert die Existenz eines rechten Nachfolgers (b) • Eine Rechtsrotation am Knoten b (Bezugsknoten) erfordert die Existenz eines linken Nachfolgers (a) 13-2 © U.-P. Schroeder, Uni Paderborn Realisierung einer einfachen Linksrotation durch Änderung der Verzeigerung a b void s_rotate_left(element* &a) { // performs a single left rotation with regard to node a element * b = a->right; a->right = b->left; b->left = a; a = b; } void s_rotate_right(element* &a) { // performs a single right rotation with regard to node a element * b = a->left; a->left = b->right; b->right = a; a = b; } 13-3 © U.-P. Schroeder, Uni Paderborn Doppelrotation Die Doppelrotation besteht aus zwei Einfachrotationen: Beispiel: Doppelrotation links (im Gegenuhrzeigersinn) Bezugsknoten Bezugsknoten a ⇒ c A b B b a D C ⇒ b A c B C a A c B D 1. Schritt 2. Schritt Einfachrotation rechts am rechten Nachfolger Einfachrotation links am Bezugsknoten 13-4 C D © U.-P. Schroeder, Uni Paderborn Doppelrotation linksherum: {A} a {B} b {C} c {D} Bezugsknoten a b ⇒ c A b B D a A c B C In C++: void d_rotate_left(element* &a) { // performs a double rotation anticlockwise s_rotate_right(a->right); s_rotate_left(a); } 13-5 C D © U.-P. Schroeder, Uni Paderborn Doppelrotation rechtsherum {A} a {B} b {C} c {D} c Bezugsknoten a b D b A B ⇒ a A C In C++: void d_rotate_right(element* &a) { //performs a double rotation clockwise s_rotate_left(a->left); s_rotate_right(a); } 13-6 c B C D © U.-P. Schroeder, Uni Paderborn Mit diesen vier Spielarten der Rotation lassen sich Unsymmetrien der Last aus der Sicht des Bezugsknotens ausgleichen. Ja nach Lage des überlasteten Teilbaums kann eine der vier Varianten eingesetzt werden. Bezugsknoten Überlasteter Teilbaum Einfache Doppelte Doppelte Rechtsrotation Einfache Linksrotation 13-7 © U.-P. Schroeder, Uni Paderborn Linksrotation Anwendungsfall: Der rechte Teilbaum ist zu groß. Einfachrotation: Innerhalb des rechten Teilbaums ist der rechte (äußere) Teilbaum zu groß a b b A B a C A 13-8 C B © U.-P. Schroeder, Uni Paderborn Linksrotation Doppelrotation: Innerhalb des rechten Teilbaums ist der linke (innere) Teilbaum zu groß Bezugsknoten a b ⇒ c A b B D a A C 13-9 c B C D © U.-P. Schroeder, Uni Paderborn Rechtsrotation Anwendungsfall: Der linke Teilbaum ist zu groß. Einfachrotation: Innerhalb des linken Teilbaums ist der linke (äußere) Teilbaum zu groß b a A a C b A B B 13-10 C © U.-P. Schroeder, Uni Paderborn Rechtsrotation Doppelrotation: Innerhalb des linken Teilbaums ist der rechte (innere) Teilbaum zu groß c Bezugsknoten a b D ⇒ b A B a A C 13-11 c B C D © U.-P. Schroeder, Uni Paderborn 13.2 AVL-Bäume Um die logarithmische Komplexität der Operationen zu erhalten, müssen Gleichgewichtsbedingungen nicht nur für den Wurzelknoten, sondern für alle Knoten gelten. Gleichgewicht nur bezüglich der Wurzel: AVL-Bäume sind binäre Suchbäume, die eine gewisse Gleichgewichtsbedingung zwischen Teilbäumen einhalten. Sie sind benannt nach ihren beiden Erfindern Adelson-Velskii und Landis (1962) Definition Ein AVL-Baum ist ein binärer Suchbaum, in dem für jeden Knoten gilt: Die Höhen h des linken und des rechten Teilbaums unterscheiden sich um höchstens 1. Formal: ∆h ≤ 1 mit ∆ h : = hL − hR 13-12 © U.-P. Schroeder, Uni Paderborn Minimaler AVL-Baum der Höhe 7 13-13 © U.-P. Schroeder, Uni Paderborn Prinzip des AVL-Baums Um die Gleichgewichtsbedingung einzuhalten und leicht überprüfen zu können, ist es sinnvoll, in den Knoten zusätzlich die Höhe des jeweiligen Teilbaums speichern struct element { int height; value data; element *left; element *right; }; //definition of AVL node element //height of subtree defined by node //data value //left successor (or child) //right successor (or child) Nach dem Einfügen oder Löschen von Elementen werden die Höhen aktualisiert und alle betroffenen Knoten entlang des Pfades von der Wurzel bis zur Einfüge- oder Löschstelle bezüglich der Gleichgewichtsbedingung überprüft. Bei Verletzung der Bedingung wird durch Rotation das Gleichgewicht wiederhergestellt. 13-14 © U.-P. Schroeder, Uni Paderborn Hilfsfunktionen für AVL-Baum-Implementierung int Max(int x, int y) { // returns max of x and y if (x<y) return y; else return x; } int node_ht(element *node) { // returns height of node even if node is NULL if (node == NULL) return -1; else return node->height; } void calc_height(element *node) {//updates height of node assuming correct height of successors node->height=1+Max(node_ht(node->left),node_ht(node->right)); } element* get_min(element * node) { // returns pointer to minimum of subtree while (node->left != NULL) node=node->left; return node; } 13-15 © U.-P. Schroeder, Uni Paderborn Rotationsfunktionen void s_rotate_right(element* &a) { // performs single right rotation and updates heights element * b = a->left; a->left = b->right; b->right = a; a = b; calc_height(a->right); calc_height(a); } void s_rotate_left(element* &a) { // performs single left rotation and updates heights element * b = a->right; a->right = b->left; b->left = a; a = b; calc_height(a->left); calc_height(a); } void d_rotate_left(element* &a) { s_rotate_right(a->right); s_rotate_left(a); } void d_rotate_right(element* &a) { s_rotate_left(a->left); s_rotate_right(a); } 13-16 © U.-P. Schroeder, Uni Paderborn Einfügen Wir nehmen an, die Gleichgewichtsbedingung sei vor dem Einfügen an allen Knoten erfüllt. Das Einfügen eines Knotens in einem Teilbaum läßt seine Höhe unverändert oder erhöht sie um +1. 60 20 h=3 60 h=1 h=2 h=0 80 h=1 10 40 h=0 30 h=0 70 h=0 90 20 h=2 h=0 10 h=1 40 h=0 30 50 Höhe ändert sich nicht h=3 h=0 h=1 70 75 Höhe ändert sich 13-17 80 h=1 h=2 h=0 90 © U.-P. Schroeder, Uni Paderborn Höhenänderung beim Einfügen: Fallunterscheidung Falls eine Höhenänderung eintritt, können entlang des Einfügepfades am jeweils betrachteten Knoten folgende Fälle unterschieden werden: a) Die Teilbäume des Knotens waren gleich hoch Der neue Knoten ändert die Höhe eines der beiden Teilbäume um +1. Die Gleichgewichtsbedingung wird eingehalten b) Die Teilbäume des Knotens waren ungleich hoch Der neue Knoten wird im kleineren Teilbaum eingefügt und ändert dessen Höhe um +1. Die beiden Teilbäume haben jetzt gleiche Höhe. Die Gleichgewichtsbedingung wird eingehalten c) Die Teilbäume des Knotens waren ungleich hoch Der neue Knoten wird im größeren Teilbaum eingefügt und ändert dessen Höhe um +1. Die Höhen der beiden Teilbäume unterscheiden sich jetzt um 2. Die Gleichgewichtsbedingung ist verletzt und muß durch Rotation wiederhergestellt werden 13-18 © U.-P. Schroeder, Uni Paderborn Nur im Fall c) gibt es also etwas zu tun. Betrachten wir also wieder die möglichen Fälle. (Das Ganze gilt analog für die spiegelbildliche Situation) Gegebene Situation (Gleichgewichtsverletzung in a, rechter Teilbaum zu hoch: hb = hA + 2): a B Fall I: ? b A C C ist schuld : hb = hC + 1 Linksrotation einfach Situation: Lösung: b a a b A B A C 13-19 C B © U.-P. Schroeder, Uni Paderborn Fall II: B ist schuld : hb = hB + 1 Linksrotation doppelt Situation: Lösung: b‘ a a b A b‘ BL C A BR B 13-20 b BL BR C © U.-P. Schroeder, Uni Paderborn Wirkung der Einfachrotation auf die Höhen Nach Einfügen: h=x+3 Nach Rotation: h=x+2 a b h=x+2 b x+1 A C x x+1 ⇒ a x+1 A B x x B C x Stelle der Lasterhöhung Wirkung der Doppelrotation auf die Höhe Nach Einfügen: h=x+3 Nach Rotation: h=x+2 a c h=x+2 b D x x+1 ⇒ x+1 x c A x x-1 B x C x+1 b x-1 x Alternativ mögliche Lasterhöhung 13-21 A x x-1 B a C x-1 x D x © U.-P. Schroeder, Uni Paderborn Einfügen void check_rot_left(element* &node) { if (node==NULL) return; // empty subtree else if (node->right!=NULL) // left rotation possible if (node_ht(node->right)-node_ht(node->left)==2) //rotate if (node_ht(node->right->left)>node_ht(node->right->right)) d_rotate_left(node); // double rotation else s_rotate_left(node); // single rotation else calc_height(node); // update node height else calc_height(node); // update node height } void check_rot_right(element* &node) { if (node==NULL) return; // empty subtree else if (node->left!=NULL) // right rotation possible if (node_ht(node->left) - node_ht(node->right)==2) if (node_ht(node->left->right)>node_ht(node->left->left)) d_rotate_right(node); // double rotation else s_rotate_right(node); // single rotation else calc_height(node); // update node height else calc_height(node); // update node height } 13-22 © U.-P. Schroeder, Uni Paderborn void ins(element* &p, value v) { if (p == NULL) // insert position found: create new node { p = new element; p ->height = 0; p ->left = NULL; p ->data = v; p ->right = NULL; return; } if (v < p->data) // branch to left subtree { ins(p->left, v); check_rot_right(p); } if (v > p->data) // branch to right subtree { ins(p->right, v); check_rot_left(p); } // else element is already in the tree: nothing is being done } 13-23 © U.-P. Schroeder, Uni Paderborn Beispiel Die Eigenschaft des AVL-Baums läßt sich gut an Hand eines sortierten Einfügens erkennen, das bei einem normalen (freien) Binärbaum eine Degeneration zur Folge hätte. Beispiel: Einfügen der Werte 10, 20, ... , 70 in einen am Anfang leeren AVL-Baum 10 20 10 20 20 10 10 30 30 40 20 40 10 20 40 30 50 10 40 50 30 20 60 13-24 10 60 30 50 70 © U.-P. Schroeder, Uni Paderborn Entfernen Das Entfernen verläuft ähnlich wie beim freien Binärbaum (Kap. 12). Wir verwenden jedoch eine rekursive Formulierung. Nachdem der zu entfernende Knoten gefunden wurde, werden je nach Existenz von Nachfolgern vier Fälle unterschieden: • Hat der Knoten keinen Nachfolger, kann er direkt gelöscht werden. • Hat er nur einen linken Nachfolger, so wird sein linker Nachfolger an seinen Vorgänger gehängt (Überbrückung). Dann kann er gelöscht werden. • Hat er nur einen rechten Nachfolger, so wird sein rechter Nachfolger an seinen Vorgänger gehängt (Überbrückung). Dann kann er gelöscht werden. • Hat er zwei Nachfolger, so wird er durch den linkesten Knoten in seinem rechten Teilbaum (sein Nachfolger in In-Ordnung) ersetzt (Werttransfer). Dieser Knoten wird dann durch einen weiteren (rekursiven) Aufruf von remove gelöscht. Bei jeder (rekursiven) Rückkehr aus remove muß eine Rotationsprüfung durchgeführt werden, da eine Höhenänderung stattgefunden haben kann. Fallunterscheidung wie beim Einfügen. 13-25 © U.-P. Schroeder, Uni Paderborn void rem(element* &node, value v) { element *p; if (node == NULL) return; // (sub)tree empty: not found else if (v < node->data) rem(node->left, v); // go to left subtree else if (v > node->data) rem(node->right, v); // go to right subtree else // element found { if (node->left != NULL && node->right != NULL) { // two children p=get_min(node->right); // min of right subtree node->data = p->data; // value transfer rem(node->right,node->data);//remove min of right subtree check_rot_right(node); } else { p=node; if (node->left==NULL && node->right==NULL) { delete p; node = NULL; } else { if (node->left==NULL) // only right child { node=node->right; check_rot_right(node);} else // only left child if (node->right==NULL) { node=node->left; check_rot_left(node);} delete p; calc_height(node); } } } } 13-26 © U.-P. Schroeder, Uni Paderborn Beispiel: Aus dem folgenden AVL-Baum (a) werden die Elemente 10, 15, 20, 25, 30, 35, 40, 45, 50 sukzessive entfernt. 35 35 15 45 25 10 40 45 30 15 50 30 20 40 55 (a) (b) 35 35 25 45 30 40 (c) 50 20 55 25 20 15 25 45 30 50 55 40 (d) 13-27 50 55 © U.-P. Schroeder, Uni Paderborn 45 50 35 30 45 40 50 35 40 55 55 (e) (f) 45 50 40 45 50 (h) 55 (g) 50 (i) 55 55 55 (j) 13-28 © U.-P. Schroeder, Uni Paderborn Komplexität der Operationen im AVL-Baum Von den Rotationen abgesehen, verlaufen die Operationen • • • Suchen Einfügen Entfernen wie bei den freien Binärbäumen, d.h. es muß ein Pfad von der Wurzel bis maximal zu einem Blatt abgelaufen werden. Da der Aufwand für eine Rotation oder Doppelrotation konstant ist (O(1)), d.h. nicht von der Größe des Baumes abhängt, gilt - wie sonst auch in Bäumen - daß der Aufwand der drei Operationen linear mit der Höhe des Baumes wächst. Es bleibt also die Frage, wie die Höhe eines AVL-Baumes mit seiner Knotenzahl zusammenhängt. 13-29 © U.-P. Schroeder, Uni Paderborn Höhe von AVL-Bäumen Um eine Abschätzung zu erhalten, betrachten wir den ungünstigsten Fall, d.h. AVL-Bäume mit einer für die gegebene Knotenzahl maximalen Höhe, bzw. AVL-Bäume mit einer für eine gegebene Höhe minimalen Knotenzahl: Diese Bäume genügen offensichtlich einem gewissen Bildungsgesetz 13-30 © U.-P. Schroeder, Uni Paderborn Höhe von AVL-Bäumen F0 F1 Fh ... Fh-2 Fh-1 Der Baum Fh der Höhe h setzt sich also zusammen aus einem Baum der Höhe h-1 und einem Baum der Höhe h-2. Für die Entwicklung der Knotenzahlen nh = n( Fh ) gilt also: nh = 1 + nh−1 + nh−2 mit n0 = 1 und n1 = 2 d.h. die Knotenzahlen der maximal asymmetrischen AVL-Bäume entsprechen (fast) den FibonacciZahlen. Die Bäume heißen daher auch Fibonacci-Bäume. Ein AVL-Baum der Höhe h hat daher mindestens fh+2 -1 Knoten, wobei fk die k-te Fibonacci-Zahl meint. 13-31 © U.-P. Schroeder, Uni Paderborn Höhe von AVL-Bäumen Für einen allgemeinen AVL-Baum der Höhe h gilt demnach: nh ≥ fh+2 −1 Für Fibonacci-Zahlen gilt die Abschätzung des Goldenen Schnitts: k 1 1 + 5 fk > −1 5 2 1 1 + 5 und daher nh ≥ fh+2 −1 > 5 2 bzw. h < h+2 −2 1 log2 [ 5 (nh + 2)] − 2 1 + 5 log2 2 Im O-Kalkül also: h = O(log n) Die Grundoperationen in AVL-Bäumen besitzen also höchstens logarithmischen Aufwand. 13-32