Fallstudie: Binäre Suchbäume - Universität Basel | Informatik

Werbung
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()) {
Herunterladen