Fallstudie: Binäre Suchbäume Andreas Forster Bernhard Egger Alexander Stiemer Prof. Thomas Vetter University of Basel, Department of Mathematics and Computer Science Friday, 7th of December 2012 Grundlegendes Vorgehen 1. Verstehe das Problem mit Stift und Papier. 2. Unterteile das Problem in einfachere Teilprobleme. (Führe diesen Schritt durch bis jedes Teilproblem lösbar ist.) 3. Überlege dir eine Lösung für die einzelnen Teilprobleme mit Stift und Papier. 4. Beginne erst mit implementieren, wenn du weist was du programmieren musst! Sortieren Sortiere ein Array Als Ganzes nicht so einfach zu lösen. ⇒ Löse Teilprobleme! Lösungsansätze I Nimm zwei sortierte Arrays und füge sie sortiert zusammen. Beginne mit Arrays der Länge 1. ( Merge Sort ) I Sortiere zwei Nachbarn. Widerhole dies solange bis alle Nachbarn sortiert sind. ( Bubble Sort) Computergrafik Rendern eines 3D Objektes Als Ganzes nicht so einfach zu lösen. ⇒ Löse Teilprobleme! Lösungsansatz I Zerlege das Objekt in Dreiecke. I Bestimme für jedes Dreieck ob es sichtbar ist. I Bestimme für jedes Dreieck welches die Eckpunkte auf dem Bildschirm sind. I Bestimme für jeden Pixel im Dreieck welche Farbe er bekommt. Beispiel empty root empty root 0000 0000 L—R L—R 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel 4000 Basel empty root 0000 0000 L—R L—R 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel empty root 4000 Basel 0000 0000 L—R L—R 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel 3000 Bern 4000 Basel 0000 0000 L—R L—R 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel empty root 4000 L—R 3000 4000 Bern Basel 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel 2500 Biel 4000 L—R 3000 4000 Bern Basel 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel empty root 4000 L—R 2500 Biel 3000 4000 Bern Basel 0000 0000 0000 0000 L—R L—R L—R L—R Beispiel empty root 4000 L—R 3000 4000 L—R Basel 2500 3000 0000 0000 Biel Bern L—R L—R Beispiel 8500 Zug 4000 L—R 3000 4000 L—R Basel 2500 3000 0000 0000 Biel Bern L—R L—R Beispiel empty root 4000 L—R 8500 Zug 3000 4000 L—R Basel 2500 3000 0000 0000 Biel Bern L—R L—R Beispiel empty root 4000 L—R 3000 8500 L—R L—R 2500 3000 4000 8500 Biel Bern Basel Zug Binäre Suchbäume Struktur Beschreibung Funktionen I Knoten I I Knoten haben einen Schlüssel (Postleitzahl) Für einen Schlüssel einen Wert zurück geben. I Ein neues Schlüssel-Wert-Paar einfügen. I Ausgabe auf die Konsole. I Grafische Ausgabe. I Maximal ein Wurzelknoten I Innere- und Blattknoten I Innereknoten haben genau zwei Kindknoten I Blattknoten speichern einen Wert (Ort) Struktur : Suchbaum ”Definieren Sie dazu ... (a) eine Suchbaum-Klasse, die den eigentlichen Suchbaum enthält. Diese Klasse hat Funktionen zum Einfügen und Suchen und enthält den Wurzelknoten des Suchbaumes.” Listing 1: struktur/Suchbaum.java 1 public class Suchbaum { 2 Knoten wurzel; 3 4 public void einfuegen ( int plz, String ort ) { } 5 6 7 public String suchen ( int plz ) { return ""; } 8 9 10 11 12 } Struktur : Knoten ”Definieren Sie dazu ... (b) eine Knoten-Klasse, die als Basisklasse für die inneren und die Blatt-Knoten dient. Diese Klasse enthält den Schlüssel.” Listing 2: struktur/Knoten.java 1 public abstract class Knoten { 2 int plz; 3 4 public Knoten ( int plz ) { this.plz = plz; } 5 6 7 8 9 }; Kompiler-Check! Kann dieses Minimalbeispiel schon kompiliert werden? JA! Struktur : InnererKnoten ”(c) eine InnererKnoten-Klasse, die von der Knoten Klasse abgeleitet ist, und die zusätzlich zu dem Schlüssel noch zwei Kind-Knoten enthält.” Listing 3: struktur/InnererKnoten.java 1 public class InnererKnoten extends Knoten { 2 public Knoten kind_links; public Knoten kind_rechts; 3 4 5 6 }; Struktur : BlattKnoten ”(d) eine BlattKnoten-Klasse, die von der Knoten-Klasse abgeleitet ist, und die zusätzlich den zu dem Schlüssel gehörigen Wert enthält.” Listing 4: struktur/BlattKnoten.java 1 public class BlattKnoten extends Knoten { 2 String ort; 3 4 5 }; Struktur : Test ”(a) eine Test-Klasse welche Schlüssel-Werte-Paare einfügt, nach Schlüsseln sucht und den dazu gespeicherten Wert ausgibt.” Listing 5: struktur/Test.java 1 class Test { 2 public static void main ( String [] args ) { 3 4 Suchbaum baum = new Suchbaum(); 5 6 baum.einfuegen(4000,"Basel"); 7 8 System.out.print("Suche PLZ 4000 ... "); System.out.println("Ort: \""+baum.suchen(4000)+" \""); 9 10 }; 11 12 }; toString : Test ”(b) für jede der Knoten-, Blatt- und Suchbaum-Klassen eine toString Methode so dass der Baum auf die Konsole ausgegeben werden kann. Verwenden Sie diese in der Test-Klasse um den Baum auszugeben. Versuchen Sie mit der Ausgabe die Struktur des Baumes zu veranschaulichen.” Listing 6: toString/Test.java 1 class Test { 2 public static void main ( String [] args ) { ... System.out.println(baum); ... }; 3 4 5 6 7 8 }; toString : Suchbaum ”(b) für jede der Knoten-, Blatt- und Suchbaum-Klassen eine toString Methode ...” Listing 7: toString/Suchbaum.java 1 2 public class Suchbaum { Knoten wurzel; 3 ... 4 5 @Override public String toString () { return "BAUM = " + wurzel; } 6 7 8 9 10 } Was passiert wenn die Wurzel noch null ist? Es wird “null” als String ausgegeben. toString : Knoten ”(b) für jede der Knoten-, Blatt- und Suchbaum-Klassen eine toString Methode ...” Listing 8: toString/Knoten.java 1 2 public abstract class Knoten { int plz; 3 ... 4 5 @Override public String toString ( ) { return ""+plz; } 6 7 8 }; Die beiden anderen Knotenklassen erben diese Methode. Reicht dies schon um alle Postleitzahlen auszugeben? Nein! So würde nur die Wurzel ausgegeben. Wir müssen den Baum jedoch rekursiv ausgeben. toString : InnererKnoten ”(b) für jede der Knoten-, Blatt- und Suchbaum-Klassen eine toString Methode ...” Listing 9: toString/InnererKnoten.java 1 2 3 public class InnererKnoten extends Knoten { public Knoten kind_links; public Knoten kind_rechts; 4 ... 5 6 @Override public String toString ( ) { return "[" + plz + ": " + kind_links + " " + kind_rechts + "]"; } 7 8 9 10 11 }; toString : BlattKnoten ”(b) für jede der Knoten-, Blatt- und Suchbaum-Klassen eine toString Methode ...” Listing 10: toString/BlattKnoten.java 1 2 public class BlattKnoten extends Knoten { String ort; 3 ... 4 5 @Override public String toString ( ) { return "("+plz+"/"+ort+")"; } 6 7 8 9 10 }; Einfügen : Suchbaum Was müssen wir beachten wenn wir ein neues Paar einfügen wollen? Behandle alle möglichen Zustände des Suchbaumes separat ( Eine Instanz ist durch die Werte ihrer Membervariablen (wurzel) definiert.): 1. Der Suchbaum ist leer. 2. Der Suchbaum besitzt schon eine Wurzel. Listing 11: einfuegen/Suchbaum.java 1 2 3 4 5 6 7 public void einfuegen ( int plz, String ort ) { if (wurzel == null) { wurzel = new BlattKnoten(plz,ort); } else { wurzel = wurzel.einfuegen(plz,ort); } } Einfügen : BlattKnoten 1. (Wir sind in einem Blatt vom Baum) 2. Erstelle für das neue PLZ/Ort-Paar einen BlattKnoten. 3. Ersetze das Blatt im Baum mit einem InnererKnoten und der grösseren PLZ. 4. Füge dem InnererKnoten die Blätter hinzu, links das Blatt mit der kleineren PLZ. Listing 12: einfuegen/BlattKnoten.java 1 2 3 4 5 6 7 8 9 10 11 @Override public Knoten einfuegen ( int plz, String ort ) { Knoten k; BlattKnoten blatt = new BlattKnoten(plz,ort); if ( this.plz < plz ) { k= new InnererKnoten(plz, this, blatt); } else { k = new InnererKnoten(this.plz, blatt, this); } return k; } Einfügen : InnererKnoten 1. Reiche das PLZ/Ort-Paar an den richtigen Kindknoten weiter. 2. Füge das neue Kind anstelle des alten Kindes ein. 3. InnererKnoten bleibt Kind seines Elternknotens, deshalb gibt er sich selbst zurück. Listing 13: einfuegen/InnererKnoten.java 1 2 3 4 5 6 7 8 9 @Override public Knoten einfuegen ( int plz, String ort ) { if ( plz < this.plz ) { kind_links = kind_links.einfuegen(plz,ort); } else { kind_rechts = kind_rechts.einfuegen(plz,ort); } return this; } Einfügen : Test Listing 14: einfuegen/Test.java 1 class Test { 2 public static void main ( String [] args ) { 3 4 ... 5 6 // fuege Staedte ein baum.einfuegen(4000,"Basel"); baum.einfuegen(3000,"Bern"); baum.einfuegen(2500,"Biel"); baum.einfuegen(8500,"Zug"); System.out.println(baum); 7 8 9 10 11 12 }; 13 14 }; Suchen : Suchbaum Listing 15: suchen/Suchbaum.java 1 2 public class Suchbaum { Knoten wurzel; 3 ... 4 5 public String suchen ( int plz ) { if ( wurzel == null ) { return ""; } else { return wurzel.suchen(plz); } } 6 7 8 9 10 11 12 13 } Suchen : Knoten Listing 16: suchen/Knoten.java 1 2 public abstract class Knoten { int plz; 3 ... 4 5 public abstract String suchen ( int plz ) ; 6 7 8 } Suchen : InnererKnoten Listing 17: suchen/InnererKnoten.java 1 2 3 public class InnererKnoten extends Knoten { Knoten kind_links; Knoten kind_rechts; 4 ... 5 6 @Override public String suchen ( int plz ) { if ( plz < this.plz ) { return kind_links.suchen(plz); } else { return kind_rechts.suchen(plz); } } 7 8 9 10 11 12 13 14 15 16 } Suchen : BlattKnoten Listing 18: suchen/BlattKnoten.java 1 2 public class BlattKnoten extends Knoten { String ort; 3 ... 4 5 @Override public String suchen ( int plz ) { if ( plz == this.plz ) { return ort; } else { return ""; } } 6 7 8 9 10 11 12 13 14 } Suchen : Test Listing 19: suchen/Test.java 1 class Test { 2 public static void main ( String [] args ) { Suchbaum baum = new Suchbaum(); 3 4 5 ... 6 7 // suche Stadt System.out.print("Suche PLZ 4000 ... "); System.out.println("Ort: \""+baum.suchen(4000)+" \""); 8 9 10 } 11 12 } Zeichnen Die Klasse BufferedImageWindow lässt sich ähnlich der ImageWindow Klasse verwenden. Nutzen Sie die Befehle ... um eine rekursive Funktion in den Knoten Klassen zu schreiben welche den Baum zeichnet. Dabei soll in den abgeleiteten Klassen die Methode jeweils überschrieben werden. Implementieren Sie in der Suchbaum-Klasse eine Methode welche den Baum mit Hilfe der rekursiven Funktion zeichnet. Rufen Sie dann diese Methode aus Ihrer Test-Klasse auf. Das folgende Bild zeigt eine mögliche Ausgabe für einen Baum. Zeichnen : Test Listing 20: zeichnen/Test.java 1 class Test { 2 public static void main ( String [] args ) { 3 4 ... 5 6 baum.draw(); 7 8 } 9 10 }; Zeichnen : Suchbaum Listing 21: zeichnen/Suchbaum.java 1 import ch.unibas.informatik.cs101.*; 2 3 4 5 6 7 8 9 public void draw() { BufferedImageWindow biw = new BufferedImageWindow (1000,1000); biw.setColor(0,0,255); wurzel.draw(biw,500,20,1000,10); biw.openWindow(); biw.redraw(); } Zeichnen : Knoten Listing 22: zeichnen/Knoten.java 1 import ch.unibas.informatik.cs101.*; 2 3 public abstract class Knoten { 4 ... 5 6 public abstract void draw ( BufferedImageWindow biw, int x, int y, int w, int s ); 7 8 9 }; Zeichnen : InnererKnoten Listing 23: zeichnen/InnererKnoten.java 1 import ch.unibas.informatik.cs101.*; 2 3 4 5 6 7 8 9 10 11 @Override public void draw ( BufferedImageWindow biw, int x, int y, int w, int s ) { biw.setFontSize(s*2); biw.drawString(""+plz,x,y); biw.drawLine( x , y , x-w/4 , y+5*s ); biw.drawLine( x , y , x+w/4 , y+5*s ); kind_links.draw(biw,x-w/4,y+5*s,w/2,s-1); kind_rechts.draw(biw,x+w/4,y+5*s,w/2,s-1); } Zeichnen : BlattKnoten Listing 24: zeichnen/BlattKnoten.java 1 import ch.unibas.informatik.cs101.*; 2 3 ... 4 5 6 7 8 9 @Override public void draw( BufferedImageWindow biw, int x, int y, int w, int s) { biw.setFontSize(s*2); biw.drawString(""+plz+"|"+ort,x,y); } Weiteres I Was passiert wenn eine schon vorhandene Postleitzahl eingefügt werden soll? ( Exception / ignorieren / überschreiben ) I Was passiert wenn eine nicht vorhandene Postleitzahl gesucht wird? (”” / Exception / null ) I ... Alternativen - Eine einzige Knoten-Klasse Listing 25: alternativen/einknoten/Struktur.java 1 public class Knoten { 2 int plz; String ort; Knoten links; Knoten rechts; 3 4 5 6 7 public String toString( ) { return "[("+plz+"/"+ort+")"+links+rechts+"]"; } 8 9 10 11 12 } Listing 26: alternativen/einknoten/Konstruktoren.java 1 public class Knoten { 2 3 4 // Innerer-Knoten public Knoten( int plz ) { Alternativen - Eine einzige Knoten-Klasse Listing 28: alternativen/einknoten/einfuegen1.java 1 2 3 4 5 6 7 8 public void einfuegen ( int plz, String ort ) { if ( wurzel == null ) { // Wir haben noch keinen Knoten im Baum wurzel = new Knoten(plz, ort); } else { ... } } Listing 29: alternativen/einknoten/einfuegen2.java 1 2 3 ... Knoten parent = null; Knoten blatt = wurzel; 4 5 6 7 while ( blatt.links()!=null && blatt.rechts()!=null ) { // Suche das Blatt wo der neue Knoten angehaengt wird Alternativen - Mehrere Knotenklassen, iterativ Listing 32: alternativen/mehrknoten/einfuegen1.java 1 2 3 4 5 6 7 8 public void einfuegen (int plz, String ort) { // leerer Baum if (wurzel == null) { wurzel = new BlattKnoten(plz, ort); return; } ... } Listing 33: alternativen/mehrknoten/einfuegen2.java 1 2 3 4 5 6 7 8 public void einfuegen (int plz, String ort) { ... // suche den Blattknoten Knoten eltern = null; Knoten kind = wurzel; while (kind instanceof InnererKnoten) { eltern = kind; if (plz < eltern.plz()) {