Bäume Universität Paderborn Prof. Dr. Heike Wehrheim Eine rekursive Datenstruktur mit rekursiven Methoden Wichtigstes Beispiel in der Informatik: binärer Baum M heißt Binärbaum über G, wenn gilt: G: Grundmenge 1. M ist die leere Menge, also M = ∅ (wobei ∅ das leere Element repräsentiert) oder 2. Es gibt ein g ∈ G mit: M = (B1, g, B2) und B1 und B2 sind Binärbäume über G. GP1 – WS 08/09 599 Beispiel Universität Paderborn Prof. Dr. Heike Wehrheim Grundmenge G = natürliche Zahlen = {1, 2, 3, ... } M1 = (∅, 2, ∅) ist Binärbaum über G M2 = (M1, 3, ∅) ist Binärbaum über G M3 = (M1, 5, M2) ist Binärbaum über G M4 = (M3, 2, M2) ist Binärbaum über G M4 = (M3, 2, M2) = ((M1, 5, M2), 2, (M1, 3, ∅)) = ((M1, 5, (M1, 3, ∅)), 2, (M1, 3, ∅)) = (((∅, 2, ∅), 5, ((∅, 2, ∅), 3, ∅)), 2, ((∅, 2, ∅), 3, ∅)) GP1 – WS 08/09 600 Universität Paderborn Prof. Dr. Heike Wehrheim Beispiel M4 = (M3, 2, M2) M2 = (M1, 3, ∅) M3 = (M1, 5, M2) 2 5 M3 3 M1 M2 M2 M1 2 2 M3 5 M3 5 M2 M1 M1 M2 3 3 M2 M1 M2 M1 GP1 – WS 08/09 601 Universität Paderborn Prof. Dr. Heike Wehrheim Weiter… M1 = (∅, 2, ∅) M4=((M1, 5, (M1, 3, ∅)), 2, (M1, 3, ∅)) 2 2 M3 5 Jetzt noch M1 ersetzen durch 2 M1 M2 3 3 M2 2 M1 M3 5 2 M1 M2 3 3 M2 2 GP1 – WS 08/09 2 602 Struktur Universität Paderborn Prof. Dr. Heike Wehrheim Es entsteht eine Hierarchie, gefüllt mit Elementen der Grundmenge Jeder Knoten hat einen Wert und einen linken und rechten Teilbaum, der ggf. leer ist Knoten ohne Nachfolger sind Blattknoten oder Blätter Knoten mit Nachfolgern heißen innere Knoten Eigenes Beispiel: an der Tafel GP1 – WS 08/09 603 Benutzung in Programmen Universität Paderborn Prof. Dr. Heike Wehrheim Aufbau als verzeigerte Struktur (wie bei linearen Listen) Pro Knoten drei Einträge: 1. Verweis auf den linken Teilbaum (null, wenn Teilbaum = ∅) 2. Gespeicherter Wert der Grundmenge 3. Verweis auf den rechten Teilbaum (null, wenn Teilbaum = ∅) GP1 – WS 08/09 604 In Java Universität Paderborn Prof. Dr. Heike Wehrheim Zahlen (int) als Grundmenge: class BinTree { private BinTree left, right; private int value; // Konstruktor für innere Knoten BinTree (BinTree l, int v, BinTree r) { left = l; value = v; right = r; } // Konstruktor für Blattknoten BinTree (int v) { left = null; value = v; right = null; } ...} GP1 – WS 08/09 605 Universität Paderborn Prof. Dr. Heike Wehrheim Anschaulich 6 3 steht also für :BinTree left: right: null value: 6 GP1 – WS 08/09 :BinTree left: null right: null value: 3 606 Beispielaufbau eines Baumes BinTree BinTree BinTree BinTree BinTree BinTree b1 b2 b3 b4 b5 b6 = = = = = = new new new new new new BinTree(2); BinTree(3); BinTree(b1, 6, b2); BinTree(4); BinTree(b4, 8, null); BinTree(b5, 3, b3); oder in einer einzigen Anweisung: BinTree b6 = new BinTree( new BinTree( new BinTree(4), 8, null), 3, new BinTree( new BinTree(2), 6, new BinTree(3)) ); GP1 – WS 08/09 Universität Paderborn Prof. Dr. Heike Wehrheim 3 8 4 6 2 3 607 Baumdurchläufe Universität Paderborn Prof. Dr. Heike Wehrheim Drei Arten, je nach Position des eigenen Wertes: INFIX = Erst linken Teilbaum ausgeben, dann den eigenen Wert, dann rechten Teilbaum ausgeben PREFIX = Erst den eigenen Wert ausgeben, dann linken Teilbaum, dann rechten Teilbaum ausgeben POSTFIX = Erst den linken Teilbaum ausgeben, dann rechten Teilbaum, dann eigenen Wert ausgeben Im Beispielbaum (von letzter Folie): INFIX: 4 - 8 - 3 - 2 - 6 - 3 PREFIX: 3 - 8 - 4 - 6 - 2 - 3 POSTFIX: 4 - 8 -2 - 3 - 6 - 3 zusätzlich mit Klammern, also in der Form INFIX: ( linker Teilbaum - eigener Wert - rechter Teilbaum ) INFIX: ( ( ( 4 ) 8 ( ) ) 3 ( ( 2 ) 6 ( 3 ) ) ) GP1 – WS 08/09 608 Infix-Ausgabe in Java Universität Paderborn Prof. Dr. Heike Wehrheim void inFix() { // Ausgabe in Infix-Form, // geklammert System.out.print(“(“); // Drucke linken Teilbaum if (left !=null) left.inFix(); // Drucke eigenen Wert System.out.print(value); // Drucke rechten Teilbaum if (right !=null) right.inFix(); System.out.print(“)”); } Aufruf etwa durch b6.inFix(); BinTreeTest.java produziert (((4)8)3((2)6(3))) GP1 – WS 08/09 609 Postfix-Ausgabe Universität Paderborn Prof. Dr. Heike Wehrheim void postFix() { // Ausgabe in Postfix-Form // ohne Klammern // Drucke linken Teilbaum if (left !=null) left.postFix(); // Drucke rechten Teilbaum if (right !=null) right.postFix(); // Drucke eigenen Wert System.out.print(value); } Dann produziert b6.postFix(); die Ausgabe 482363 GP1 – WS 08/09 610 Wichtig für Rekursion Universität Paderborn Prof. Dr. Heike Wehrheim Abbruchbedingung: Hier: kein rechter oder linker Unterbaum mehr da (left == null, right == null) „Auf Abbruch hinarbeiten“: Methode wird für Objekte aufgerufen, die näher am Abbruch sind, nämlich tiefer im Baum, näher an den Blättern GP1 – WS 08/09 611 Weiteres Beispiel Universität Paderborn Prof. Dr. Heike Wehrheim Berechnung der Summe aller Werte von Knoten int summeKnoten () { int summe = 0; summe += value; if (left != null) summe += left.summeKnoten(); if (right != null) summe += right.summeKnoten(); return summe; } BinTreeSummenTest.java GP1 – WS 08/09 612 Eigene Aufgabe Universität Paderborn Prof. Dr. Heike Wehrheim Schreiben Sie eine rekursive Methode, die die Knoten eines Binärbaumes zählt. BinTreeSummenTest.java GP1 – WS 08/09 613 Bedeutung von Binärbäumen Universität Paderborn Prof. Dr. Heike Wehrheim Binäre Suchbäume zum (sehr) schnellen Suchen in großen Datenmengen Werte im linken Teilbaum kleiner als der eigene Wert; dieser wiederum kleiner (kleiner gleich) als die Werte im rechten Teilbaum INFIX-Form entspricht der sortierten Wertefolge Bäume, die Rechenausdrücke beschreiben Innere Knoten sind mit Operationen versehen; Blätter entsprechen den Argumenten eines Ausdrucks Schnelle Auswertung des Ausdrucks im Baum INFIX-Form entspricht der normalen geklammerten Schreibweise GP1 – WS 08/09 614 Universität Paderborn Prof. Dr. Heike Wehrheim Binäre Suchbäume Für jeden Knoten im Baum gilt: Die Werte im linken Teilbaum sind kleiner als Wert des Knotens Die Werte im rechten Teilbaum sind größer (gleich) dem Wert des Knotens Doppelte Werte können zugelassen sein oder auch nicht (hier: keine doppelten Werte) Beispiel: 12 8 34 10 GP1 – WS 08/09 23 46 615 Universität Paderborn Prof. Dr. Heike Wehrheim Aufbau Sortierter Baum (über Zahlen) public class SortedBinTree { int value; SortedBinTree left; SortedBinTree right; SortedBinTree (int v) { value = v; left = null; right = null; } GP1 – WS 08/09 Warum kein Konstruktor, der Werte für left und right als Parameter bekommt? 616 Aufbau II Universität Paderborn Prof. Dr. Heike Wehrheim Aufbau muss einen sortierten Baum ergeben, Benutzung einer Methode insert Was ist zu beachten? Der einzufügende Werte kann schon drin sein -> dann nicht nochmal einfügen Einfügen startet bei Wurzelknoten: einzufügender Wert kleiner als Wert des Wurzelknotens -> links einfügen einzufügender Wert größer als Wert des Wurzelknotens -> rechts einfügen Achtung: prüfen, ob linker bzw. rechter Teilbaum überhaupt existiert, sonst nur neuen Knoten mit einzufügendem Wert erzeugen und entsprechend einhängen Einfügen in Teilbaum funktioniert wie Einfügen an Wurzelknoten -> rekursive Methode GP1 – WS 08/09 617 insert Universität Paderborn Prof. Dr. Heike Wehrheim void insert (int v) { if (v == value) System.out.println("Value already existed."); else if (v < value) { links einfügen if (left!= null) left.insert(v); Links neuen else Knoten left = new SortedBinTree(v); erzeugen } else { if (right!= null) rechts einfügen right.insert(v); else right = new SortedBinTree(v); } } Rechts neuen Knoten SortedBinTree.java erzeugen GP1 – WS 08/09 618 Fragen Universität Paderborn Prof. Dr. Heike Wehrheim Gegeben class BinTree { private BinTree left, right; private int value; BinTree(BinTree l, int v, BinTree r) { left = l; value = v; right = r; } } Welchen Baumdurchlauf druckt die folgende Objektmethode xxFix() von BinTree? void xxFix() { if (right != null) right.xxFix(); System.out.print(value); if (left!= null) left.xxFix(); } (a) Prefix (b) Infix (c) Postfix (d) Prefix rückwärts (e) Infix rückwärts (f) Postfix rückwärts GP1 – WS 08/09 619 Fragen II Universität Paderborn Prof. Dr. Heike Wehrheim Bestimmen Sie zu dem Infix-Ausdruck (5+7)*(44-(8465/35)) den dazugehörigen Postfix-Ausdruck. Malen Sie sich dazu zuerst den Baum auf, den der Infix-Ausdruck repräsentiert. GP1 – WS 08/09 620 Generische Klassen Universität Paderborn Prof. Dr. Heike Wehrheim Generische Klassen sind Schablonen für konkrete Klassen, bei denen Komponententypen noch offen gelassen werden und durch Typvariablen dargestellt werden. Eine konkrete Klasse entsteht durch Instantiierung der Typvariablen mit konkreten Typen, den Typargumenten. Kennen wir im Prinzip schon: HashMap<K,V> : Typen von Schlüssel und Werten werden durch Typvariablen K und V repräsentiert HashMap<Integer,String> : eine konkrete Klasse mit Typargument Integer für K und String für V GP1 – WS 08/09 621 Einführungsbeispiel Universität Paderborn Prof. Dr. Heike Wehrheim Klasse zum Abspeichern von Paaren von Integern class IntegerPaar { private Integer erster; private Integer zweiter; IntegerPaar(Integer e, Integer z) { erster = e; zweiter = z; } public void setErster (Integer e) { erster = e; } public Integer getErster () { return erster; } … // gleiches für zweiter } GP1 – WS 08/09 622 Und nun … Universität Paderborn Prof. Dr. Heike Wehrheim Klasse zum Abspeichern von Paaren von Strings class StringPaar { private String erster; private String zweiter; StringPaar(String e, String z) { erster = e; zweiter = z; } public void setErster (String e) { erster = e; } public String getErster () { return erster; }… // gleiches für zweiter } GP1 – WS 08/09 623 Und nun … Universität Paderborn Prof. Dr. Heike Wehrheim Klassen zum Abspeichern von Paaren von Personen, Rechtecken, Hunden, … Jedes Mal: Klasse kopieren, Typen der Objektvariablen ändern (und entsprechend Parameter der Methoden) Möchte man eine Methode von …Paar ändern/hinzufügen, muß man sie 1,2,3, … -mal ändern/hinzufügen Stattdessen sinnvoller: eine Klassenschablone angeben, darin konkreten Typ durch Typvariable ersetzen bei Erzeugung von Objekten: konkreten Typ als Typargument angeben; wird für Typvariable eingesetzt GP1 – WS 08/09 624 Am Beispiel Paar Universität Paderborn Prof. Dr. Heike Wehrheim class Paar<T> { private T erster; private T zweiter; Paar(T e, T z) { erster = e; zweiter = z; } public void setErster (T e) { erster = e; } public T getErster () { return erster; }… /gleiches für zweiter } GP1 – WS 08/09 625 Dann geht folgendes … Universität Paderborn Prof. Dr. Heike Wehrheim Paar<Integer> pi = new Paar<Integer>(new Integer(4), new Integer(32)); Paar<String> ps = new Paar<String>("bar","foo"); Paar<Student> pst = new Paar<Student>(… , …); usw. Und folgendes ist möglich: Integer i = pi.getErster(); ps.setErster(“bla”); GP1 – WS 08/09 626 Warum nicht Object? Universität Paderborn Prof. Dr. Heike Wehrheim Was wäre, wenn wir Paar mit Object als Typ definiert hätten? class ObjectPaar { private Object erster; private Object zweiter; ObjectPaar(Object e, Object z) { erster = e; zweiter = z; } … } Möglich wäre nun ObjectPaar op1 = new ObjectPaar(“hallo“,“bla“); Paar<T> erlaubt das nicht! aber auch ObjectPaar op2 = new ObjectPaar(“foo”, new Integer(42)); GP1 – WS 08/09 627 Ausserdem … Universität Paderborn Prof. Dr. Heike Wehrheim Zum Zugreifen nun Typecast nötig String s = (String) op1.getErster(); Das kann Fehler liefern: Integer s = (Integer) op2.getErster(); (ClassCastException) Verwendung von generischen Datenstrukturen: Compiler kann zur Übersetzungszeit nicht passende Typen überprüfen Typsicherheit erhöht GP1 – WS 08/09 628 Universität Paderborn Prof. Dr. Heike Wehrheim Mehrere Typvariablen Brauchen eine Paar-Klasse mit unterschiedlichen Elementen: class Paar2<T,U> { private T erster; private U zweiter; Paar2(T e, U z) { erster = e; zweiter = z; } public void setErster (T e) { erster = e; } public void setZweiter (U z) { zweiter = z; } public T getErster () { return erster; } public U getZweiter() { return zweiter; } } GP1 – WS 08/09 629 Und dann … Universität Paderborn Prof. Dr. Heike Wehrheim Konkrete Klasse: Paar2<Integer, String> p1 = new Paar2<Integer,String>(2,“foo“); Integer i = p1.getErster(); // ok Integer j = p1.getZweiter(); // Fehler bei Übersetzung Auch möglich: Paar2<Integer,Paar2<Boolean,String>> … (Paar2 taucht selber als Typargument auf) GP1 – WS 08/09 630 Anwendungsbeispiel Universität Paderborn Prof. Dr. Heike Wehrheim Generische Klasse für Binärbäume: class BinTree<T> { private BinTree<T> left; private BinTree<T> right; private T value; BinTree(T v, BinTree<T> l, BinTree<T> r) { value = v; left = l; right = r; } } Wie sehen nun die Methoden setLeft, getLeft, setValue und getValue aus? GP1 – WS 08/09 631