Allgemeiner Binärbaum

Werbung
3
Binärbäume
Vorüberlegungen 2
Prozedural, funktional oder
objektorientiert? 2
Der leere Baum 3
Beispiele und Terminologie 4
Allgemeiner Binärbaum 6
Minimaler Befehlssatz 6
Implementierung 7
Generierung von Binärbäumen 8
Elementare Operationen 9
Durchlauftechniken 11
Erweiterte Baumoperationen 15
Die zusätzlichen Baumoperationen im
Einzelnen 16
Interaktive Baumerweiterung 16
Der Dialog beim Tierraten und die
Erweiterung der Datenbasis. 17
Konstruktion des Huffmanbaumes 17
Aufbau des Strukturbaums 20
Syntaxdiagramme 21
Codeerzeugung und Syntaxprüfung 22
2
Kap. 3
Binärbäume
Vorüberlegungen
Prozedural, funktional oder objektorientiert?
In der Veröffentlichung der Methoden zu Binärbäumen zum Zentralabitur in NRW findet man
sowohl Funktionen, die selbst Binärbäume zurück liefern als auch Prozeduren, die in einem
gegebenen Baum Veränderungen durchführen.
Funktionale Sicht
Die Sichtweise, das jede Operation auf dem Baum einen neuen Baum erzeugt, ist eine funktionale. Sie wird in den Baumkonstruktoren angewendet. Dieser Sicht folgende Änderungen
von Inhalten führen jeweils zu neuen Bäumen oder liefern Bäume oder Inhalte. Dazu gehören
die Konstruktoren mit einem Inhalt und mit einem Inhalt und zwei Teilbäumen und die
Abfrage des Inhaltes in der Baumwurzel.
Prozedurale Sicht
Die übrigen Vorschläge von Operatoren im Zentralabitur folgen einer prozeduralen Sichtweise. Dazu gehört beispielsweise das Anhängen eines linken Teilbaumes an die Wurzel. Das
Ergebnis ist kein neuer, sondern eine veränderter Baum.
Beide zeigen eine Sicht auf den Baum von außen:

Ein Binärbaum

ist leer
 oder ein Element mit einer Verknüpfung mit zwei Binärbäumen
Die Verknüpfungen und Operationen werden global gesetzt und gesteuert: Man verbindet von
außen zwei Teilbäume zu einem neuen Baum oder regelt von außen das Einfügen.
Objektorientierte Sicht
Streng dem objektorientierten Paradigma folgend, wird der Baum nicht mehr als ganzes gesehen, sondern als einzelner Knoten, der über genügend Attribute und Methoden verfügt, um
Aufträge selbst zu organisieren oder sie an die Nachbarknoten weiter zu leiten. Aus dieser
internen Sicht handelt es sich um einen Knoten mit Inhalt und einer Komposition von bis zu
zwei Nachbarknoten. Um beispielsweise ein Element, geht der Auftrag an den ersten Knoten.
Das Objekt prüft selbst lokal, ob es zuständig ist oder ob ein Nachbarknoten benachrichtigt
werden muss, um das Einfügen zu gestalten. Informationen zu diesem Ansatz bietet die
Begleit-CD im Ordner binBaumOOP.
Die Lösungen zu den Aufgaben sind in der Klasse BinTree zu ergänzen.
Methoden und Techniken
3
Vorgehensweise
Die Einführung beschränkt sich auf die notwendigsten (meist funktionalen) Methoden. Sie
reichen aus, um beliebige Binärbäume zu konstruieren und zu manipulieren. Die prozeduralen
Methoden vereinfachen manche Operationen und werden an entsprechenden Stellen eingeführt und implementiert. Die objektorientierte Sicht findet bei geordneten Bäumen Beachtung.
Der leere Baum
Wann ist ein Baum leer? Bei N.Wirth oder R. Sedgewick beispielsweise ist der leere Baum
ein so genannter Nullpointer, also gleich null oder gleich nil. Sein Symbol ist ein Anker
─┤. Die Abiturvorgaben favorisieren stattdessen ein erzeugtes Objekt mit leerem Inhalt und
zwei Ankern. Dieser Ansatz führt möglicherweise zu Irritationen, wenn Bäume ihre Informationen nur in den Blättern (s. u.) haben und die übrigen Verzweigung nur Strukturkriterien
erfüllen und im Inneren zu leeren Knoten führen. Ein Beispiel ist der nachfolgend vorgestellte
Huffmancode. Gemäß den Abiturvorgaben kann ein Baum nur leer sein, wenn er erzeugt ist.
So wird er mit new BinTree() oder create(BinTree) implementiert, aber mit dem
Ankersymbol dargestellt.
4
Kap. 3
Binärbäume
Beispiele und Terminologie
Strukturbaum
Alternativen:
(12 / 4) + (2 * 3)
UPN: 12 4 / 2 3 * +
Umgekehrt polnische Notation
Suchbaum
Alle Werte im linken Teilbaum der Wurzel
sind kleiner als die Wurzel, alle Werte rechts
davon sind größer. Die Knoten mit den
Werten 4 und 78 bilden ihrerseits wieder
Wurzeln von Binärbäumen, die Teilbäume
des gesamten Baumes sind. Jeder Teilbaum
unterliegt der gleichen Ordnung.
Säugetier
nein
ja
Hai
Haustier
nein
ja
Wolf
0
0
Hund
1
1
0
B
D
0
C
1
R
1
A
Frage-Antwort-Baum
Der Binärbaum zeigt eine kleine Wissensbasis, die mit Ja/Nein-Antworten ein Problem
eingrenzt. Interessant für Diagnosesysteme
wird das Verfahren, wenn die Möglichkeit
der Wissenserweiterung auf Grund von
Fragen an den Benutzer besteht.
Kodierungsbaum
D. Huffman schlägt 1952 eine Codierung zur
Kompression von Texten vor, die sich an der
Häufigkeit der Buchstaben orientieren. Je
häufiger der Buchstabe vorkommt, umso
kürzer ist sein Code. Im Beispiel hat A den
Code 11. Die Codierung von DA lautet:
1011
Die Bilder zeigen unterschiedliche Binärbäume. Bis auf die Endknoten, Blätter genannt, ist
jeder Knoten mit ein bis zwei Nachfolgern (links und rechts) verknüpft. Der obere Knoten
heißt Wurzel. Die Verbindungslinien wirken wie Zweige in einem nach unten wachsenden
Baum. Sie werden in der Notation der Graphen (ein Binärbaum ist ein Graph) als Kanten
bezeichnet. Die maximale Anzahl von Kanten auf dem Weg von der Wurzel zu einem Blatt
heißt Tiefe (engl. depth) oder auch Höhe des Baumes.
Methoden und Techniken
5
Übung 1.1
Zu jedem der oben beschriebenen Bäume bestimme man die Tiefe, die Blätter und zeichne
den rechten Teilbaum und beschreibe die Bedeutung des Teilbaumes. Im Strukturbaum
beispielsweise stellt der linke Teilbaum beispielsweise den Term 12 /4 dar.
Strukturbaum
Suchbaum
Frage-AntwortBaum
Code-Baum
(Huffman)
Tiefe
Blätter
Zeichnung
des rechten
Teilbaumes
Interpretation
des rechten
Teilbaumes
Übung 1.2
a) Strukturbaum
Man zeichne den Strukturbaum zum Ausdruck 2*(4+7)-9 und gebe den Term in UPN
aus. Um den Ausdruck in UPN aus dem Baum zu erschließen, lese man zunächst von der
Wurzel ausgehend den linken Teilbaum aus, dann den rechten Teilbaum und schreibe
schließlich das Wurzelelement auf. Die linken und rechten Teilbäume der Blätter sind leer.
b) Suchbaum
Wie viele Vergleiche mit Knoteninhalten sind notwendig, um herauszufinden, dass die Zahlen
3, 8 und 72 nicht im Binärbaum enthalten sind?
c) Frage-Antwort-Baum
Das gesuchte Tier ist eine Katze. Erweitern Sie die Wissensbasis!
d) Huffmancode
1.Man interpretiere den Code 1100011110101110110001111 (aus Sedgewick: Algorithms)
Verwenden Sie den Huffmancode aus der obigen Tabelle.
2. Verschlüsseln Sie mit Hilfe des abgebildeten
1
0
E
Codes das Wort „SEEMANN“. Wieso eignet sich
N
der abgebildete Code für das Wort?
M
A
S
6
Kap. 3
Binärbäume
Allgemeiner Binärbaum
Minimaler Befehlssatz
BinTree (bzw. TBinTree)
-value: Object
-left: BinTree
-right: BinTree
<<create>> BinTree() // leerer Baum
<<create>> BinTree(root: Object);
<<create>> BinTree(root: Object, left: BinTree, right: BinTree)
+getLeftTree():BinTree
+getRightTree():BinTree
+public getRootItem(): Object
+isEmpty(): boolean
Erläuterungen
BinTree und TBinTree sowie Object und TObject sind jeweils Synonyme.
Weitere Informationen:
http://www.learn-line.nrw.de/angebote/abitur-gost/fach.php?fach=15
Konstruktor
nachher
BinTree()
Ein leerer Baum existiert
Kommentar: Auf Grund dieses Konstruktors entsteht ein leerer Baum, der sich von null
oder nil unterscheidet. Er enthält ein leeres Element.
Konstruktor
nachher
BinTree (Object v) bzw. create (v: TObject)
Der Binärbaum existiert und hat einen Wurzelknoten mit dem Inhalt v und
zwei leeren Teilbäumen (new Bintree() oder BinTree.create).
Konstruktor
BinTree (Object v, BinTree left, BinTree right)
create (Object v, BinTree left, BinTree right)
nachher
Der Binärbaum existiert und hat einen Wurzelknoten mit dem Inhalt v, dem
linken Teilbaum left und dem rechten Teilbaum right.
Anfrage
getRootItem(): Object
vorher
nachher
Der Binärbaum existiert und ist nicht leer.
Diese Anfrage liefert den Inhalt des Wurzelknotens des Binärbaums.
Anfrage
getLeftTree(): BinTree
vorher
nachher
Der Binärbaum existiert und ist nicht leer
Diese Anfrage liefert den linken Teilbaum des erzeugten Binärbaums. Der
Binärbaum ist unverändert.
Methoden und Techniken
Anfrage
getRightTree(): BinTree
vorher
nachher
Der Binärbaum ist nicht leer
Diese Anfrage liefert den rechten Teilbaum des erzeugten Binärbaums. Der
Binärbaum ist unverändert.
Anfrage
isEmpty(): boolean
nachher
Diese Anfrage liefert den Wahrheitswert true, wenn der Binärbaum leer ist,
sonst liefert sie den Wert false.
Kommentar
Die Anfrage bezieht sich nicht auf leere Zeiger wie null oder nil, sondern
auf einen erzeugten Baum mit leerem Element und allen Baummethoden.
Implementierung
Java
Delphi
public class BinTree{
unit uBinTree;
private Object value;
interface
private BinTree left;
type TInhalt = TObject;
private BinTree right;
type BinTree = class
private
// Konstruktor 0, leer
value : TInhalt;
public BinTree() {
left
this(null, null, null);
}
: BinTree;
right : BinTree;
public
constructor create;
// Konstruktor 1, Wurzel
constructor create(v: TInhalt);
public BinTree(Object v) {
constructor create(v: TInhalt,
this(v, null, null);
}
li, re BinTree);
function getRootItem: TInhalt;
function getLeftTree: BinTree;
//Konstruktor 3,Wurzel+Teilbäume
function getRightTree: BinTree;
public BinTree(Object v, BinTree li,
BinTree re){
implementation
value = v;
constructor create;
left = li;
begin
right = re;
value:=nil; left:=nil; right:=nil;
}
end;
public Object getRootItem(){
constructor create(v: TInhalt);
return value;
}
begin
value:=v; left:=nil; right:=nil;
end;
7
8
Kap. 3
Binärbäume
public BinTree getLeftTree(){
return left;
}
constructor create(v: TInhalt,
li, re BinTree);
begin
value:=v; left:=li; right:=re;
public BinTree getRightTree(){
end;
return right;
function getRootItem: TInhalt;
}
begin result := value; end;
public boolean isEmpty(){
return value == null;
function getLeftTree: BinTree;
begin result := left; end;
}
function getRightTree: BinTree;
} // BinBaumEnde
begin getRightTree := right; end;
function isEmpty :boolean;
begin result := value; end;
Generierung von Binärbäumen
Dass die vorgestellten Konstruktoren ausreichen, um die Binärbäume der beschriebenen Art
zu erzeugen, wird am Beispiel des Struktur- und des Suchbaumes vorgestellt. Die übrigen
Generierungen gelten als Übungen. Zur Überprüfung liegt auf der Begleit-CD im Ordner
Aufgaben zu diesem Kapitel ein Programm vor, das die Ausgabe des erzeugten Binärbaumes per Mausklick ermöglicht, falls er konstruiert ist. Die implementierte Methode
zeige() veranlasst die Ausgabe des Binärbaums b, dessen Deklaration bereits erfolgt ist.
Um den Algorithmus zur Darstellung des Binärbaumes zu verstehen, muss man sich etwas
intensiver mit der Programmiersprache und deren Grafikfähigkeiten beschäftigen, als dies in
diesem Kurs vorgesehen ist. Den Algorithmus findet man in der Klasse BBTools, die im
Folgenden durch weitere hilfreiche Methoden ergänzt werden soll. Nehmen Sie die Implementierungen im Verzeichnis operationenGUI bei der Klasse BBAufgabenGUI vor.
Dort finden Sie die jeweiligen Methodensignaturen zum Ausfüllen. Ein- und Ausgabeüberlegungen erübrigen sich.
Der Strukturbaum zu (12 / 4) + (2 * 3)
In Java
BinTree b;
BinTree b1 = new BinTree("/",new BinTree("12"),new BinTree("4"));
BinTree b2 = new BinTree("*",new BinTree("2"),new BinTree("3"));
b = new BinTree("+",b1,b2);
In Delphi
var b, b1, b2: BinTree;
b1 := BinTree.create('/',BinTree.create('12'),BinTree.create('4'));
b2 := BinTree.create('*',BinTree.create('2'),BinTree.create('3'));
b := BinTree.create('+',b1,b2);
Methoden und Techniken
9
Der Suchbaum (bis zur Tiefe 2)
Hier existieren Knoten mit leeren Teilbäumen, die mit new BinTree() oder
BinTree.create() zu erzeugen sind.
In Java
BinTree b;
BinTree b1 = new BinTree("4",new BinTree(),new BinTree("34"));
BinTree b2 = new BinTree("78",new BinTree("62"),new BinTree());
b = new BinTree("59",b1,b2);
In Delphi
var b, b1, b2: BinTree;
b1 := BinTree.create ('4',BinTree.create,BinTree.create('34'));
b2 := BinTree.create ('78',BinTree.create('62'),BinTree.create);
b := BinTree.create ('59',b1,b2);
Übung 1.3
Geben Sie Konstruktoren zum Frage/Antwortbaum und zum Codierungsbaum nach Huffman
an.
0
1
Säugetier
nein
ja
0
1
0
1
Hai
Haustier
nein
Wolf
D
B
0
ja
Hund
C
A
1
R
Die leeren Knoten beim Huffmancode sind mit Leerzeichen zu bewerten.
Elementare Operationen
Der Strukturbaum hat gezeigt, dass er je nach Leseart anders interpretiert werden kann. Es
kommt darauf an, ob die Wurzel vor den Teilbäumen ausgegeben wird oder später. Durchlauftechniken dieser Art und andere Fragestellungen, zum Beispiel nach der Baumtiefe, sollen
im Folgenden in einer (statischen) Werkzeugdatei (BBTools) zusammengestellt und implementiert werden. Damit wird der Boden für komplexere Anwendungen von Binärbäumen
bereitet.
Berechnung der Baumtiefe: tiefe (BinTree b): integer
Der Aufruf erfolgt mit einem Baumexemplar meinBaum
in Java:
int t = BBTools.tiefe(meinBaum);
in Delphi:
t: integer; tool : BBTools;
10
Kap. 3
Binärbäume
tool := BBTools.create; t := tool.tiefe(meinBaum);

Die Tiefe ist gleich 0, wenn der Baum leer ist

Die Tiefe des Baumes ist um eins größer, als das Maximum der Tiefen des linken und
des rechten Teilbaumes.
Betrachten wir zunächst die Funktion zur Berechnung des Maximums zweier natürlicher
Zahlen und verwenden diese zur Lösung!
In Java
private static int maximum (int a, int b){
if (a <= b) return b;
else return a;
}
Programmtechnisch bietet sich die Festlegung einer Tiefe von -1 für den leeren Baum an.
public static int tiefe (BinTree b){
if(b.isEmpty())return -1;
else return 1+maximum(tiefe(b.getLeftTree()), tiefe(b.getRightTree()));
}
In Delphi
function maximum(a:integer; b:integer):integer;
begin
if (a <= b) then maximum := b;
else maximum := a;
end;
function tiefe (b: BinTree): integer;
begin
if(b.isEmpty) then tiefe := -1
else tiefe := 1+maximum(tiefe(b.getLeftTree), tiefe(b.getRightTree));
end;
Übung 1.4
a) Bestimmung der Anzahl der Knoten: knotenzahl (b: BinTree b): integer
b) Ist eine Zeichenkette im Baum enthalten?
enthalten(s: String, b: BinTree):boolean
Falls der Baum leer ist, kann die Zeichenkette nicht enthalten sein.
Enthalten ist die Zeichenkette, wenn sie mit dem Wert der Wurzel übereinstimmt oder im
linken oder im rechten Teilbaum enthalten ist.
Methoden und Techniken
11
Übung 1.5
Äußere Knoten in der Vertikalen
Im Zusammenhang mit Suchbäumen spielt das am weitesten rechts oder am weitesten links
liegende Element des Baumes eine gewisse Rolle, wenn gelöscht werden soll. Es handelt sich
bei diesen Elementen um das größte und das kleinste des Suchbaumes. Im Ausgangsbeispiel
sind dies die Zahlen 4 und 78. Die Strategie bei der Suche des am weitesten rechts befindlichen Elements liegt darin, den Kanten so weit nach rechts unten zu folgen, bis ein Knoten
keinen rechten Nachfolger aufweist.
ganzRechts(b: BinTree): Object

Falls b leer ist,

falls jedoch der rechte Teilbaum leer ist, gebe man den Wert der
ist der Rückgabewert null oder nil,
aktuellen Wurzel zurück,

ansonsten liegt der gesuchte Wert ganzRechts vom linken Teilbaum,
Man implementiere die rekursiven Methoden ganzRechts() und ganzLinks().
Durchlauftechniken
+
Die Knoten eines Baumes können in gewissen Reihenfolgen besucht
und ausgegeben werden. Der Strukturbaum lässt unter Anderen die
sinnvollen Auslesemöglichkeiten 12/4+2*3 und 12 4 / 2 3 * + zu.
/
12
*
4
2
3
Im ersten Fall handelt es sich um eine Ordnung, die mit „inorder“ bezeichnet wird: Besuche
zunächst den linken Teilbaum L, danach die Wurzel W und schließlich den rechten Teilbaum
R. „in“ bezieht sich auf die Wurzel:
1. inorder:
2. preorder
2. postorder
LWR
WLR
LRW
besuche die Wurzel zwischen den Teilbäumen
besuche die Wurzel vor den Teilbäumen
besuche die Wurzel nach den Teilbäumen
Wie der Baum, sind auch die Durchläufe rekursiv aufgebaut. Bei der Ordnung Links-WurzelRechts (lwr, inorder) muss das Pluszeichen warten, bis der linke Teilbaum durchlaufen ist:
links
linker Teilbaum
(+)
rechter Teilbaum
(/)
(+)
rechter Teilbaum
rechts
12
/
4
+
links
12
/
4
+
2
(*)
*
rechts
3
Jeder Durchlauf besucht Knoten des Baumes in einer gewissen Ordnung und garantiert, dass
jeder Knoten genau einmal besucht wird. Bei einer solchen Vorgabe spricht man von einem
Euler-Durchgang (engl. euler tour traversal) nach dem Mathematiker Euler.
12
Kap. 3
Binärbäume
Übung 1.6
a) Versuchen Sie zu verstehen, wieso der UPN-Term 12 4 / 2 3 * + dem PostorderDurchlauf LRW entspricht. Der Durchlauf erzeugt Postfix-Notation.
b) Wie lautet die Ausgabe beim Preorder-Durchlauf WLR?
Implementierunsvorschlag für den In-Order -Durchlauf
Zunächst sollen die Inhalte in einer Ausgabe sequentiell dargestellt werden. Die entsprechende Methode laute ausgabe(Object). Als Erweiterung liefere die Methode die
Ausgabe als Zeichenkette.
Java
Delphi
public void inord(BinTree b){
PROCEDURE inord(b: BinTree);
String s;
VAR s: String;
if (!b.isEmpty()) {
BEGIN IF not b.isEmpty THEN
inord(b.getLeftTree());
BEGIN inord(b.getLeftTree());
ausgabe(b.getRootItem());
ausgabe(b.getRootItem());
inord(b.getRightTree());
inord(b.getRightTree());
}
}
END
END;
Als Schreibziel kommt die Standardausgabe oder ein Textfeld (in TreeGUI: einAusgabe)
in Betracht.
Übung 1.7
a) Implementieren Sie die drei vorgestellten Baumdurchläufe.
b) Statt der sequentiellen Ausgabe sollen die Werte entsprechend der jeweiligen Ordnung
in eine Zeichenkette geschrieben werden. Als Trennsymbol eignet sich ein Strichpunkt.
c) Die Baumelemente sind ordnungsgemäß in eine Liste zu schreiben.
Zur Präsentation der Übungsergebnisse eignet sich eine graphische Benutzeroberfläche der im
nachfolgenden Bild dargestellten Art. Der Prototyp liegt auf der Begleit-CD im Aufgabenverzeichnis dieses Kapitels vor. Die Methoden sind in der Klasse BBAufgabenGUI bereits
vorbereitet. Es fehlt lediglich die Implementierung. Die grafische Ausgabe erfolgt automatisch. Das Ausgabefeld wird mit schreibe(Zeichenkette) angesprochen. Im Beispiel
erscheint die Zeichenkette „Säugetier, Hai, Haustier, Wolf, Hund“, die im Programmverlauf
zusammengestellt wird.
Methoden und Techniken
13
Abb. 3. 1 Der Frage/Antwortbaum mit Preorder-Durchlauf
Übung 1.8
a) Sowohl beim Huffmanncode, als auch im Fragebaum enthalten die Blätter wichtige
Informationen. Es ist eine Methode zu entwerfen, die alle Blätter im Inorder-Durchlauf
ausgibt.
b) Der Huffmann-Baum ist so auszulesen, dass die Buchstaben und ihr Code tabellarisch
zusammengefasst sind.
c) Ein Dialog führt durch die als Binärbaum
strukturierte Wissensbasis beim Raten von
Tieren. Falls der gesuchte Tiername nicht
enthalten ist, erfolgt eine entsprechende Ausgabe
(schreibe(String)) wie „Unbekanntes
Tier“ oder „Das Tier wurde erraten“. Es wäre
wünschenswerte, die Wissensbasis dynamische
erweitern und abspeichern zu können. Diese
Erweiterung erfolgt später.
Sie finden auf der Begleit-CD in der Datei BBAufgabenGUI Methoden namens blattAusgabe(), HuffmanCode() und tiereRaten(), welche die Funktionen und Prozeduren blaetter(BinTree,String):String, Huffman(String, BinTree):
String und dialog(BinTree)aufrufen. Letztere sind zu implementieren. Statt die
14
Kap. 3
Binärbäume
Ergebnisse in Zeichenketten zu sammeln, könnte zu diesem Zweck auch Listen in Frage
kommen. Zeichenketten bieten den Vorteil der einfachen Ausgabe.
Die Berechnung des Strukturbaumes
Innere Knoten enthalten die Operatoren, in den Blättern stehen die Zahlen bzw. die Operanden. Für die Berechnung bedeutet diese Konstellation, dass während des Durchlaufs die
Operatoren jeweils auf die linken und rechten Teilbäume angewendet werden müssen. Unter
der Voraussetzung, dass der Baum nicht leer ist, bietet sich das folgende rekursive Verfahren
an:

Falls die Wurzel ein Blatt ist, also die linken und rechten Teilbäume leer sind, hat der
Baum den Wert der Wurzel.

Ansonsten bestimme man die Rechenergebnis der linken und rechten Teilbäume,
o lese den Operator aus der Wurzel und
o wende den Operator auf die beiden Ergebnisse in der berechneten Reihenfolge
an.
Übung 1.9
Kodieren Sie eine Methode zur Berechnung eines Strukturbaumes mit den möglichen
zweiwertigen Operatoren Dividieren, Multiplizieren, Addieren und Subtrahieren. Sie finden
auf der Begleit-CD in der Datei BBAufgabenGUI eine Methode namens berechnen(),
welche die Methode berechne(BinBaum):double aufruft. Letztere ist zu
implementieren.
Ergebnis: 26.0
Wie der Baum mit aus dem Term 2 + (6 – 3) * 8 automatisch erzeugt werden kann, wird
später behandelt. Vorerst genügt die Berechnung.
Zur Erinnerung an die Konstruktion per Hand:

erzeuge einen Baum mit dem Wert 2  baum2

erzeuge den Baum zu 6-3 (-, neuer Baum(6), neuer Baum(3))  baum63

erzeuge einen Baum aus (*, baum63, neuer Baum(8))  baum638

erzeuge einen Baum aus (+, baum2, baum638)  b
Methoden und Techniken
15
Erweiterte Baumoperationen
In den vorangehenden Beispielen entstehen die Binärbäume ausschließlich mit Hilfe von
Konstruktoren, die zu Beginn sämtliche Inhalte festlegen. Anhand der Ordnungskriterien
muss es möglich sein, einen Baum während der Laufzeit eines Programms verändern oder
erweitern zu können. Hier stellt sich die Frage, ob die eingeführten Baummethoden das
Einfügen und Löschen erlauben oder, ob weitere Methoden notwendig sind. Schaut man auf
die bei learnline veröffentlichten Empfehlungen, findet man weitere Methoden, die einer
prozeduralen Denkweise folgen. Funktionale Programmierer benötigen diese zusätzlichen
Operatoren nicht.
Funktionaler oder prozeduraler Ansatz?
Sollen Operationen, wie das Einfügen von Elementen, zu neuen Bäumen führen oder
gegebene Bäume verändern? Das Beispiel der Verknüpfung der Wurzel mit einem linken
Teilbaum verdeutlicht die Unterschiede von funktionaler und prozeduraler Sicht:
Methode
b.adtreeLeft zur Veränderung des linken Teilbaumes
Funktional:
b.addTreeLeft(tree: BinTree): BinTree
entspricht
<<create>>(b.getRootItem(),tree, b.getRightTree())
Prozedural:
b.addTreeLeft (tree: BinTree): void
prozedural
Ausgangssituation
b
t
b.addTreeLeft(c)
b
t
b.addTreeLeft(c) b
b
t
5
5
2
funktional
9
2
c
c
3
3
4
5
5
9
2
9
c
3
4
4
Beim funktionalen Ansatz wird zunächst mit Hilfe von c, der Wurzel von b und dem rechten
Teilbaum von b ein neuer Binärbaum erzeugt (gestrichelter Pfeil). Durch die Zuordnung des
neuen Baum zu b entsteht das Ergebnis. Im rechten Bild tangiert die Operation bei b den
Baum t nicht, während t im mittleren Bild die Veränderung von b teilt. Ein solcher Fall
verursacht einen Seiteneffekt auf t.
Gleiches spiegelt sich in der Implementierung wider. Seien value der Wurzelinhalt (d.h.
getRootItem()), left der linke Teilbaum (getLeftTree()), und right der rechte
Teilbaum (getRightTree()).
16
Kap. 3
Binärbäume
Funktional
addTreeLeft(tree: BinTree): BinTree
Rückgabe: new BinTree(value, tree, right)
Prozedural
addTreeLeft (tree: BinTree): void
left = tree bzw. left := tree;
Die zusätzlichen Baumoperationen im Einzelnen
public void clear()
public void setRootItem (Object pObject)
public void addTreeLeft (BinTree pTree)
public void addTreeRight (BinTree pTree)
Dokumentation der Methoden der Klasse BinTree bei learnline.
Auftrag
nachher
clear()
Der Binärbaum ist leer.
Auftrag
setRootItem (Object pObject)
nachher
Die Wurzel hat unabhängig davon, ob der Binärbaum leer ist oder schon eine
Wurzel hat, pObject als Inhalt. Eventuell vorhandene Teilbäume werden
nicht geändert.
Auftrag
addTreeLeft (BinTree pTree)
vorher
nachher
Der Binärbaum ist nicht leer.
Die Wurzel hat den übergebenen Baum als linken Teilbaum.
Auftrag
addTreeRight (BinTree pTree)
vorher
nachher
Der Binärbaum ist nicht leer.
Die Wurzel hat den übergebenen Baum als rechten Teilbaum.
Übung 1.10
Die angegeben Methoden sind in BinTree zu ergänzen und zu implementieren.
Interaktive Baumerweiterung
Der Fragebaum steht exemplarisch für 0/1-Bäume, die auch beim Huffmancode auftauchen.
Ähnlichen Kriterien unterliegen Suchbäume. Deren Behandlung erfolgt auf Grund ihrer vielfältigen Anwendungsmöglichkeiten gesondert.
Anlagen
17
Der Dialog beim Tierraten und die Erweiterung der Datenbasis.
Entscheidende Antworten stehen in den Blättern, wie beispielsweise die Tiergattung Hund.
Nehmen wir an, der Dialog hat bis zu diesem Endknoten geführt und es muss mit nein geantwortet werden. Statt des Hundes haben Sie sich nämlich eine Katze ausgedacht. Zu Erweiterung der Datenbasis muss dem System einerseits die Gattung Katze mitgeteilt werden, andererseits aber auch eine Frage, die beide Gattungen Hund und Katze voneinander unterscheidet. Bei der Beantwortung dieser Unterscheidungsfrage mit nein soll zum neuen Namen
verzweigt werden.
Übung 1.11
Entwerfen Sie einen Algorithmus für die Erweiterung der Datenbasis durch Befragung des
Benutzers. Die Situation tritt während des Dialogs auf, wenn ein Blatt durchlaufen wird und
die damit verbundene Frage mit nein beantwortet werden muss!
Hund
Frage: Hund?
Antwort: nein
Frage: Wie heißt die Gattung?
Antwort: Katze
Frage: Welche mit ja zu beantwortende Frage
führt zum Hund statt zur Katze?
Kann es bellen
Katze
Hund
Antwort: Kann es bellen
Das Beispiel zeigt, wie das Blatt mit dem Inhalt „Hund“ durch den Inhalt „Kann es bellen“
ersetzt wird. „Katze“ und „Hund“ sind Inhalte zweier neuer Bäume, die links und rechts
anzubinden sind.
Entwerfen Sie ein Programm zur Erweiterung der Datenbasis im Dialog. Der erweiterte Baum
ist zwischenzuspeichern (z.B. b  ratebaum ) und beim nächsten Dialogstart bereitzustellen (z.B. ratebaum  b; zeige();).
Konstruktion des Huffmanbaumes
Übung 1.12 (Kreative Aufgabe zum Huffmancode)
18
Kap. 3
Binärbäume
Ein ausschließlich aus Großbuchstaben und Leerzeichen bestehender Text ist mit dem
Huffmancode zu komprimieren. Die Zeichenfolge „LEERE LEHRE “ diene als Beispiel. Zur
Hervorhebung des Leerzeichens wird es als # dargestellt.
Lösungsplan
1 Man bestimme die Häufigkeit aller Zeichen des Textes
Buchstabe
#
E
H
L
R
Häufigkeit 1
4
1
2
2
2 Eine geordnete Liste diene als Speicher für Huffmancodebäume. Die Knoten des
Huffmancodebaumes enthalten nicht nur die Buchstaben, sondern auch deren Häufigkeit. Die
Häufigkeit dient als Ordnungskriterium: Wenn die Wurzel eines Baumes
Knoteninhalt
b1 eine kleineres Häufigkeitsattribut enthält als der eines zweiten Baumes
+buchstabe:char
+haeufigkeit: int
b2, gehört b1 in der Liste vor b2. Zu Beginn bestehen sämtliche Bäume
jeweils nur aus der Wurzel (Häufigkeit, Buchstabe).
Die Liste:
Die Knoteninhalte:
#
1
H
1
L
2
R
2
E
4
Der beschriebenen Ordnung entsprechend, befindet sich der E-Baum am Ende der Liste.
Huffman schlägt vor, die ersten beiden Bäume zu einem zu vereinigen, dessen Häufigkeit der
Summe beider Häufigkeiten entspricht. Die Buchstaben spielen in der neuen Wurzel keine
Rolle. Die beiden ersten Listenknoten sind zu löschen und der neue Baum ist gemäß seinem
Häufigkeitsattribut in die Liste einzufügen.
Die Liste:
Die Knoteninhalte:
L
2
2
#
1
H
1
R
2
E
4
Anlagen
19
Der neue Baum findet in der Liste als erstes Element seinen Platz, denn das nächste Element
zeigt keinen höheren Häufigkeitswert. Anders gestaltet sich Situation im nächsten Schritt, bei
dem ein Baum mit dem Häufigkeitswert 4 entsteht. Der Baum gehört in der Liste zwischen Rund E-Knoten.
Die Liste:
R
2
Die Knoteninhalte:
E
4
4
L
2
2
#
1
H
1
Nach zwei weiteren Schritten entsteht schließlich der endgültige Baum, aus dem der Code
erschlossen werden kann.
Die Liste:
E0
R  10
Die Knoteninhalte:
#  1100
H  1101
L  111
LEERE LEHRE 
11100100110011101101
10
E
4
6
R
2
100
4
L
2
2
#
H
1
1
Vorgehensweise

Bestimme die Häufigkeit der Buchstaben des Textes

Erzeuge eine geordnete Liste der enthaltenen Buchstaben als Wurzel von
Binärbäumen die nach der nach Häufigkeit geordnet sind

Wiederhole die Zusammenfassung der beiden ersten Bäume der Liste nach dem oben
beschriebenen Verfahren so lange, bis die Liste nur noch aus einem Element besteht.
20
Kap. 3
Binärbäume
(erzeuge den neuen Baum, lösche die beiden ersten Listenelemente, füge den neuen
Baum geordnet ein)

Gib die Codetabelle aus.

Komprimiere den Text entsprechend
Aufbau des Strukturbaums
Abb. 3. 2 Entstehung eines Strukturbaumes aus einer als Zeichnkette formuliertem Term
Aufgabe
Ausgehend von einem gültigen vorzeichenlosen arithmetischen soll der zugehörige
Strukturbaum entstehen.
Lösungsdiskussion
Im ersten Schritt wird die Syntax arithmetischer Ausdrücke anhand von Diagrammen
festgelegt. Es folgt die Syntaxprüfung unter Verwendung eines so genannten rekursiven
Abstiegs. Im gleichen Durchgang entsteht der Strukturbaum.
Der Ausdruck 2 + (6-3)*8 stellt eine Summe aus der Zahl 2 und einem Produkt dar.
Anlagen
21
Ausdruck
(6
+
2
-
3)
*
8
Summand
Summand
2
(6
+
Zahl
-
3)
*
8
Faktor
2
+
Faktor
6
-
3
*
8
Zahl
Ausdruck
In der Skizze fällt auf, das die Zahl 2 zunächst als Summand angesehen wird, um sich später
als eine einzelne Zahl zu entpuppen. Gleiches gilt auch für den Faktor 8. Vor der endgültigen
Auflösung bleibt ein Ausdruck als Faktor, dessen Analyse noch fehlt. Die mit Kreisen
umrahmten Symbole lassen sich nicht weiter auflösen. Daher heißen sie terminale Symbole.
„Terminal“ bedeutet so viel wie „endgültig“. Der Gesamtausdruck lässt sich gemäß der
Skizze beschreiben als Summe von 2 und der Multiplikation eines Ausdruckes mit 8. So
genannte Syntaxdiagramme verallgemeinern die Syntax eines beliebigen Ausdruckes:
Syntaxdiagramme
Ein arithmetischer Ausdruck ohne Vorzeichen
ist ein Produkt, oder eine Folge von Produkten,
die mit Plus- oder Minuszeichen verbunden
sind.
+
Ausdruck
Summand
Ein Summand hat einen Faktor, oder er besteht
aus einer Folge von Faktoren, die mit dem
Mal- oder Teilungszeichen verknüpft sind.
*
Summand
/
Faktor
Faktor
Zahl
(
Ausdruck
)
Der Faktor kann eine Zahl
sein oder ein geklammerter
Ausdruck. Mit dem Oval
um das Zahlsymbol wird
dieses als quasi terminal
22
Kap. 3
Binärbäume
angesehen Es handelt sich dabei um Folgen der Ziffern 0, 1, 2 bis 9. Mit dem nicht terminalen
Symbol (Nonterminal) Ausdruck entsteht eine rekursive Beschreibung der Syntax.
Eine Zeichenfolge, die auf dem gezeigten Graphen vom Startpfeil bis zu einem der Endpfeile
führt gehört zur Sprache der auf ganze Zahlen und Grundoperationen beschränkten arithmetischen Ausdrücke. Sie enthält die terminalen Symbole {+, -, *, /, ), (, 0, 1, 2,… 9} und die
Nonterminals {Ausdruck, Summand und Faktor}. Gestartet wird mit dem nicht terminalen
Symbol Produkt. Jedes Zeichen des Ausdrucks, das nicht zur Menge der terminalen Symbole
gehört, beendet den Ausdruck. Falls zu diesem Zeitpunkt der Graph nicht durchlaufen ist,
handelt es sich bei der Zeichenkette nicht um einen gültigen Ausdruck.
Codeerzeugung und Syntaxprüfung
Als Code wird hier der Strukturbaum angesehen, der die Struktur und indirekt den Wert eines
Ausdrucks repräsentiert.
Zuständig für die Syntaxprüfung ist der Parser. Das Programm richtet sich nach gewissen
Regeln:
Regeln des Parsers

Die Zeichenkette wird von Anfang an zeichenweise bearbeitet

Begonnen wird mit dem Lesen des ersten Zeichens

Solange gültige Zeichen vorkommen und das Ende der Zeichenkette noch nicht
erreicht ist gehe man entlang des Pfades im Grafen

Ein Nonterminal führt zum Aufruf eine gleichnamigen Prozedur oder Funktion

Ein terminales Symbol führt zum Lesen des nächsten Symbols

Das aktuelle Zeichen weist eindeutig den Weg

Rückwärts gerichtete Pfeile werden als Schleifen interpretiert
Die Strukturbaumerzeugung
Ausgehend von einer den Ausdruck repräsentierende Zeichenkette term wird jeweils ein
aktuelles Zeichen zeichen gelesen. Leerzeichen zwischen Operatoren sollen erlaubt sein.
Sie werden einfach überlesen, da sie keine strukturelle Bedeutung haben. Aus praktischen
Gründen wird die Zeichenkette mit einem Endzeichen # versehen. Die Maßnahme beugt
einem Laufzeitfehler vor, der entstehen kann, wenn am Ende der Zeichenkette der Versuch
unternommen wird, das nächste Zeichen zu lesen.
String term;
var term: String;
char zeichen;
var zeichen: char;
int position = 0;
var position: integer;
boolean fehler = false;
var fehler: boolean;
//--------------------
position := 0; fehler := false;
Anlagen
23
//--------------------------public void liesZeichen(){
while(term.charAt(position)== ' ')
position++;
zeichen = term.charAt(position);
position++;
}
procedure liesZeichen();
begin
while term[position]= ' ' do
position := position+1;
zeichen = term[position];
position := position+1;;
end;
public boolean istZiffer (){
return (zeichen >= '0' &&
zeichen <= '9');
function istZiffer: boolean;
begin
istZiffer := zeichen IN['0'.. '9'];
}
end;
public String liesZahl(){
function liesZahl:String;
String z="";
var z:String;
while (istZiffer()) {
begin
z = z+zeichen;
z := '';
liesZeichen();
while istZiffer do
} // while
return z;
} //liesZahl
begin
z := z + zeichen;
liesZeichen;
end;
end;
public void fehler(String s){
System.out.println(s);
error = true;
}
procedure fehler(s: String);
begin
writeln(s);
error = true;
end;
Die Beispiele zeigen nützliche Operationen zum Lesen eines Zeichens, einer Zahl und der
Abfrage, ob es sich bei einem Zeichen um eine Ziffer handelt. Falls ein Fehler während des
Lesens auftritt, erfolgt eine gezielte Ausgabe. Der Fehler wird protokolliert, damit
gegebenenfalls die Syntaxprüfung beendet werden kann.
Nachfolgend finden Sie die Übersetzung des Syntaxdiagramms zum Ausdruck in Java,
anschließend die entsprechende zum Summand in Delphi (als Prozedur).
public BinTree liesAusdruck(){
Ausdruck
BinTree baum = new BinTree();
BinTree teilbaum = new BinTree();
+
-
char op;
baum = liesSummand();
while ((zeichen == '+') || (zeichen == '-')){
op = zeichen;
Summand
24
Kap. 3
Binärbäume
liesZeichen();
teilbaum = liesSummand();
if (error) break;
if (op == '+')
baum = new BinTree (baum, "+",
teilbaum);
else
baum = new BinTree (baum, "-", teilbaum);
}//while
return baum;
}//liesTerm()
In Delphi
PROCEDURE liesSummand(VAR b:BinTree);
Summand
*
VAR operand : BinTree;
/
operator : TObject;
BEGIN (*lies_ausdruck*)
Faktor
liesFaktor (b);
WHILE zeichen IN ['+','-' ]DO
BEGIN
operator := Zeichen;
liesZeichen;
LiesProdukt (operand);
b := BinTree.create(operator,b, operand);
END; {WHILE}
END;
Beide Fälle demonstrieren die Übersetzungsregeln, Nonterminals als Unterprogrammaufrufe
zu übersetzen und jedem Terminalzeichen das Lesen des nächsten folgen zu lassen. Die rückwärts gerichteten Pfeile leiten eine Schleife ein, die nur beim Auftreten der entsprechenden
Symbole betreten wird.
Das Diagramm zum Faktor
Faktor
Zahl
(
Ausdruck
)
zeigt die Alternative Zahl
oder geöffnete Klammer.
Da nur ein Zeichen zur
Prüfung vorliegt leitet nur
eine Ziffer den Aufruf von liesZahl ein. Falls keines der erwarteten Zeichen vorliegt,
endet die Prüfung der Syntax und die Baumkonstruktion mit einem Fehler, da die Syntax
nicht stimmt. Im Falle einer Ziffer, wird die Zahl geprüft. Handelt es sich bei dem Zeichen
um eine offene Klammer, sind das nächste Zeichen und der Ausdruck zu lesen. Nach der
Bearbeitung des Ausdrucks liegt das nächste Zeichen vor. Sein Wert entscheidet über den
Anlagen
25
Fortgang entlang der Pfeile. Es muss eine geschlossene Klammer sein. Andernfalls bricht die
Prüfung mit der Fehlermeldung „geschlossene Klammer erwartet“ ab.
Übung 1.13

Implementieren Sie die Syntaxprüfung (engl. parser) und Erzeugung des zugehörigen
Strukturbaumes. Als Eingabe liegt eine Zeichenkette vor, die vor der Überprüfung mit
einer Endmarke (z.B. #) versehen wird, die kein terminales Symbol sein darf.

Der Ausdruck soll auch Vorzeichen wie + oder – enthalten dürfen.
o Ergänzen Sie den Syntaxgraphen
o Implementieren Sie den Parser und Baumerzeuger
Übung 1.14 (Kreative Vertiefung zur Syntaxprüfung und Strukturbaum)
Die folgenden Syntaxdiagramme beschreiben eine Sprache logischer Ausdrücke.
Logischer Ausdruck
UndAusdruck
~
UndAusdruck
+
UndAusdruck
Faktor
Faktor
Faktor
w
f
(
OderAusdruck
Fehler
)
*
26
Kap. 3
Binärbäume
1. Zeichen Sie den Strukturbaum zum Term w + f * (w+f)
2. Skizzieren Sie den Parser und Erzeuger des Strukturbaumes
3. Erläutern Sie, wie der Strukturbaum zur Wertberechnung des boolesches Ausdruckes
dienen. Es gilt:
*
w
f
w w
f
f
f
f
+
w
f
w w
w
f
f
w
~w  f, ~f w
Herunterladen