Universität Bremen Bäume 2 Thomas Röfer Suchbäume Suchen, Einfügen, Löschen Balancierte Bäume (AVL-Bäume) B-Bäume Universität Bremen Rückblick „Bäume 1“ Begriffe Durchlaufen von Bäumen Spielprobleme Huffman-Baum 1 Eigene Züge Eigene Züge 9 0 1 5 0 0 1 2 0 1 2 01 2 0 3 1 1 1 1 1 1 B H PI-2: Bäume 2 4 1 Eigene Züge Gegnerische Züge 9 1 Gegnerische Züge 0 18 G F 2 E D C A Universität Bremen Suchbäume Motivation Ein Suchbaum dient zum schnellen Auffinden von Werten, möglichst in O(log n) Definition Die Schlüssel aller linken Nachfolger sind kleiner als der Schlüssel eines Knotens, die Schlüssel aller rechten sind größer (oder gleich) Ein natürlicher Baum wird durch zufälliges Einfügen von Schlüsseln erzeugt und ist nicht notwendigerweise balanciert 27 … 39 39 … ∞ Alternative Schreibweise ((( 1 ) 3 (( 14 ) 15 )) 27 ( 39 )) -∞ … 1 1…3 15 … 27 Sichtweise Die Blätter repräsentieren Intervalle im Wertebereich der Schlüssel PI-2: Bäume 2 3 … 14 14 … 15 3 Universität Bremen Normaler Suchbaum Ansatz Werte stehen in inneren Knoten Blätter sind leer Suchalgorithmus Fall 1: Knoten ist innerer Knoten Falls Schlüssel gleich gesuchtem Wert, dann gefunden Ansonsten abhängig von Vergleich zwischen Wert und Schlüssel links oder rechts weitersuchen Fall 2: Knoten ist Blatt nicht gefunden Wert Aufwand O(Höhe des Baumes) PI-2: Bäume 2 4 Universität Bremen Blattsuchbäume Ansatz Geordnet wie Suchbäume Werte stehen in Blättern Innere Knoten enthalten nur „Wegweiser“ z.B. größten Wert des linken Teilbaums Suchalgorithmus Fall 1: Knoten ist innerer Knoten Abhängig von Vergleich zwischen Wert und Wegweiser links oder rechts weitersuchen Fall 2: Knoten ist Blatt Wenn Wert gleich Schlüssel, dann gefunden Ansonsten nicht gefunden PI-2: Bäume 2 5 Universität Bremen Suchen in Suchbaum Wenn gesuchter Wert gleich dem Schlüssel gefunden kleiner als Schlüssel weitersuchen in linkem Nachfolger ansonsten weitersuchen in rechtem Nachfolger PI-2: Bäume 2 Comparable Comparable search(Comparable search(Comparable value) value) {{ return return search2(root, search2(root, value); value); }} private private Comparable Comparable search2( search2( Node node, Comparable Node node, Comparable value) value) {{ if(node if(node == == null) null) return null; return null; else else {{ int int cc == value.compareTo(node.data); value.compareTo(node.data); if(c if(c << 0) 0) return return search2(node.left, search2(node.left, value); value); else if(c > 0) else if(c > 0) return return search2(node.right, search2(node.right, value); value); else else return return node.data; node.data; }} }} 6 Universität Bremen Suchbaum mit Wächter/Stopper Ansatz Wie beim Suchen in Listen/Arrays mit Wächter/Stopper Test auf „nicht gefunden“ entfällt, da Wert garantiert gefunden wird Könnte in Java auch durch Ausnahmebehandlung ersetzt werden Implementierung In einem Suchbaum sind alle Blätter identisch (ist somit eigentlich kein Baum mehr) In dieses einzige Blatt wird der gesuchte Wert geschrieben PI-2: Bäume 2 7 Universität Bremen Einfügen in einen Suchbaum insert(17) PI-2: Bäume 2 8 Universität Bremen Einfügen in einen Suchbaum Algorithmus Suche nach dem einzufügenden Wert, wobei selbst dann weitergesucht wird, wenn er gefunden wurde Es sei denn, man will jeden Wert nur einmal im Suchbaum repräsentieren Dadurch wird auf jeden Fall ein externer Knoten gefunden Dieser wird durch ein neues Blatt mit dem einzufügenden Wert ersetzt PI-2: Bäume 2 void void insert(Comparable insert(Comparable value) value) {{ if(root if(root == == null) null) new root = root = new Node(value); Node(value); else else insert2(root, insert2(root, value); value); }} private private void void insert2(Node insert2(Node node, node, Comparable Comparable value) value) {{ if(value.compareTo(node.data) if(value.compareTo(node.data) << 0) 0) if(node.left == null) if(node.left == null) node.left node.left == new new Node(value); Node(value); else else insert2(node.left, insert2(node.left, value); value); else else if(node.right if(node.right == == null) null) node.right node.right == new new Node(value); Node(value); else else insert2(node.right, insert2(node.right, value); value); }} 9 Universität Bremen Löschen aus einem Suchbaum 8 4 2 1 5 1× n× 3 8 5 6 2 7 1 6 3 7 remove(4) PI-2: Bäume 2 10 Universität Bremen Löschen aus einem Suchbaum Suche den zu löschenden Knoten Merke unterwegs jeweils den Elternknoten Wenn er gefunden wurde Falls einer seiner Nachfolger leer ist, ersetze ihn in seinem Elternknoten durch den jeweils anderen Nachfolger Ansonsten suche den Knoten k mit dem nächst größeren Wert, überschreibe im zu löschenden Knoten den Wert mit dem Wert von k und lösche danach k PI-2: Bäume 2 void void remove(Comparable remove(Comparable value) value) {{ remove2(root, remove2(root, null, null, value); value); }} private private void void remove2(Node remove2(Node node, node, Node parent, Node parent, Comparable Comparable value) value) {{ if(node if(node != != null) null) {{ int int cc == value.compareTo(node.data); value.compareTo(node.data); if(c < 0) if(c < 0) remove2(node.left, remove2(node.left, node, node, value); value); else else if(c if(c >> 0) 0) remove2(node.right, remove2(node.right, node, node, value); value); else else removeNode(node, removeNode(node, parent); parent); }} }} 11 Universität Bremen Löschen aus einem Suchbaum private private void void removeNode(Node removeNode(Node node, node, Node parent) Node parent) {{ if(node.right if(node.right == == null) null) replaceChild(parent, replaceChild(parent, node, node, node.left); node.left); else else if(node.left if(node.left == == null) null) replaceChild(parent, replaceChild(parent, node, node, node.right); node.right); else else if(node.right.left if(node.right.left == == null) null) {{ node.right.left node.right.left == node.left; node.left; replaceChild(parent, replaceChild(parent, node, node, node.right); node.right); }} else else {{ Node Node pon pon == getParentOfNext(node); getParentOfNext(node); node.data node.data == pon.left.data; pon.left.data; pon.left = pon.left.right; pon.left = pon.left.right; }} }} PI-2: Bäume 2 private private void void replaceChild(Node replaceChild(Node node, node, Node oldNode, Node newNode) Node oldNode, Node newNode) {{ if(node if(node == == null) null) root = newNode; root = newNode; else else if(node.left if(node.left == == oldNode) oldNode) node.left = newNode; node.left = newNode; else else node.right node.right == newNode; newNode; }} private private Node Node getParentOfNext(Node getParentOfNext(Node node) node) {{ node node == node.right; node.right; while(node.left.left while(node.left.left != != null) null) node = node.left; node = node.left; return return node; node; }} 12 Universität Bremen Balancierte Bäume Motivation Durch degenerierte Bäume kann die Suchdauer stark ansteigen AVL-Baum (Adelson-Velskij und Landis) Definition Ein Baum ist AVL-ausgeglichen oder höhenbalanciert, wenn für jeden Knoten des Baumes gilt, dass sich die Höhe seines linken Teilbaums von der des rechten Teilbaums um maximal 1 unterscheidet Algorithmus-Idee Jedes Einfügen oder Löschen kann die Balanciertheit des Baumes zerstören. Daher wird der Baum jeweils rebalanciert, sobald die Höhen des linken und rechten Teilbaums um zwei auseinander liegen PI-2: Bäume 2 13 Universität Bremen Balanciertheit, Höhe, Rotieren Baum x Leerer Baum: Sonst: (t1, x, t2) t1 Balanciertheit t2 heightbal( ) = true heightbal((t1, x, t2)) = | height(t1) - height(t2) | ≤ 1 /\ heightbal(t1) /\ heightbal(t2) Höhe height( ) =0 height((t1, x, t2)) = max(height(t1), height(t2)) + 1 Baum rotieren rotr(((t1, y, t2), x, t3)) = (t1, y, (t2, x, t3)) rotl((t1, y, (t2, x, t3))) = ((t1, y, t2), x, t3) y t1 rotr x t3 t1 t2 PI-2: Bäume 2 x t2 rotl f((t1, x, t2)) = (t2, x, t1) Node f(Node n) { return new Node(n.right, n.data, n.left);} y 14 t3 Universität Bremen Einfügen in AVL-Baum Einfügen insert(x, ) = ( , x, ) insert(x, (t1, y, t2)) = rebal((insert(x, t1), y, t2)) falls x < y rebal((t1, y, insert(x, t2))) sonst Neigung eines Knotens slope( ) slope((t1, x, t2)) Kann im Baum gespeichert werden und braucht nicht immer wieder neu ausgerechnet zu werden Rebalancieren rebal(t) = PI-2: Bäume 2 shiftr(t) shiftl(t) t + =0 = height(t1) - height(t2) - + -1 falls slope(t) = 2 falls slope(t) = -2 sonst 15 Universität Bremen (Doppel-)Rotieren in AVL-Baum rotr((rotl(t1), x, t2)) falls slope(t1) = -1 shiftr((t1, x, t2)) = rotr((t1, x, t2)) x 2 t1 t3 y 0, 1 t2 y 0 t4 t2 PI-2: Bäume 2 t1 y -1 y 1, 0 t2 z t3 t4 rotr((t1, x, t2)) shiftl((t1, x, t2)) = z 0 x 2 x 1, 0 t3 t4 sonst t3 t5 rotr((rotl(t1), x, t2)) rotl((t1, x, rotr(t2))) falls slope(t2) = 1 rotl((t1, x, t2)) t4 sonst 16 x 0, -1 t5 t2 Universität Bremen Löschen aus einem AVL-Baum Löschen remove(x, ) = remove(x, (t1, x, )) = t1 remove(x, ( , x, t2)) = t2 remove(x, (t1, x, t2)) = rebal(t1, y, t) wobei (y, t) = leftest(t2) remove(x, (t1, y, t2)) = rebal((remove(x, t1), y, t2)) falls x < y remove(x, (t1, y, t2)) = rebal((t1, y, remove(x, t2))) falls x ≥ y 8 4 1× 2 n× 1 3 6 5 7 Finden des nächsten Elements leftest(( , x, t)) = (x, t) leftest((t1, x, t2)) = (y, rebal((t, x, t2))) wobei (y, t) = leftest(t1) Aufwand 8 5 2 6 Rebalancieren eines Knotens passiert in konstanter Zeit Bei Einfügen und Löschen wird auf dem Pfad von der 1 3 Wurzel zu einem Knoten pro Knoten einmal rebalanciert Also ist der Aufwand O(Höhe des Baumes) Da der Baum balanciert ist, ist der Aufwand also O(log n), auch im schlechtesten Fall PI-2: Bäume 2 17 7 Universität Bremen B-Bäume Motivation AVL-Bäume eignen sich nicht gut für die Verwaltung von Daten auf Massenspeichern (Festplatten) Auf Daten auf Festplatten wird in Form von Sektoren zugegriffen Der Wechsel zwischen Sektoren kostet Zeit (Kopfbewegung) Daher: Daten linear, Baumstruktur als Index (ein Blattsuchbaum) Index ist auch ein Baum, aber ein B-Baum, bei dem ein Knoten in einen Sektor passt B-Baum der Ordnung m Alle Blätter habe die gleiche Tiefe Jeder Knoten mit Ausnahme der Wurzel und der Blätter hat wenigstens m/2 Kinder Die Wurzel hat mindestens 2 Kinder Jeder Knoten hat höchstens m Kinder Jeder Knoten mit i Kindern hat i-1 Schlüssel PI-2: Bäume 2 18 Universität Bremen Einfügen und Suchen in B-Baum Suchen Binäre Suche in den Schlüsseln eines Knotens Dann zum nächsten Knoten, so lange, bis Blatt erreicht Einfügen Suche nach Einfügeposition (ein Blatt) Fall 1: Übergeordneter Knoten ist noch nicht voll Füge Schlüssel in Knoten ein Erzeuge neues Blatt und referenziere dieses aus dem Knoten insert(x) PI-2: Bäume 2 19 Universität Bremen Einfügen in einen B-Baum Fall 2: Übergeordneter Knoten ist voll Füge Schlüssel in Knoten ein (ist zu groß) Teile Knoten in der Mitte Füge mittleren Knoten dem Elternknoten hinzu Falls dieser zu voll ist, teile ihn auch Falls die Wurzel geteilt wird, schaffe eine neue Wurzel und hänge die beiden Hälften der alten Wurzel als Nachfolger daran PI-2: Bäume 2 20 Universität Bremen Beispiel: Einfügen in B-Baum insert(14) PI-2: Bäume 2 21