4 Bäume 4.1 Begriffe und Konzepte Hierarchisches Strukturierungshilfsmittel Bäume: Vererbungsbäume, Dateisysteme, arithmetischer Ausdruck als Baum usw. Fahrzeug KFZ LKW Prof. Dr. Manh Tien Tran PKW Fahrrad Motorrad 1 4 Bäume Arithmetischer Ausdruck a + b*c - d/e + a * b Prof. Dr. Manh Tien Tran / d e c 2 4 Bäume Definition Unter einem orientierten Wurzelbaum versteht man Menge von Knoten und Kanten mit den Eigenschaften: ● ● Es gibt genau einen ausgezeichneten Knoten (die Wurzel). Jeder Knoten, mit Ausnahme der Wurzel, ist durch genau eine Kante mit seinem Vaterknoten (Elternknoten,Vorgänger, parent) verbunden. Er wird dann auch Kind (Sohn, Nachfolger, child) dieses Knotens genannt. Zwei Knoten mit demselben Vater heißen Brüder (Geschwister, siblings). Ein Knoten ohne Kinder heißt Blatt. Die anderen Knoten bezeichnet man als innerer Knoten. Jeder Knoten ist die Wurzel eines Unterbaums, welcher aus ihm und den Knoten unter ihm besteht. Bemerkung Ein Wurzelbaum ist ein azyklischer (kreisfreier) Graph (siehe später) Prof. Dr. Manh Tien Tran 3 4 Bäume Wurzel Kante innerer Knoten + a / * b d e c Blatt Prof. Dr. Manh Tien Tran 4 4 Bäume Bäume sind Mengen mit hierarchischer (rekursiver) Struktur. Alternative Definition Ein orientierter Wurzelbaum (oder kurz: Wurzelbaum) ist eine endliche Menge B von Objekten, die entweder leer ist, oder für die folgendes gilt: ● ● in B existiert ein ausgezeichnetes Element w (die Wurzel von B). Wurzel hat keinen Vorgänger Die Elemente B \ { w } können disjunkt zerlegt werden in B1, B2, ... , Bm, wobei jeder Bi ein orientierter Baum ist. Prof. Dr. Manh Tien Tran 5 4 Bäume Anders ausgedrückt: ● Die leere Menge { } und { w } sind beides Bäume ● Wenn B1, ... , Bm Bäume sind, dann ist B = { w,B1, B2,...,Bm } ein Baum. B1, B2,...,Bm heißen Unterbäume von B. Algorithmen, die auf Bäumen arbeiten, haben oft rekursive Ausprägungen. Prof. Dr. Manh Tien Tran 6 4 Bäume Definition Der äußere Grad (auch positiver Grad, Ausgangsgrad) eines Knotens ist die Anzahl seiner direkten Nachfolger. Ein Pfad in einem Baum ist eine Folge von unterschiedlichen Knoten, in der die aufeinander folgenden Knoten durch Kanten verbunden sind. Unter dem Niveau (Stufe) eines Knotens versteht man die Länge des Pfads (Anzahl der Kanten) von der Wurzel zu dem Knoten. Die Höhe eines Baumes entspricht dem maximalen Niveau eines Blatts plus 1. Das Gewicht eines Baumes ist die Anzahl seiner Blätter. Bemerkung Ein Blatt hat den Grad 0. Ein innerer Knoten hat einen Grad > 0. Prof. Dr. Manh Tien Tran 7 4 Bäume Niveau 0 + a * b Prof. Dr. Manh Tien Tran Niveau 1 / d c e Niveau 2 Niveau32 8 4 Bäume Definition ● ● ● Ist die maximale Anzahl n der Kinder vorgegeben, so spricht man man von nären Baum. Sind die Kinder jedes Knotens in einer bestimmten Reihenfolge geordnet, wird ein solcher Baum als geordneter Baum bezeichnet. Binärbäume sind geordnete Bäume, bei den jeder Knoten maximal zwei Kinder hat. Die Unterbäume heißen rechter und linker Unterbaum. Bemerkung Die Unterscheidung von rechten und linken Unterbäumen ist wichtig (wegen der Reihenfolge). Prof. Dr. Manh Tien Tran 9 4 Bäume Definition ● ● ● Zwei Binärbäume heißen ähnlich, wenn sie dieselbe Struktur haben. Zwei Binärbäume heißen äquivalent, wenn sie ähnlich sind und dieselbe Information enthalten. Ein Binärbaum der Höhe h heißt vollständig, wenn jeder innere Knoten nicht leere linke und rechte Unterbäume hat und jeder Knoten auf dem Niveau h-1 (letztes Niveau) Blatt ist. - ● vollständiger Baum der Höhe 3 + a Prof. Dr. Manh Tien Tran / * d e 10 4 Bäume Bemerkung Die maximale Anzahl von Knoten auf dem Niveau k ist 2k Die Anzahl von Knoten in einem vollständigen Binärbaum der Höhe h ist 2h – 1. Definition ● Ein Binärbaum heißt voll, wenn jedes Niveau mit inneren Knoten ausgefüllt ist, evtl. mit Ausnahme des letzten Niveaus. Prof. Dr. Manh Tien Tran 11 4 Bäume 4.2 Darstellung von binären Bäumen Möglichkeiten: - statische Darstellung mit Hilfe von Arrays - dynamische Darstellung (mit Hilfe von Referenzen/Verweisen) Flexibiltät Speicherbedarf Zugreifbarkeit statisch - - + dynamisch + + - Im Folgenden betrachten wir die Darstellung mit Referenzen. Für statische Darstellungen siehe Literatur. Prof. Dr. Manh Tien Tran 12 4 Bäume 4.2.1 Verkettete Darstellung public class CTree<E> { private CTreeNode<E> mRoot; Ref auf Daten // ... } Referenz auf den linken Teilbaum Referenz auf den rechten Teilbaum class CTreeNode<E> { private int mKey; // wird verwendet für binäre Suchbäume private E mData; // Verweis auf Datenobjekt private CTreeNode<E> mLeft; // Verweis auf den linken Unterbaum private CTreeNode<E> mRight; // Verweis auf den rechten Unterbaum ... Prof. Dr. Manh Tien Tran 13 4 Bäume 4.2.1.1 Konstruktoren public CTreeNode() { this(0,null, null, null); } public CTreeNode(E data) { this(0, data, null, null); } public CTreeNode(int key, E data) { this(key, data, null, null); } public CTreeNode(int key, E data, CTreeNode<E> left, CTreeNode<E> right) { mKey = key; mData = data; mLeft = left; mRight = right; } Prof. Dr. Manh Tien Tran 14 4 Bäume 4.2.1.2 setter/getter-Methoden public int getKey() { return mKey; } public void setKey(int key) { mKey = key; } public E getData() { return mData;} public void setData(E data) { mData = data; } public CTreeNode<E> getLeft() { return mLeft; } public void setLeft(CTreeNode<E> left) { mLeft = left; } public CTreeNode<E> getRight() { return mRight; } public void setRight(CTreeNode<E> right) { mRight = right; } Prof. Dr. Manh Tien Tran 15 4 Bäume Man kann mit diesen einfachen Methoden Bäume konstruieren (alle Keys seien 0) A CTreeNode<String> l = new CTreeNode<String>("D"); CTreeNode<String> r = new CTreeNode<String>("E"); B C r = new CTreeNode<String>(0,"C", l, r); l = new CTreeNode<String>("B"); D E CTreeNode<String> root = new CTreeNode<String>(0,"A", l, r); Prof. Dr. Manh Tien Tran 16 4 Bäume 4.2.2 Durchlaufen (Traversierung) von verketteten binären Bäumen Ziel: Systematisches Aufsuchen jedes Knotens und Verarbeitung des Knoteninhalts. Möglichkeiten (mit der Konvention "links vor rechts") ● ● ● ● Inorder-Durchlauf (Zwischenordnung): zuerst wird der linke Teilbaum besucht, dann der Knoten selbst und anschließend der rechten Teilbaum. Preorder-Durchlauf (Vorordnung): Knoten, linker dann rechter Teilbaum. Postorder-Durchlauf (Nachordnung): linker, rechter Teilbaum dann Knoten. Levelorder-Durchlauf: Breitensuche, d.h. auf jedem Niveau des Baumes werden alle Knoten besucht, bevor auf das nächste Niveau gewechselt wird. Prof. Dr. Manh Tien Tran 17 4 Bäume Beispiel Inorder: A DBEAHFICG Preorder: B D C ABDECFHIG Postorder: E F H Prof. Dr. Manh Tien Tran G I DEBHIFGCA Levelorder: ABCDEFGHI 18 4 Bäume Implementierungen 1. Möglichkeit: Mit Hilfe der Rekursion. Zum Beispiel zur Ausgabe in Preorder public static<E> void printPreOrder(CTreeNode<E> root) { if (root == null) // Nichts zu tun return; System.out.println(root.mData); // Verarbeitung des Knotens printPreOrder(root.mLeft); printPreOrder(root.mRight); } Analog für Inorder und Postorder Prof. Dr. Manh Tien Tran 19 4 Bäume Implementierungen 2. Möglichkeit: Mit Hilfe von Queue für Levelorder – iterative Methode public static <E> void printLevelOrder(CTreeNode<E> root) { if (root == null) return; // Wir verwenden eine Queue für die Knoten CListQueue<CTreeNode<E>> q = new CListQueue<CTreeNode<E>>(); // Die Queue wird initialisiert mit dem Element der Stufe 0 q.add(root); Prof. Dr. Manh Tien Tran 20 4 Bäume do { // erster Knoten aus der Queue entfernen CTreeNode<E> current = q.get(); // und die Kinder in die Queue hinzufügen – Nächste Level! if (current.mLeft != null) q.add(current.mLeft); if (current.mRight != null) q.add(current.mRight); System.out.println(current.mData); } while (q.isEmpty() == false); } Prof. Dr. Manh Tien Tran 21 4 Bäume Iterative Methode – Beispiel für Inorder. Mit Stack public static <E> void printInorder(CTreeNode<E> root) { if (root == null) return; // nichts zu tun // Wir verwenden einen Stack (um die Rekursion zu "simulieren" CListStack<CTreeNode<E>> stack = new CListStack<CTreeNode<E>>(); CTreeNode<E> current = root; Prof. Dr. Manh Tien Tran 22 4 Bäume while (true) { while (current != null) { stack.push(current); // steigt soweit wie möglich nach links ab und current = current.getLeft(); // legt alle Knoten auf den Stack } if (stack.isEmpty()) break; // stop falls der Stack leer ist current = stack.pop(); // das oberste Element entnehmen und verarbeiten System.out.println(current.getData()); current = current.getRight(); // Fortsetzung mit dem rechten Unterbaum } } Prof. Dr. Manh Tien Tran 23 4 Bäume Fädelung von verketteten Binärbäumen Rekursive Durchlaufalgorithmen sind "relativ teuer" – iterative sind effizienter, erfordern aber mehr Programmieraufwand. => Technik der Fädelung: Man versucht, die Baumknoten in der Reihenfolge der gewünschten Durchlaufordnung zu verknüpfen. Idee ● Jeder Knoten mit Grad < 2 hat freie Verweise, die zur Fädelung verwendet werden können (=> Siehe Literatur). Probleme ● Nur ein Durchlauftyp wird bevorzugt. ● Hoher Aufwand, wenn sich der Baum ständig ändert. Prof. Dr. Manh Tien Tran 24 4 Bäume 4.3 Binäre Suchbäume Ziele ● Effiziente Verarbeitung großer geordneter Datenbestände ● Effiziente Suchoperationen ● Sortierte Verarbeitung des Datenbestandes Forderungen ● Auf einzelne Datensätze wird mittels Schlüssels (entweder Teil der Daten oder Extradaten) zugegriffen. ● Auf den Schlüsselwerten ist eine Ordnung definiert. ● Die Schlüsselwerte sind eindeutig im gesamten Datenbestands. Prof. Dr. Manh Tien Tran 25 4 Bäume Voraussetzung Im Folgenden nehmen wir an, dass die Schlüssel vom Typ int sind. Eigenschaften von binären Suchbäumen ● ● ● ● Leerer Baum ist auch ein Suchbaum. Alle Schlüssel der Datenobjekte im linken Unterbaum von B sind kleiner als der Schlüssel in der Wurzel von B. Alle Schlüssel der Datenobjekte im rechten Unterbaum von B sind größer als das in der Wurzel von B. Der linke und der rechte Unterbaum von B sind jeweils auch binäre Suchbäume. Prof. Dr. Manh Tien Tran 26 4 Bäume Grundoperationen auf binären Suchbäumen ● Einfügen eines Knotens ● Suchen eines Knotens ● Sortierte Ausgabe aller Knoten ● Löschen eines Knotens 4.3.1 Datenstruktur public class CBinSearchTree<E> { // Referenz auf die Wurzel des binären Suchbaums private CTreeNode<E> mRoot = null; Prof. Dr. Manh Tien Tran 27 4 Bäume 4.3.2 Konstruktor /** Konstruktor - Ein leerer Baum wird erzeugt */ public CBinSearchTree() {} 4.3.3 Einfügen eines Knotens Vorgehensweise ● ● ● ● Neue Knoten werden immer als Blätter eingefügt Die Position des Blattes wird durch den Schlüssel des neuen Knotens (bzw. durch die Vergleiche mit dem neuen Knoten) festgelegt. Beim Aufbau eines Baumes wird der erste Knoten die Wurzel Hat der Baum schon Knoten, dann wird zuerst durch Vergleiche die Position des Vaterknotens bestimmt. Der neue Knoten wird dann als Kind hinzugefügt. Prof. Dr. Manh Tien Tran 28 4 Bäume Beispiele Fall1 – lexikographische Ordnung "Schlüssel vom Typ String" Einfüge-Reihenfolge: Melone, Apfel, Banane, Ananas, Clementine, Trauben, Mango, Kiwi, Orange Melone Trauben Apfel Ananas Orange Banane Clementine Mango Prof. Dr. Manh Tien Tran Kiwi 29 4 Bäume Fall2 – lexikographische Ordnung Einfüge-Reihenfolge: Mango, Apfel, Banane, Ananas, Clementine, Melone, Kiwi, Orange, Trauben Mango Melone Apfel Ananas Banane Orange Trauben Clementine Kiwi Prof. Dr. Manh Tien Tran 30 4 Bäume Bemerkungen Die Reihenfolge des Einfügens bestimmt das Aussehen des Baumes ● Bei sortierter Reihenfolge entartet der binäre Suchbaum zu einer linearen Liste. ● public boolean insert(int k, E e) { // erster Fall: der Baum ist leer if (mRoot == null) { // Neuer Knoten wird die Wurzel des Baumes mRoot = new CTreeNode<E>(k, e, null, null); return true; } Prof. Dr. Manh Tien Tran 31 4 Bäume // Baum ist nicht leer, wir müssen die richtige Position suchen // Hier verwenden wir zwei Referenzen CTreeNode<E> parent = null; CTreeNode<E> child = mRoot; while (child != null) { // wir gehen nach unten - Update die Referenz parent = child; // Vergleiche die Schlüssel int cmp = key - child.getKey(); Prof. Dr. Manh Tien Tran 32 4 Bäume if (cmp < 0) // nach links, weil kleiner child = child.getLeft(); else if (cmp == 0) // Objekt schon im Baum => liefert false zurück return false; else // nach rechts weil größer child = child.getRight() ; } if (key - parent.getKey() < 0) parent.setLeft(new CTreeNode<E>(key, e)); else parent.setRight(new CTreeNode<E>(key, e)) return true; } // end of insert Prof. Dr. Manh Tien Tran 33 4 Bäume Alternativ (Rekursion) public boolean insertRec(int key, E e) { // Baum ist leer => Wurzel ist der neue Knoten if (mRoot == null) { mRoot = new CTreeNode<E>(key, e, null, null); return true; } // Aufruf einer Rekursion return insertNodeRec(mRoot, key, e); } Prof. Dr. Manh Tien Tran 34 4 Bäume private static <E> boolean insertNodeRec(CTreeNode<E> root, int key, E e) { int cmp = key - root.getKey() ; // Ergebnis des Vergleichs if (cmp == 0) return false; // Gleich => false zurückgeben else if (cmp < 0) { // < 0 => links if (root.getLeft() == null) { // links ist noch frei => Einfügen root.setLeft(new CTreeNode<E>(key, e)); return true; } else // links ist nicht frei => Einfügen return insertNodeRec(root.getLeft(), key, e); // in den linken Unterbaum } Prof. Dr. Manh Tien Tran 35 4 Bäume else { if (root.getRight() == null) { // rechts weil größer // rechts ist frei? Ja => Einfügen root.setRight(new CTreeNode<E>(key, e)); return true; } else // rechts belegt => weiter mit dem return insertNodeRec(root.getRight(), key, e); // rechten Unterbaum } } Prof. Dr. Manh Tien Tran 36 4 Bäume 4.3.4 Suchen eines Knotens Vorgehensweise ● ● ● Die Position des Knotens wird nach dem gleichen Verfahren wie beim Einfügen eines Knotens gesucht. Die Suchmethode liefert das Objekt zurück, falls das Element mit dem angegebenen Schlüssel gefunden wird, null sonst. Man kann sowohl iterativ als auch rekursiv formulieren. In diesem Fall ist die Rekursion schlechter! Prof. Dr. Manh Tien Tran 37 4 Bäume public E find(int k) { CTreeNode<E> tmp = mRoot; while (tmp != null) { int cmp = k - tmp.getKey(); if (cmp < 0) tmp = tmp.getLeft(); else if (cmp == 0) return tmp.getData(); else tmp = tmp.getRight(); } return null; } Prof. Dr. Manh Tien Tran 38 4 Bäume public static <E> E findRek(CTreeNode<E> root, int k) { if (root == null) return null; int cmp = k - root.getKey(); if (cmp < 0) return findRek(root.getLeft(), k); else if (cmp == 0) return root.getData(); else return findRek(root.getRight(), k); } public E findRek(int key) { return findRek(mRoot, key); } Prof. Dr. Manh Tien Tran 39 4 Bäume 4.3.5 Sortierte Ausgaben aller Knoten Vorgehensweise Ein Inorder-Durchlauf des binären Suchbaums liefert eine nach aufsteigenden Schlüsselwerten sortierte Liste der Knoten Melone Beispiel Trauben Apfel Ananas Banane Orange Ananas, Apfel, Banane, Melone, Orange, Trauben Prof. Dr. Manh Tien Tran 40 4 Bäume public String toString() { if (mRoot == null) return "[]"; StringBuilder s = new StringBuilder("["); toStringRek(s, mRoot); // siehe unten // ein Komma am Ende zu viel => Ersetz es durch ] s.setCharAt(s.length()-1, ']'); return s.toString(); } Prof. Dr. Manh Tien Tran 41 4 Bäume private static<E> void toStringRek(StringBuilder s, CTreeNode<E> root) { if (root == null) return; toString(s, root.getLeft()); s.append(root.getData() + ","); toString(s, root.getRight()); } Prof. Dr. Manh Tien Tran 42 4 Bäume 4.3.6 Löschen eines Knotens Vorgehensweise ● ● Die Position des zu löschenden Knotens wird nach dem gleichen Verfahren wie beim Einfügen eines Knotens gesucht. Beim Löschen eines Knotens muss man 3 Fälle unterscheiden Fall 1 Der Knoten ist ein Blatt => Lösche diesen Knoten, keine weitere Operationen zu löschender Knoten Prof. Dr. Manh Tien Tran 43 4 Bäume Fall 2 Der Knoten ist ein innerer Knoten, der entweder einen leeren linken oder einen leeren rechten Unterbaum besitzt => Der zu löschende Knoten wird entfernt und durch den Wurzelknoten des nicht-leeren Unterbaum ersetzt. zu löschender Knoten Prof. Dr. Manh Tien Tran 44 4 Bäume Fall 2 zu löschender Knoten Prof. Dr. Manh Tien Tran 45 4 Bäume Fall 3 Der zu löschende Knoten ist ein innerer Knoten mit zwei nicht leeren Unterbäumen. Problem: Wo werden die beiden Unterbäume nach dem Löschen des Knotens angehängt? Sei K der zu löschenden Knoten Lösung1: Sei Kr der Knoten mit dem kleinsten Schlüssel im rechten Unterbaum von K. ● Vertausche die Daten von K und Kr ● Lösche den Knoten Kr => Fall 1 oder 2 Lösung 2: Wie Lösung 1 aber mit dem größten Element im linken Unterbaum D.h. Fall 3 wird auf Fall 1 oder 2 zurückgeführt. Prof. Dr. Manh Tien Tran 46 4 Bäume Daten tauschen wird später gelöscht (Fall 1 oder 2) Prof. Dr. Manh Tien Tran 47 4 Bäume Beispiel Mango Melone Apfel Ananas Banane Orange Trauben Clementine Kiwi Löschen Kiwi : Fall 1 Prof. Dr. Manh Tien Tran 48 4 Bäume Beispiel (Fortsetzung) Mango Melone Apfel Ananas Banane Clementine Orange Trauben Löschen Orange : Fall 2 Prof. Dr. Manh Tien Tran 49 4 Bäume Beispiel (Fortsetzung) Mango Melone Apfel Ananas Banane Trauben Clementine Fall 3: Lösche Apfel (=> Apfel mit Bananr vertauschen, und wie vorhin löschen) Prof. Dr. Manh Tien Tran 50 4 Bäume Beispiel (Fortsetzung) Ergebnis: Mango Banane Ananas Prof. Dr. Manh Tien Tran Clementine Melone Trauben 51 4 Bäume Wichtiger Sonderfall: Zu löschenden Knoten ist die Wurzel. In Fällen 1 und 2 benötigen wir zwei Referenzen: Eine auf den Vater und eine auf den zu löschenden Knoten. Fall 1 vater == null => die Wurzel wird gelöscht mRoot mRoot Prof. Dr. Manh Tien Tran 52 4 Bäume Fall 2 Der zu löschende Knoten ist links vom Vater vater.setLeft(ersatz) vater knoten ersatz Prof. Dr. Manh Tien Tran 53 4 Bäume Fall 3 Der zu löschende Knoten ist rechts vom Vater vater.setRight(ersatz) vater knoten ersatz Prof. Dr. Manh Tien Tran 54 4 Bäume /** * Suche nach einem Baumknoten, der das Objekt enthält * @param key Schlüssel zum Finden * @return Array mit zwei Elementen: Der Vater und der Knoten mit dem * Schlüssel falls gefunden, null sonst */ private Object[] findNode(int key) { // Wir starten mit der Wurzel CTreeNode<E> parent = null; CTreeNode<E> current = this.mRoot; Prof. Dr. Manh Tien Tran 55 4 Bäume while (current != null) { // so lange wir den Schlüssel noch nicht gefunden haben, vergleichen // wir den Schlüssel mit dem im Knoten abgespeicherten Schlüssel int compareResult = key - current.getKey(); if (compareResult == 0) { // Gleich => return das Ergebnis return new Object[]{parent, current}; } Prof. Dr. Manh Tien Tran 56 4 Bäume else if (compareResult > 0) { // größer => Versuch mit dem rechten Baum parent = current; current = current.getRight(); } else { // sonst den linken Unterbaum parent = current; current = current.getLeft(); } } // Objekt ist nicht im Baum return null; } // end of findNode Prof. Dr. Manh Tien Tran 57 4 Bäume /** * Löscht ein Objekt mit dem gegebenen Schlüssel * @param key Schlüssel des Objekts zum Löschen * @return Das zum löschende Objekt (null falls Objekt nicht im Baum ist */ public E remove(int key) { // wenn der Baum leer ist => null zurückgeben if (null == this.mRoot) { return null; } Prof. Dr. Manh Tien Tran 58 4 Bäume // Schritt 1: Den zu löschenden Knoten finden // wir brauchen dazu zwei Referenzen. Eine auf den zu löschenden Knoten // und eine auf seinen Vater (um die Referenzen ggf. zu ändern) Object[] tmp = findNode(key); if (tmp == null) { // Objekt nicht im Baum return null; } Prof. Dr. Manh Tien Tran 59 4 Bäume // Vorbereitung tmp[0] zeigt auf den Vater, tmp[1] auf den zu löschenden // Knoten – current zeigt auf den zu löschenden Knoten, parent der Vater // und replacement den Ersatzknoten (null im Fall 1, bzw. ein normaler // Knoten im Fall 2. Fall 3 wird zurückgeführt auf Fall 1 bzw. 2 CTreeNode<E> parent = (CTreeNode<E>) tmp[0]; CTreeNode<E> current = ((CTreeNode<E>) tmp[1]); E ret = current.getData(); boolean links = false; // Merker, ob Ersatzknoten links oder rechts steht CTreeNode<E> replacement; Prof. Dr. Manh Tien Tran 60 4 Bäume if (current.getLeft() == null) { // Fall 1 bzw. Fall 2 - Beachte, dass Fall 1 ein Spezialfall von Fall 2 ist // (Ersatz ist null im Fall 1) replacement = current.getRight(); } else if (current.getRight() == null) { replacement = current.getLeft(); } Prof. Dr. Manh Tien Tran 61 4 Bäume else { // Fall 3: Knoten ist ein innerer Knoten mit zwei Elementen => // Ersetz den Knoten mit dem Mimimum des rechten Unterbaums CTreeNode<E> succ = current.getRight(); parent = current; // das kleinste Element im rechten Unterbaum suchen while (succ.getLeft() != null) { parent = succ; succ = succ.getLeft(); } Prof. Dr. Manh Tien Tran 62 4 Bäume // Daten in knoten übertragen current.setKey(succ.getKey()); current.setData(succ.getData()); // zu löschenden Knoten ist succ und wird durch seinen rechten Sohn // (Fall 1 bzw. 2) ersetzt current = succ; replacement = current.getRight(); } Prof. Dr. Manh Tien Tran 63 4 Bäume // Knoten löschen if (parent == null) { // Wurzel wird gelöscht mRoot = replacement; } else if (parent.getLeft() == current) { parent.setLeft(replacement); } else { parent.setRight(replacement); } return ret; } // end of remove Prof. Dr. Manh Tien Tran 64 4 Bäume 4.3.7 Kosten der Grundoperationen ● n sei die Anzahl der Knoten. ● Kostenmaß: #aufgesuchten Knoten bzw. #Vergleiche Sortierte Ausgabe aller Knoten Inorder-Durchlauf => O(n) Einfügen eines Knotens Aufwand in Abhängigkeit von der Einfügeposition => Weg von der Wurzel bis zu einem Blatt => Durch die Höhe h beschränkt => Aufwand O(h) Prof. Dr. Manh Tien Tran 65 4 Bäume Suchen eines Knotens Aufwand in Abhängigkeit von der Position => Maximaler Suchweg läuft von der Wurzel bis zu einem Blatt => Durch die Höhe h beschränkt => Aufwand O(h) Löschen eines Knotens Schlimmster Fall ist die Suche nach einem Austauschpartner im Fall 3 => Ebenfalls durch die Höhe h beschränkt => Aufwand O(h) Folgerung Die Höhe spielt eine entscheidende Rolle bei binären Suchbäumen. Prof. Dr. Manh Tien Tran 66 4 Bäume Schlimmster Fall Der Baum ist entartet (nur rechte oder linke nicht-leere Unterbäume) => O(n) Bester Fall Der Baum ist voll. Die Höhe ist etwa log(n) => O(log(n)) Durchschnittliche mittlere Zugriffskosten (ohne Beweis) Im Mittel O(log(n)) (im Vergleich zu dem Idealfall mit einem mittleren Aufwand von etwa 39% zu rechnen). In der meisten Anwendung will man einen gewissen Grad von Ausgeglichenheit der Suchbäume garantieren. => Balancierte binäre Suchbäume Prof. Dr. Manh Tien Tran 67 4 Bäume 4.4 AVL-Bäume AVL: Adelson-Velskii, Landis (russische Mathematiker), 1962 Definition Ein AVL-Baum ist ein binärer Suchbaum mit der Eigenschaft: Für jeden (inneren) Knoten ist der absolute Betrag der Differenz der Höhen des linken und rechten Teilbaumes maximal 1. Die Knoten sei k mit den Unterbäumen left und right. Der Balancierungsfaktor von k ist definiert durch b(k) = Höhe(left) – Höhe(right) Die AVL-Eigenschaft besagt, dass für jeden Knoten k der Balancierungsfaktor b(k) entweder -1, 0 oder 1 ist. Prof. Dr. Manh Tien Tran 68 4 Bäume Beispiel 10 10 7 1 20 9 4 AVL-Baum Prof. Dr. Manh Tien Tran 7 15 4 1 20 9 5 kein AVL-Baum 69 4 Bäume 4.4.1 Datenstruktur class CAVLTreeNode<E> { private int mKey; // wird verwendet für binäre Suchbäume private E mData; // Verweis auf Datenobjekt private int mBF; // Balancierungsfaktor private CAVLTreeNode<E> mLeft; // Verweis auf den linken Unterbaum private CAVLTreeNode<E> mRight; // Verweis auf den rechten Unterbaum ... Prof. Dr. Manh Tien Tran 70 4 Bäume 4.4.2 Grundoperationen auf AVL-Bäumen ● ● ● ● Einfügen eines Knotens => erfordert ggf. Reorganisationen Suchen eines Knotens => wie bei binären Suchbäumen (hier wird garantiert, dass der Suchaufwand O(log(n)) ist) Sortierte Ausgaben eines Knotens => wie bei binären Suchbäumen (Aufwand O(n)) Löschen eines Knotens=> erfordert ggf. Reorganisationen Wichtig: Die maximale Höhe eines AVL-Baums ist immer O(log(n)) Aufwand für Einfügen und Löschen ist somit O(log(n)) Prof. Dr. Manh Tien Tran 71 4 Bäume Einfügen in AVL-Bäumen Vorgehensweise: ● ● ● Durchlaufe von der Wurzel zur Bestimmung der Einfügeposition und füge den neuen Knoten als Blatt ein. Nach dem Einfügen eines neuen Knotens können die Balancen der Knoten auf dem Pfad von der Wurzel bis zum neuen Knoten geändert werden. Die Verletzung der AVL-Eingenschaften muss durch sogenannte Rotation behoben werden. Zu betrachtende Fälle sind: – Einfügen im linken Teilbaum des linken Kindes (LL) – Einfügen im rechten Teilbaum des linken Kindes (RL) – Einfügen im rechten Teilbaum des rechten Kindes (RR) – Einfügen im linken Teilbaum des rechten Kindes (LR) Prof. Dr. Manh Tien Tran 72 4 Bäume Beispiel Im Knoten stehen Schlüssel / Balancierter Faktor 1010/ 1 7/1 7 1/-1 1 10 / 2 20/1 20 9/0 9 15/0 15 4/0 4 1/-2 15/0 9/0 4/1 5/0 Prof. Dr. Manh Tien Tran 20/1 7/2 5/0 73 4 Bäume Beispiel Balancieren 10 / 2 20/1 7/2 1/-2 10 / 1 15/0 9/0 4/0 1/0 4/1 20/1 7/1 9/0 15/0 5/0 5/0 Prof. Dr. Manh Tien Tran 74 4 Bäume Für das Ausgleichen wird ausgehend von eingefügten Knoten k0 ein Knoten k1 auf dem Pfad zur Wurzel gesucht, dessen Großvater k3 als erster unbalanciert ist. 1. Fall Rotationstyp LL (einfache Rotation) Einfügen in linken Teilbaum des linken Kindes. k3 / 2 k2/ 1 k2 / 0 k1 / 0 k3 / 0 k1 / 0 Prof. Dr. Manh Tien Tran 75 4 Bäume Fall 3 Rotationstyp RR (einfache Rotation) Einfügen in rechten Teilbaum des rechten Kindes k3 / -2 k2 / 0 k3 / 0 k2/ -1 k1 / 0 k1 / 0 Prof. Dr. Manh Tien Tran 76 4 Bäume Fall 2 Rotationstyp LR (doppelte Rotation) Einfügen in rechten Teilbaum des linken Kindes k3 / 2 k1 / 0 L k2 / 0 k2/ -1 k3 / 0 R k1 / 0 Prof. Dr. Manh Tien Tran 77 4 Bäume Fall 4 Rotationstyp RL (doppelte Rotation) Einfügen in linken Teilbaum des rechten Kindes k3 / -2 k1 / 0 R k2/ 1 k3 / 0 k2 / 0 L k1 / 0 Prof. Dr. Manh Tien Tran 78 4 Bäume Beispiel Einfügen 2, 4, 11 , 10 2 2/0 4 2/-1 11 4/0 10 4/-1 2/0 4/-1 10 11/1 RR 2/-2 10/0 11/2 10/1 2/0 11/0 4/-2 2/0 4/0 LL 11/0 4/-1 2/0 10/0 9/ 0 11/0 9/0 Prof. Dr. Manh Tien Tran 79 4 Bäume Beispiel (Fortsetzung) Einfügen 6 , 1 , 3 6 4/-2 10/1 2/0 9/ 1 6/ 0 RL 11/0 1 1/ 0 Prof. Dr. Manh Tien Tran 9/0 4/0 2/ 0 6/ 0 10/-1 6/ 0 11/0 3 9/1 4/1 2/ 1 6 10/-1 9/1 4/1 2/ 0 11/0 1/ 0 6/ 0 10/-1 11/0 3/ 0 80 4 Bäume Beispiel (Fortsetzung) Einfügen 5, 7 5 9/1 4/0 2/ 0 1/ 0 3/ 0 10/-1 6/ 1 5/ 0 Prof. Dr. Manh Tien Tran 7 9/1 4/0 2/ 0 11/0 1/ 0 3/ 0 10/-1 6/ 0 5/ 0 11/0 7/ 0 81 4 Bäume Beispiel (Fortsetzung) Einfügen 8 8 9/2 4/-1 2/ 0 1/ 0 3/ 0 6/0 10/-1 6/ -1 5/ 0 4/-1 2/ 0 11/0 7/ -1 1/ 0 3/ 0 5/ 0 9/0 7/ -1 10/-1 8/ 0 11/0 8/ 0 Prof. Dr. Manh Tien Tran 82 4 Bäume Löschen in AVL-Bäumen Vorgehensweise: ● ● Durchlaufe von der Wurzel aus einem Suchpfad zur Bestimmung der Löschposition und lösche den Knoten (=> Löschpfad) Höhenänderung von Unterbäumen möglich, deren Wurzeln auf dem Löschpfad liegen. Nur die Knoten auf diesem Pfad kann das AVL-Kriterium verletzt werden, Ablauf: ● ● ● Zuerst "normales Löschen" (wie bei binären Suchbäumen). Kritischen Knoten bestimmen (nähester Vorgänger mit BF = +2 oder -2. Dieser ist Ausgangspunkt der Rotation. Entlang des Löschpfad (bis zur Wurzel) müssen ggf. Rotationen durchgeführt werden. Prof. Dr. Manh Tien Tran 83 4 Bäume 6/0 Beispiel Löschen von 9 4/-1 10/0 Tauschen 6/0 4/-1 2/ 0 1/ 0 5/ 0 3/ 0 2/ 0 9/0 7/ -1 1/ 0 5/ 0 11/0 6/0 4/-1 11/0 2/ 0 1/ 0 Prof. Dr. Manh Tien Tran 9/-1 8/ 0 3/ 0 10/-1 8/ 0 7/ -1 3/ 0 5/ 0 10/1 7/ -1 11/0 8/0 84 4 Bäume 7/0 Beispiel Löschen von 6 4/1 10/1 Tauschen 6/0 4/1 2/ 0 1/ 0 2/ 0 10/1 5/ 0 3/ 0 7/ -1 1/ 0 5/ 0 11/0 8/0 3/ 0 7/1 11/0 4/1 8/0 2/ 0 1/ 0 Prof. Dr. Manh Tien Tran 6/ -1 10/0 5/ 0 8/0 11/0 3/ 0 85 4 Bäume Beispiel Löschen von 1, 5 7/1 4/1 2/ 0 1/ 0 7/1 4/1 10/0 5/ 0 3/ 0 8/0 11/0 2/ -1 2/ -1 5/ 0 8/0 3/ 0 7/1 4/2 10/0 7/0 10/0 8/0 3/0 11/0 11/0 2/ 0 10/0 4/ 0 8/0 11/0 3/ 0 Prof. Dr. Manh Tien Tran 86 4 Bäume Beispiel Lösche 11, 8 und 10 7/0 7/1 3/0 2/ 0 10/0 4/ 0 8/0 Lösche 11 und 8 11/0 2/ 0 3/0 10/0 4/ 0 3/0 7/2 3/0 Lösche 10 2/ 0 Prof. Dr. Manh Tien Tran LL Rotation 4/ 0 2/ 0 7/2 4/ 0 87 4 Bäume 4.5 Vergleiche (worst-case) sortiertes Array verkettete Liste normaler binärer Suchbaum AVL-Baum Suchen O(log n) O(n) O(log n) O(log n) Ausgabe aller Knoten O(n) O(n) O(n) O(log n) Einfügen O(n) O(n) O(n) O(log n) Löschen O(n) O(n) O(n) O(log n) Direkter Zugriff O(1) O(n) O(n) O(n) Prof. Dr. Manh Tien Tran 88 4 Bäume 4.5 Wichtige Variante Ein Rot-Schwarz-Baum ist ein binärer Suchbaum, in dem jeder Knoten eine Zusatzinformation – seine Farbe – trägt. Dabei gelten: 1) Jeder Knoten im Baum ist entweder rot oder schwarz. 2) Die Wurzel des Baums ist schwarz. 3) Alle leeren Knoten (null) sind schwarz. 4) Ist ein Knoten rot, so sind beide Kinder schwarz. 5) Jeder Pfad von einem gegebenen Knoten zu seinen Blattknoten enthält die gleiche Anzahl schwarzer Knoten (Schwarzhöhe/Schwarztiefe). (siehe z.B. wikipedia.de bzw. Implementierung in JDK) Wichtige Eigenschaft: Längster Pfad ist max. doppelt so lang wie kürzester Pfad. Prof. Dr. Manh Tien Tran 89