# 2. Baumstrukturen Bäume gehören zu den wichtigsten Datenstrukturen der Informatik. Sie finden Anwendung in vielen Bereichen, z.B. als Suchbäume zum schnellen Finden von Elementen in geordneten Mengen. ## Binärbaum Der Binärbaum wird in der Regel als rekursive Datenstruktur betrachtet: Ein Binärbaum besteht demnach aus einem Element (genauer einem Objekt einer zuvor festgelegten Klasse), einem linken und einem rechten Teilbaum. Eine Ordnung ist für einen Binärbaum nicht erforderlich. ### Einen Binärbaum aufbauen Für ein erstes Beispiel soll der folgende Baum aufgebaut werden: ![](img/baum2.gif) Zur Vereinfachung wird als Grundklasse für den Baum die Klasse *String* definiert, d.h. die Elemente des Beispielsbaums werden als Zeichenketten gespeichert. Sicherlich wäre eine Modellierung als Zahl naheliegend, doch dann wäre der Einsatz der Wrapper-Klasse Integer (vgl. Abschnitt [Objekte als Datenspeicher](linear.md#objekte-als-datenspeicher)) oder der Entwurf einer neuen Klasse erforderlich. Der folgende Quellcode baut den Baum *bottom-up* (d.h. von unten nach oben) auf. Eine andere Vorgehensweise ist nicht möglich, da beispielsweise der Gesamtbaum (vgl. Element 23) nur dann erzeugbar ist, wenn linker und rechter Teilbaum (vgl. Elemente 17 und 38) bereits existieren. ```java BinaryTree k5 = new BinaryTree("5"); BinaryTree k14 = new BinaryTree("14"); BinaryTree k13 = new BinaryTree("13",k5,k14); BinaryTree k19 = new BinaryTree("19"); BinaryTree k17 = new BinaryTree("17",k13,k19); BinaryTree k39 = new BinaryTree("39"); BinaryTree k40 = new BinaryTree("40",k39,null); BinaryTree k24 = new BinaryTree("24"); BinaryTree k38 = new BinaryTree("38",k24,k40); tree = new BinaryTree("23",k17,k38); ``` ### Einen Binärbaum durchsuchen (Tiefensuche) Beim Durchsuchen eines Binärbaums kommt zunächst die Tiefensuche in Betracht. Hier wird der Baum rekursiv durchsucht, wobei dem linken Teilbaum Vorrang vor dem rechten Teilbaum gegeben wird. Der Zeitpunkt der Verarbeitung des aktuellen Elements (hier angedeutet durch den Ausdruck auf dem Bildschirm) vor dem ersten Rekursionsaufruf, zwischen den beiden Aufrufen oder nach dem zweiten Rekursionsaufruf beeinflusst zusätzlich die Reihenfolge der Verarbeitung. Die drei Varianten werden als *PreOrder*, *InOrder* bzw. *PostOrder* bezeichnet. Die am häufigsten eingesetzte Variante ist der InOrder-Durchlauf, da er die Elemente in ihrer natürlichen Reihenfolge verarbeitet. Im obigen Beispiel, das eine Anordnung von Zahlen im Baum vorsieht, würden die Elemente deshalb auch in aufsteigender Reihenfolge abgearbeitet. ```java public void inorder(BinaryTree tree) { if (!tree.isEmpty()) { inorder(tree.getLeftTree()); System.out.println(tree.getContent()); // Ausdrucken inorder(tree.getRightTree()); } } ``` ### Einen Binärbaum durchsuchen (Breitensuche) Als Alternative zur Tiefensuche steht die Breitensuche zur Verfügung, die die Elemente ebenenweise abarbeitet. ![](img/baum2.gif) Für den Beispielbaum würde die Reihenfolge der Verarbeitung dann *23, 17-38, 13-19-24-40, 5-14-39* lauten. Um die Ebenen abzubilden, wird eine [Schlange](linear.md#stack-und-queue) als Hilfsdatenstruktur verwendet. ```java public void levelorder(BinaryTree tree) { Queue> q = new Queue>(); q.enqueue(tree); while (!q.isEmpty()) { BinaryTree t = q.front(); q.dequeue(); System.out.println(t.getContent()); // Ausdrucken if (!t.getLeftTree().isEmpty()) { q.enqueue(t.getLeftTree()); } if (!t.getRightTree().isEmpty()) { q.enqueue(t.getRightTree()); } } } ``` ### Einen Binärbaum verarbeiten (mit Rückgabewert) Zum Abschluss soll hier ein komplexeres Beispiel präsentiert werden. Zur Illustration dient der nachfolgende Baum. Die Aufgabe besteht darin, die Tiefe des Baums (d.h. die Anzahl der benutzten Ebenen, im Beispiel 4) zu berechnen. ![](img/baum2.gif) Die kompakte rekursive Methode hat es durchaus in sich: Ist der aktuelle Teilbaum leer, so ist seine Tiefe 0. Ansonsten ist seine Tiefe um 1 größer als die maximale Tiefe des linken oder des rechten Teilbaums - je nachdem, welcher von beiden die größere Tiefe besitzt. ```java public int depth(BinaryTree tree) { if (tree.isEmpty()) { return 0; } else { int left = depth(tree.getLeftTree()); int right = depth(tree.getRightTree()); if (left>right) { return left+1; } else { return right+1; } } } ``` ## Suchbaum Der Suchbaum stellt einen guten Kompromiss aus den Datenstrukturen Feld und Liste dar: Er ist dynamisch, erlaubt also jederzeit nachträgliches Einfügen oder Löschen ohne zusätzlichen Speicherbedarf. Er ermöglicht zugleich eine schnelle Suche, da die Idee der binären Suche übertragbar ist, wodurch sich logarithmischer Suchaufwand ergibt. ### Eine Klasse zum Suchen vorbereiten Voraussetzung für den Aufbau eines Suchbaums ist eine Ordnung der zu verwaltenden Elemente. Für die Klasse *BinarySearchTree* wird die erforderliche Ordnung so realisiert, dass für die Basisklasse der zu speichernden Objekte gefordert wird, dass sie das Interface *ComparableContent* implementiert. In diesem Interface wird mit den drei Methoden *isLess()*, *isGreater()* und *isEqual()* die Vergleichbarkeit der Elemente verankert. ```java public interface ComparableContent { public boolean isGreater(ContentType pContent); public boolean isEqual(ContentType pContent); public boolean isLess(ContentType pContent); } ``` Die folgende Beispielklasse *Entry* speichert für jedes Objekt exemplarisch ein ganzzahliges Attribut, das über eine get-Methode abgefragt werden kann. In den drei Vergleichsmethoden muss ein Objekt die Frage beantworten, wie es sich selbst gegenüber einem zweiten Objekt (hier *pContent*) der gleichen Klasse einordnet. ```java public class Entry implements ComparableContent { private int wert; public Entry(int pWert) { this.wert = pWert; } public boolean isLess(Entry pContent) { return wert < pContent.getWert(); } public boolean isEqual(Entry pContent) { return wert == pContent.getWert(); } public boolean isGreater(Entry pContent) { return wert > pContent.getWert(); } public int getWert() { return wert; } } ``` ### Einen Suchbaum aufbauen Steht die Basisklasse, so steht dem Aufbau des Suchbaums nichts mehr im Wege. Im nachfolgenden Beispiel werden nacheinander die Elemente 45, 25 usw. in den Suchbaum eingefügt. Für das geordnete Einfügen sorgt die Methode *insert*. Somit ergibt sich der folgende Beispielsuchbaum: ![](img/baum1.png) ```java Entry int1 = new Entry(45); Entry int2 = new Entry(25); Entry int3 = new Entry(65); Entry int4 = new Entry(15); Entry int5 = new Entry(35); tree = new BinarySearchTree(); tree.insert(int1); tree.insert(int2); tree.insert(int3); tree.insert(int4); tree.insert(int5); ``` ### In einem Suchbaum suchen Um nun ein Element im Suchbaum zu suchen, ist zunächst ein Such-Objekt der Basisklasse (in unserem Beispiel *Entry*) anzulegen. Kann die Methode *search()* ein passendes Objekt im Baum finden, so wird dieses Objekt zurückgegeben. Bleibt die Suche erfolglos, so gibt sie *null* zurück. Eine Fallunterscheidung kann die beiden Fälle trennen. ```java Entry suchitem = new Entry(77); Entry ergitem = tree.search(suchitem); if (ergitem!=null) { System.out.println("Ja"); } else { System.out.println("Nein"); } ``` ### Einen Suchbaum durchlaufen Analog zum Binärbaum kann auch der Suchbaum rekursiv durchsucht werden. Diese Möglichkeit ist aber vorrangig für Debugging-Zwecke (z.B. zum Ausdrucken des Suchbaum-Inhalts) interessant. ```java private void durchlaufeBaum(BinarySearchTree mytree) { if (!mytree.isEmpty()) { durchlaufeBaum(mytree.getLeftTree()); System.out.println(mytree.getContent()); durchlaufeBaum(mytree.getRightTree()); } } ```