2 2 Java: Bäume 2.1 Implementierung von Bäumen 2.2 Implementierung eines binären Suchbaums 2.3 Traversierung von Bäumen 2.4 Implementierung von Heapsort 19 2 Java: Bäume Teil II Java: Bäume Überblick Implementierung von Bäumen Implementierung eines binären Suchbaums Traversierung von Bäumen Implementierung von Heapsort Saake/Schallehn Algorithmen & Datenstrukturen II 2–1 Implementierung von Bäumen ◮ ◮ Allgemein: Datenstruktur bestehend aus Baum, Baumknoten und den vom Baum organisierten Datenobjekten Unterscheidungen nach ◮ ◮ ◮ ◮ ◮ ◮ Verzweigungsgrad des Baumes geordnetem oder ungeordnetem Baum Anzahl Knotentypen Suchbäume Verzeigerung der Daten Vorgehensweise für Einfügen, Löschen, etc. Saake/Schallehn 20 Algorithmen & Datenstrukturen II 2–2 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Allgemeine Struktur ◮ Baumklasse mit Baum-spezifischen Attributen und Methoden public class Tree { TreeNode root; ... } ◮ Knotenklasse mit knoten-spezifischen Attributen und Methoden private class TreeNode { ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–3 Alternative: mit Java-Generics ◮ Baum für Elemente eines bestimmten Typs T public class Tree<T> { TreeNode<T> root; ... } ◮ Objekte des Typs T sind über Knoten verwaltet private class TreeNode<T> { T element; ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–4 Alternativen: mit Vererbung ◮ Jeder Baum ist auch ein Knoten public class Tree extends TreeNode { ... } ◮ bzw. jeder Knoten ist auch ein Baum private class TreeNode extends Tree { ... } ◮ Meist nicht sinnvoll, da Klassen mit sehr unterschiedlichen Methoden Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–5 21 2 Java: Bäume Alternativen: Anzahl Knotentypen ElementNode HTML <HTML> <HEAD> <TITLE>Hello </TITLE> </HEAD> <BODY> Hello World! </BODY> </HTML> ElementNode ElementNode HEAD BODY ElementNode TextNode TITLE Hello World! TextNode Hello Saake/Schallehn Algorithmen & Datenstrukturen II 2–6 Alternativen: Anzahl Knotentypen /2 ◮ Verschiedene Knotentypen sinnvoll z.B. ◮ ◮ ◮ ◮ bei Syntaxbäumen für Statements, Blöcke, Operanden, Operatoren etc. bei hierarchischen Dokumentenstrukturen für verschiedene Dokumentenabschnitte wie Kapitel, Überschriften, Paragraphen, eingebettete Grafiken etc. bei Dateiverwaltung für Laufwerke, Verzeichnis, Dateien, Verknüpfungen etc. bei unterschiedlicher Struktur von internen Knoten (Verzweigung zu Kindknoten) und Blattknoten (Daten) Saake/Schallehn Algorithmen & Datenstrukturen II 2–7 Alternativen: Verzweigungsgrad und Ordnung ◮ ◮ Verzweigungen zu Kindknoten als Referenzen in Baumknoten Ordnung: ist die Reihenfolge der Kindknoten von Bedeutung? ◮ ◮ ◮ ja: geordneter Baum nein: ungeordneter Baum Verzweigungsgrad: (maximale) Anzahl der möglichen Kindknoten ◮ ◮ ◮ beliebiger Verzweigungsgrad fester (maximaler) Verzweigungsgrad n → n-ärer Baum wichtiger Sonderfall: n = 2 → binärer Baum Saake/Schallehn 22 Algorithmen & Datenstrukturen II 2–8 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Ungeordneter Baum mit beliebigem Verzweigungsgrad ◮ Umsetzung der Verzweigung public class TreeNode<T> { Set<TreeNode<T> > childNodes; ... } ◮ ◮ Speicheraufwand bei Verwendung von Set<...> sehr groß Alternative: dynamisch anzupassende Arrays Saake/Schallehn Algorithmen & Datenstrukturen II 2–9 Geordneter Baum mit beliebigem Verzweigungsgrad ◮ Umsetzung mit Liste public class TreeNode<T> { List<TreeNode<T> > childNodes; ... } ◮ Bessere Speicher- und Laufzeiteffizienz auch hier mit Arrays public class TreeNode<T> { TreeNode<T>[] childNodes; ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–10 Geordneter Baum mit festem Verzweigungsgrad ◮ n-ärer Baum public class TreeNode<T> { TreeNode<T>[] childNodes = new TreeNode<T>[n]; ... } ◮ Binärer Baum public class TreeNode<T> { TreeNode<T> left, right; ... } Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–11 23 2 Java: Bäume Alternative: Suchbaum ◮ Suchbaum erfordert das zusätzliche Abspeichern eines Schlüssels public class Tree<K,T> { TreeNode<K,T> root; ... } public class TreeNode<K,T> { K key; T element; ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–12 Suchbaum: Schlüssel und Daten Student: Wolf Meier Studiengang: IF Jahrgang: 2003 154302 137888 Student: Peter Pau Studiengang: WIF Jahrgang: 2000 Student: Lutz Lau Studiengang: IF Jahrgang: 1990 122543 Saake/Schallehn 178456 141666 Student: Karl Fuchs Studiengang: CV Jahrgang: 2004 Student: Knut Boll Studiengang: IF Jahrgang: 2002 Algorithmen & Datenstrukturen II 2–13 Alternative: Suchbaum ◮ Nicht-eindeutiger Suchschlüssel erzwingt mengenwertige Einträge public class Tree<K,T> { TreeNode<K,T> root; ... } public class TreeNode<K,T> { K key; Set<T> elements; ... } Saake/Schallehn 24 Algorithmen & Datenstrukturen II 2–14 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Suchbaum: Schlüssel und Daten 5.10. Gunter Saake Bob Geldof Eike Schallehn 22.9. 13.11. Can Türker Joan Jett Nick Cave Saake/Schallehn Algorithmen & Datenstrukturen II 2–15 Binärer Suchbaum ◮ Im folgenden: binärer Suchbaum ohne Betrachtung der Datenelemente → eigentliche Daten für Verständnis der Algorithmen unerheblich public class Tree<K> { TreeNode<K> root; ... } public class TreeNode<K> { K key; TreeNode<K> left, right; ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–16 Weitere Implementierungsalternativen ◮ ◮ Häufige auftretendes Kriterium: Verbesserung der Speicher- und. Laufzeiteffizienz durch Speicherung in “flachen” Strukturen, z. B. Levelorder Speicherung eines binären Baumes in einem Array → siehe Abschnitt zu Heapsort Laufzeiteffizienz durch kleinere innere Knoten: bei Suchbäumen Verweise auf Daten häufig nur an Blattknoten → z.B. Binärbaum mit Verzweigung nach ≤ und > als Schlüssel Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–17 25 2 Java: Bäume Implementierung eines binären Suchbaums ◮ Binärer Suchbaum: ◮ ◮ ◮ ◮ Häufig verwendete Hauptspeicherstruktur Insbesondere geeignet für Schlüssel fester Größe, z.B. numerische int, float und char[n] Gewährleistet O(log2 n) für Suchen, Einfügen und Löschen, vorausgesetzt Baum ist balanciert Später: ◮ ◮ ◮ Gewährleistung der Balancierung durch spezielle Algorithmen → AVL- und Rot-Schwarz-Bäume Für Sekundärspeicher: größere, angepasste Knoten günstiger → B-Bäume Für Zeichenketten als Schlüssel: variable Schlüsselgröße → Tries Saake/Schallehn Algorithmen & Datenstrukturen II 2–18 Implementierung eines binären Suchbaums /2 Code in BinarySearchTree.java auf der Vorlesungsseite. Saake/Schallehn Algorithmen & Datenstrukturen II 2–19 Implementierung eines binären Suchbaums /3 public class BinarySearchTree<K extends Comparable<K> > implements Iterable<K> { ... static class TreeNode<K extends Comparable<K> > { K key; TreeNode<K> left = null, right = null; ... } } Saake/Schallehn 26 Algorithmen & Datenstrukturen II 2–20 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Implementierung eines binären Suchbaums /4 ◮ ◮ ◮ Schlüssel müssen Comparable-Interface, d.h. compareTo()-Methode, implementieren, da Suchbaum auf Vergleichen der Schlüssel basiert Baum selber implementiert Iterable-Interface, d.h. iterator()-Methode, um Traversierung des Baums über Iterator zu erlauben → später bei Baumtraversierung TreeNode und alles weitere als innere Klassen implementiert → erlaubt Zugriff auf Attribute und Methoden der Baumklasse Saake/Schallehn Algorithmen & Datenstrukturen II 2–21 Implementierung eines binären Suchbaums /5 ◮ Besonderheit der Implementierung: ◮ ◮ “leere” Pseudoknoten head und nullNode zur Vereinfachung der Algorithmen Grundlegende Algorithmen ◮ ◮ ◮ Suchen Einfügen Löschen Saake/Schallehn Algorithmen & Datenstrukturen II 2–22 Implementierung mit Pseudoknoten head nullNode Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–23 27 2 Java: Bäume Implementierung mit Pseudoknoten /2 public class BinarySearchTree<K> { public BinarySearchTree () { head = new TreeNode<K>(null); nullNode = new TreeNode<K>(null); nullNode.setLeft(nullNode); nullNode.setRight(nullNode); head.setRight(nullNode); } ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–24 Implementierung mit Pseudoknoten /3 ◮ ◮ Ziel: Verminderung von Sonderfällen head-Knoten: ◮ ◮ Einfügen oder Löschen des Wurzelknotens würde spezielle Behandlung in der Baum-Klasse erfordern null-Knoten: ◮ ◮ Erspart Testen, ob zum linken und rechten Teilknoten navigiert werden kann im nullNode einfaches Beenden der Navigation (z.B. Rekursion) möglich Saake/Schallehn Algorithmen & Datenstrukturen II 2–25 Suchen im binären Suchbaum 1. Vergleich des Suchschlüssels mit Knotenschlüssel 6 3 1 9 5 7 2. wenn kleiner, dann in linken Teilbaum weiter suchen 3. wenn größer, dann in rechten Teilbaum weiter suchen 4. sonst Saake/Schallehn 28 Algorithmen & Datenstrukturen II gefunden 2–26 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Binärer Suchbaum: Knotenvergleich class TreeNode< ... > { ... public int compareKeyTo(K k) { return (key == null ? -1 : key.compareTo(k)); } ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–27 Binärer Suchbaum: Rekursives Suchen protected TreeNode<K> recursiveFindNode (TreeNode<K> n, K k) { if (n != nullNode) { int cmp = n.compareKeyTo (k); if (cmp == 0) return n; else if (cmp > 0) return recursiveFindNode (n.getLeft (), k); else return recursiveFindNode (n.getRight (), k); } else return null; } Saake/Schallehn Algorithmen & Datenstrukturen II 2–28 Binärer Suchbaum: Iteratives Suchen protected TreeNode<K> iterativeFindNode (K k) { TreeNode<K> n = head.getRight(); while (n != nullNode) { int cmp = n.compareKeyTo(k); if (cmp == 0) return n; else n = (cmp > 0 ? n.getLeft () : n.getRight ()); } return null; } Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–29 29 2 Java: Bäume Spezialfall: Suchen des kleinsten Elementes public K findMinElement () { TreeNode<K> n = head.getRight(); while (n.getLeft () != nullNode) n = n.getLeft (); return n.getKey (); } Saake/Schallehn Algorithmen & Datenstrukturen II 2–30 Spezialfall: Suchen des größten Elementes public K findMaxElement () { TreeNode<K> n = head.getRight(); while (n.getRight () != nullNode) n = n.getRight (); return n.getKey (); } Saake/Schallehn Algorithmen & Datenstrukturen II 2–31 Binärer Suchbaum: Einfügen ◮ Schüssel Einfügen prinzipiell in 2 Schritten 1. Schritt: Einfügeposition suchen → Blattknoten mit nächstkleinerem oder nächstgrößerem Schlüssel 2. Schritt: neuen Knoten erzeugen und als Kindknoten des Knoten aus Schritt 1 verlinken ◮ In Schritt 1: wenn Schlüssel bereits existiert, nicht erneut einfügen Saake/Schallehn 30 Algorithmen & Datenstrukturen II 2–32 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Einfügen 1: Einfügeposition Suchen public boolean insert (K k) { System.out.println(”insert: ” + k); TreeNode<K> parent = head, child = head.getRight(); while (child != nullNode) { parent = child; int cmp = child.compareKeyTo(k); if (cmp == 0) return false; else if (cmp > 0) child = child.getLeft (); else child = child.getRight (); } ... Saake/Schallehn Algorithmen & Datenstrukturen II 2–33 Einfügen 2: neuen Knoten Verlinken ... TreeNode<K> node = new TreeNode<K>(k); node.setLeft(nullNode); node.setRight(nullNode); if (parent.compareKeyTo(k) > 0) parent.setLeft (node); else parent.setRight (node); return true; } Saake/Schallehn Algorithmen & Datenstrukturen II 2–34 Binärer Suchbaum: Löschen ◮ Schüssel Löschen in 3 Schritten 1. Schritt: zu löschenden Knoten finden 2. Schritt: Nachrücker Knoten finden 2.1 Fall: externer Knoten ohne Kinder → ersetzen durch nullNode 2.2 Fall: nur ein rechter Kindknoten → ersetzen durch rechten Kindknoten 2.3 Fall: nur ein linker Kindknoten → ersetzen durch linken Kindknoten 2.4 Fall: interner Knoten mit Kindern links und rechts → ersetzen durch Knoten mit kleinstem (alternativ: größtem) Schlüssel im rechten (alternativ: linken) Teilbaum 3. Schritt: Baum reorganisieren Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–35 31 2 Java: Bäume Löschen 1: Knoten suchen public boolean remove (K k) { TreeNode<K> parent = head, node = head.getRight(), child = while (node != nullNode) { int cmp = node.compareKeyTo(k); if (cmp == 0) break; else { parent = node; node = (cmp > 0 ? node.getLeft() : node.getRight()); } } if (node == nullNode) return false; ... Saake/Schallehn Algorithmen & Datenstrukturen II 2–36 Löschen 2: Nachrücker finden /1 ... if (node.getLeft() == nullNode && node.getRight() == nullNode child = nullNode; else if (node.getLeft() == nullNode) child = node.getRight(); else if (node.getRight() == nullNode) child = node.getLeft(); ... Saake/Schallehn Algorithmen & Datenstrukturen II 2–37 Löschen 2: Nachrücker finden /2 ... else { child = node.getRight(); tmp = node; while (child.getLeft () != nullNode) { tmp = child; child = child.getLeft (); } child.setLeft(node.getLeft()); if (tmp != node) { tmp.setLeft(child.getRight()); child.setRight(node.getRight()); } } ... Saake/Schallehn 32 Algorithmen & Datenstrukturen II 2–38 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Löschen 3: Baum Reorganisieren ... if (parent.getLeft() == node) parent.setLeft(child); else parent.setRight(child); return true; } Saake/Schallehn Algorithmen & Datenstrukturen II 2–39 Entartung von Bäumen ◮ Ungünstige Einfüge- oder Löschreihenfolge führt zu extremer Unbalanciertheit ◮ Extremfall: Baum wird zur Liste ◮ Dann Operationen mit Komplexität O(n) ◮ Beispiel for (int i=0; i < 10; i ++) tree.insert(i); ◮ Vermeidung: durch spezielle Algorithmen zum Einfügen und Löschen Saake/Schallehn Algorithmen & Datenstrukturen II 2–40 Traversierung von (Binär-)bäumen ◮ Traversierung durch Methoden ◮ ◮ ◮ ◮ ◮ Inorder Preorder Postorder Levelorder Traversierung mit Hilfe von Iteratoren ◮ Erfordert Zwischenspeicherung des Traversierungszustands Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–41 33 2 Java: Bäume Preorder-Traversierung ◮ Preorder-Traversierung: Behandlung (Ausgabe) des aktuellen Knotens zuerst, dann linker und rechter Teilbaum (W-L-R) static class TreeNode<K extends Comparable<K> > { ... public void traverse() { if (key == null) return; System.out.print(” ” + key); left.traverse(); right.traverse(); } } Saake/Schallehn Algorithmen & Datenstrukturen II 2–42 Inorder-Traversierung ◮ ◮ Inorder-Traversierung: Behandlung des linken Teilbaums, dann des aktuellen Knotens, dann rechter Teilbaum (L-W-R) Gibt Baum in sortierter Reihenfolge aus public void traverse() { if (key == null) return; left.traverse(); System.out.print(” ” + key); right.traverse(); } Saake/Schallehn Algorithmen & Datenstrukturen II 2–43 Postorder-Traversierung ◮ Postorder-Traversierung: Behandlung des linken und rechten Teilbaums, dann erst des aktuellen Knotens (L-R-W) public void traverse() { if (key == null) return; left.traverse(); right.traverse(); System.out.print(” ” + key); } Saake/Schallehn 34 Algorithmen & Datenstrukturen II 2–44 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Levelorder-Traversierung ◮ Levelorder-Traversierung: Ausgabe der Knoten geordnet nach 1. Baumebene, beginnend bei der Wurzel 2. Ordnung innerhalb der Baumebene, beginnend bei kleinstem Element ◮ Implementierung: siehe Übung Saake/Schallehn Algorithmen & Datenstrukturen II 2–45 Traversierung mit Iteratoren ◮ Iteratoren erlauben ◮ ◮ Schrittweise Abarbeitung Verwendung von Standardschleifen für Baumdurchlauf for (Integer i : tree) System.out.print(i); ◮ Erfordert Zwischenspeicherung des Bearbeitungszustands ◮ Unterstützung verschiedener Iteratoren Saake/Schallehn Algorithmen & Datenstrukturen II 2–46 Traversierung mit Iteratoren /2 public class BinarySearchTree<K extends Comparable<K> > implements Iterable<K> { public public public public ... static static static static final final final final int int int int INORDER = 1; PREORDER = 2; POSTORDER = 3; LEVELORDER = 4; public void setIterationOrder(int io) { if (io < 1 || io > 4) return; iterationOrder = io; } ... Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–47 35 2 Java: Bäume Traversierung mit Iteratoren /3 public Iterator<K> iterator () { switch (iterationOrder) { case INORDER: return new InorderIterator<K>(this); case PREORDER: return new PreorderIterator<K>(this); case POSTORDER: return new PostorderIterator<K>(this); case LEVELORDER: return new LevelorderIterator<K>(this); } return new InorderIterator<K>(this); } Saake/Schallehn Algorithmen & Datenstrukturen II 2–48 Inorder-Iterator /1 class InorderIterator <K extends Comparable<K> > implements java.util.Iterator<K> { java.util.Stack<TreeNode<K> > st = new java.util.Stack< TreeNode<K> >(); public InorderIterator(BinarySearchTree<K> tree) { TreeNode<K> node = tree.head.getRight(); while (node != nullNode) { st.push(node); node = node.getLeft(); } } ... Saake/Schallehn Algorithmen & Datenstrukturen II 2–49 Inorder-Iterator /2 ... public boolean hasNext() { return !st.isEmpty(); } public K next() { TreeNode<K> node = st.pop(); K obj = node.getKey(); node = node.getRight(); while (node != nullNode) { st.push(node); node = node.getLeft(); } return obj; } } Saake/Schallehn 36 Algorithmen & Datenstrukturen II 2–50 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Preorder-Iterator /1 class PreorderIterator <K extends Comparable<K> > implements java.util.Iterator<K> { ... public PreorderIterator(BinarySearchTree<K> tree) { if (tree.head.getRight() != nullNode) st.push(tree.head.getRight()); } ... Saake/Schallehn Algorithmen & Datenstrukturen II 2–51 Preorder-Iterator /2 ... public K next() { TreeNode<K> node = st.pop(); K obj = node.getKey(); if (node.getRight() != nullNode) st.push(node.getRight()); if (node.getLeft() != nullNode) st.push(node.getLeft()); return obj; } ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–52 Levelorder-Iterator /1 class LevelorderIterator <K extends Comparable<K> > implements java.util.Iterator<K> { private java.util.Queue<TreeNode<K> > q = new java.util.LinkedList<TreeNode<K> >(); public LevelorderIterator( BinarySearchTree<K> tree) { TreeNode<K> node = tree.head.getRight(); if (node != nullNode) q.offer(node); } ... Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–53 37 2 Java: Bäume Levelorder-Iterator /2 ... public K next() { TreeNode<K> node = q.poll(); K obj = node.getKey(); if (node.getLeft() != nullNode) q.offer(node.getLeft()); if (node.getRight() != nullNode) q.offer(node.getRight()); return obj; } ... } Saake/Schallehn Algorithmen & Datenstrukturen II 2–54 Implementierung von Heapsort ◮ Heap-Eigenschaft ◮ ◮ ◮ ◮ Baum ist vollständig, d.h., die Blattebene ist von links nach rechts gefüllt. Schlüssel eines jeden Knotens ist kleiner (oder gleich) als die Schlüssel seiner Kinder. Implementierung als Array (Levelorder-Abspeicherung eines Baums) Auf dessen Basis einfacher und effizienter Sortieralgorthmus umsetzbar Saake/Schallehn Algorithmen & Datenstrukturen II 2–55 Implementierung von Heapsort Code in HeapSort.java auf der Vorlesungsseite. Saake/Schallehn 38 Algorithmen & Datenstrukturen II 2–56 Uni Magdeburg, WS 2005/06 2 2 Java: Bäume Heap als Java-Array 4 9 17 4 7 11 9 14 7 17 11 14 [0] [1] [2] [3] [4] [5] Saake/Schallehn Algorithmen & Datenstrukturen II 2–57 Heapsort: Programmrahmen public class HeapSort { ... public static void main(String[] args) { Comparable[] array = initArray(20); printArray(array); heapSort(array); printArray(array); } } Saake/Schallehn Algorithmen & Datenstrukturen II 2–58 Heapsort: Hauptalgorithmus public static void heapSort(Comparable[] f) { int i; for (i = f.length / 2; i >= 0; i--) percolate(f, i, f.length); for (i = f.length - 1; i > 0; i--) { swap(f, 0, i); percolate(f, 0, i); } } Saake/Schallehn Algorithmen & Datenstrukturen II Uni Magdeburg, WS 2005/06 2–59 39 2 Java: Bäume Heapsort: Heap herstellen private static void percolate( Comparable[] f, int idx, int last) { int i = idx + 1, j; while (2 * i <= last) { j = 2 * i; if (j < last && f[j-1].compareTo(f[j]) > 0) j++; if (f[i-1].compareTo(f[j-1]) > 0) { swap(f,i-1,j-1); i = j; } else break; } } Saake/Schallehn 40 Algorithmen & Datenstrukturen II 2–60 Uni Magdeburg, WS 2005/06