Universität Bremen Universität Bremen Bäume 2 Rückblick „Bäume 1“ Begriffe Thomas Röfer Suchbäume Suchen, Einfügen, Löschen Balancierte Bäume (AVL-Bäume) B-Bäume Durchlaufen von Bäumen Spielprobleme Huffman-Baum 1 Eigene Züge Eigene Züge H PI-2: Bäume 2 9 0 A G F E D C 2 Universität Bremen Suchbäume Normaler Suchbaum Ansatz Motivation Ein Suchbaum dient zum schnellen Auffinden von Werten, möglichst in O(log n) Werte stehen in inneren Knoten Blätter sind leer Definition Suchalgorithmus 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 Fall 1: Knoten ist innerer Knoten 27 … 39 39 … ∞ Alternative Schreibweise -∞ … 1 1…3 15 … 27 Sichtweise Die Blätter repräsentieren Intervalle im Wertebereich der Schlüssel PI-2: Bäume 2 3 … 14 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 14 … 15 O(Höhe des Baumes) 3 Universität Bremen PI-2: Bäume 2 4 Universität Bremen Blattsuchbäume Suchen in Suchbaum Ansatz Wenn gesuchter Wert Geordnet wie Suchbäume Werte stehen in Blättern Innere Knoten enthalten nur „Wegweiser“ gleich dem Schlüssel gefunden kleiner als Schlüssel weitersuchen in linkem Nachfolger ansonsten weitersuchen in rechtem Nachfolger z.B. größten Wert des linken Teilbaums Suchalgorithmus Comparable Comparable search(Comparable search(Comparable value) value) {{ return return search2(root, search2(root, value); value); }} private private Comparable Comparable search2( search2( Node Node node, node, Comparable Comparable value) value) {{ 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 0 18 0 1 5 0 1 2 0 1 2 0 1 2 0 3 1 1 1 1 1 1 B Eigene Züge ((( 1 ) 3 (( 14 ) 15 )) 27 ( 39 )) 4 1 Gegnerische Züge Universität Bremen 9 1 Gegnerische Züge }} 5 PI-2: Bäume 2 if(node if(node == == null) null) return return null; 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 else if(c if(c >> 0) 0) return return search2(node.right, search2(node.right, value); value); else else return return node.data; node.data; }} 6 1 Universität Bremen Universität Bremen Suchbaum mit Wächter/Stopper Einfügen in einen Suchbaum 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 insert(17) PI-2: Bäume 2 7 PI-2: Bäume 2 Universität Bremen 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 Löschen aus einem Suchbaum void void insert(Comparable insert(Comparable value) value) {{ if(root if(root == == null) null) new Node(value); root = root = new 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) new Node(value); node.right = node.right = new Node(value); else else insert2(node.right, insert2(node.right, value); value); }} PI-2: Bäume 2 9 Universität Bremen 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 8 4 2 1 n× 3 8 5 1× 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 8 Löschen aus einem Suchbaum 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 Node parent, parent, Comparable Comparable value) value) {{ if(node if(node != != null) null) {{ int int cc == value.compareTo(node.data); value.compareTo(node.data); if(c if(c << 0) 0) remove2(node.left, remove2(node.left, node, node, value); value); else if(c else if(c >> 0) 0) remove2(node.right, remove2(node.right, node, node, value); value); else else removeNode(node, removeNode(node, parent); parent); }} }} 11 private private void void removeNode(Node removeNode(Node node, node, Node Node parent) parent) {{ if(node.right == null) if(node.right == null) replaceChild(parent, node, replaceChild(parent, 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 if(node.right.left else 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 == pon.left.right; pon.left.right; }} }} PI-2: Bäume 2 private private void void replaceChild(Node replaceChild(Node node, node, Node Node oldNode, oldNode, Node Node newNode) newNode) {{ }} if(node if(node == == null) null) root root == newNode; newNode; else else if(node.left if(node.left == == oldNode) oldNode) node.left node.left == newNode; 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 == node.left; node.left; return return node; node; }} 12 2 Universität Bremen Universität Bremen Balancierte Bäume Balanciertheit, Höhe, Rotieren Motivation Baum Durch degenerierte Bäume kann die Suchdauer stark ansteigen x Leerer Baum: Sonst: (t1, x, t2) AVL-Baum (Adelson-Velskij und Landis) Definition t1 Balanciertheit t2 heightbal( ) = true heightbal((t1, x, t2)) = | height(t1) - height(t2) | ≤ 1 /\ heightbal(t1) /\ heightbal(t2) 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 Höhe height( ) =0 height((t1, x, t2)) = max(height(t1), height(t2)) + 1 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 Baum rotieren rotr x y rotr(((t1, y, t2), x, t3)) = (t1, y, (t2, x, t3)) rotl((t1, y, (t2, x, t3))) = ((t1, y, t2), x, t3) t1 y t3 t1 x t2 t2 t3 rotl PI-2: Bäume 2 13 PI-2: Bäume 2 Universität Bremen (Doppel-)Rotieren in AVL-Baum Einfügen rebal((insert(x, t1), y, t2)) falls x < y rebal((t1, y, insert(x, t2))) sonst t1 Rebalancieren t3 - + x 1, 0 t4 shiftl((t1, x, t2)) = 15 y -1 t2 z t3 t2 t4 y 1, 0 t3 t5 t4 x 0, -1 t5 t2 rotr((rotl(t1), x, t2)) rotl((t1, x, rotr(t2))) falls slope(t2) = 1 rotl((t1, x, t2)) sonst PI-2: Bäume 2 Universität Bremen 16 Universität Bremen Löschen aus einem AVL-Baum B-Bäume Löschen Motivation 8 4 2 n× 1 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 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) 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 t3 z 0 x 2 t1 rotr((t1, x, t2)) PI-2: Bäume 2 Aufwand t2 sonst y 0 t4 -1 falls slope(t) = 2 falls slope(t) = -2 sonst 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 y 0, 1 + =0 = height(t1) - height(t2) Kann im Baum gespeichert werden und braucht nicht immer wieder neu ausgerechnet zu werden rebal(t) = rotr((t1, x, t2)) x 2 Neigung eines Knotens shiftr(t) shiftl(t) t rotr((rotl(t1), x, t2)) falls slope(t1) = -1 shiftr((t1, x, t2)) = insert(x, ) = ( , x, ) slope( ) slope((t1, x, t2)) 14 Universität Bremen Einfügen in AVL-Baum insert(x, (t1, y, t2)) = f((t1, x, t2)) = (t2, x, t1) Node f(Node n) { return new Node(n.right, n.data, n.left);} 17 7 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 3 Universität Bremen Universität Bremen Einfügen und Suchen in B-Baum Einfügen in einen B-Baum Suchen Fall 2: Übergeordneter Knoten ist voll Binäre Suche in den Schlüsseln eines Knotens Dann zum nächsten Knoten, so lange, bis Blatt erreicht 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 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 PI-2: Bäume 2 20 Universität Bremen Beispiel: Einfügen in B-Baum insert(14) PI-2: Bäume 2 21 4