Datenstruktur Baum Software Entwicklung 1

Ähnliche Dokumente
Vorlesung Datenstrukturen

368 4 Algorithmen und Datenstrukturen

Algorithmen und Datenstrukturen 1

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12. Kapitel 13. Bäume. Bäume

Binärbäume. Prof. Dr. E. Ehses,

Bäume. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 10: Collections 4. Inhalt. Bäume. Einführung. Bäume.

Geordnete Binärbäume

Bäume. Text. Prof. Dr. Margarita Esponda SS 2012 O4 O5 O6 O ALP2-Vorlesung, M. Esponda

Binäre Bäume Darstellung und Traversierung

Der linke Teilbaum von v enthält nur Schlüssel < key(v) und der rechte Teilbaum enthält nur Schlüssel > key(v)

Lernziele: Ausgleichstechniken für binäre Bäume verstehen und einsetzen können.

10. Kapitel (Teil1) BÄUME GRUNDLAGEN. Algorithmen & Datenstrukturen Prof. Dr. Wolfgang Schramm

Folge 19 - Bäume Binärbäume - Allgemeines. Grundlagen: Ulrich Helmich: Informatik 2 mit BlueJ - Ein Kurs für die Stufe 12

13. Binäre Suchbäume

Algorithmik II. a) Fügen Sie in einen anfangs leeren binären Baum die Schlüsselfolge 20, 28, 35, 31, 9, 4, 13, 17, 37, 25 ein.

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

Kap. 4.2: Binäre Suchbäume

Kapiteltests zum Leitprogramm Binäre Suchbäume

Eine Baumstruktur sei folgendermaßen definiert. Eine Baumstruktur mit Grundtyp Element ist entweder

5.14 Generics. Xiaoyi Jiang Informatik I Grundlagen der Programmierung

Algorithmen und Datenstrukturen Suchbaum

Informatik 11 Kapitel 2 - Rekursive Datenstrukturen

Algorithmen und Datenstrukturen

Suchen und Sortieren

Wiederholung ADT Menge Ziel: Verwaltung (Finden, Einfügen, Entfernen) einer Menge von Elementen

Idee: Wenn wir beim Kopfknoten zwei Referenzen verfolgen können, sind die Teillisten kürzer. kopf Eine Datenstruktur mit Schlüsselwerten 1 bis 10

Fortgeschrittene Programmiertechnik Klausur SS 2015 Angewandte Informatik Bachelor

Grundlagen der Programmierung

Tutorium Algorithmen & Datenstrukturen

2 Java: Bäume. 2.1 Implementierung von Bäumen. 2.2 Implementierung eines binären Suchbaums. 2.3 Traversierung von Bäumen

Allgemeine Informatik 2 im SS 2007 Programmierprojekt

Kapitel 4: Dynamische Datenstrukturen. Algorithmen und Datenstrukturen WS 2012/13. Prof. Dr. Sándor Fekete

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

Datenstrukturen & Algorithmen

Datenstrukturen und Algorithmen

Programmierung und Modellierung

Suchbäume. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Informatik II Bäume. Beispiele. G. Zachmann Clausthal University, Germany Stammbaum. Stammbaum. Stammbaum

Programmiertechnik II

Programmieren I. Kapitel 5. Kontrollfluss

Übung zur Vorlesung Algorithmische Geometrie

Einstieg in die Informatik mit Java

Programmierung 1 (Wintersemester 2012/13) Erklärung 5 (Prä- und Postordnung)

Bäume, Suchbäume und Hash-Tabellen

12 (2-4)-Bäume Implementierbare Funktionen. (2-4)-Bäume sind durch folgende Eigenschaften deniert: 1. Alle Äste sind gleich lang

Beispiellösungen zu den Übungen Datenstrukturen und Algorithmen SS 2008 Blatt 6

Institut für Programmierung und Reaktive Systeme 25. August Programmier-Labor Übungsblatt. int binarysearch(int[] a, int x),

Binary Decision Diagrams (Einführung)

Java 8. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Oktober 2014 JAV8

Übungen zu Programmierung I - Blatt 8

Allgemeine Hinweise: TECHNISCHE UNIVERSITÄT MÜNCHEN. Name Vorname Studiengang Matrikelnummer. Hörsaal Reihe Sitzplatz Unterschrift

1. Typen und Literale (6 Punkte) 2. Zuweisungen (6 = Punkte)

Copyright, Page 1 of 8 AVL-Baum

Informatik II. PVK Part1 Severin Wischmann n.ethz.ch/~wiseveri

Informatik II, SS 2014

11.1 Grundlagen - Denitionen

2 i. i=0. und beweisen Sie mittels eines geeigneten Verfahrens die Korrektheit der geschlossenen Form.

Objektorientierte Programmierung

MB2-ALG, SS15 Seite 1 Hauptklausur, geschrieben am

Prof. Dr. Heinrich Müller; Dr. Frank Weichert 7. September 2015

14. Rot-Schwarz-Bäume

Probeklausur: Programmierung WS04/05

AVL-Bäume Analyse. Theorem Ein AVL-Baum der Höhe h besitzt zwischen F h und 2 h 1 viele Knoten. Definition Wir definieren die nte Fibonaccizahl:

Tutoraufgabe 1 (2 3 4 Bäume):

Datenstrukturen & Algorithmen Lösungen zu Blatt 6 FS 14

Datenstruktur, die viele Operationen dynamischer Mengen unterstützt

Objektorientierte Programmierung

Programmiertechnik II

Präzedenz von Operatoren

Übungsaufgaben: 1. Objektorientierte Programmierung - Teil 1

Programmierung WS12/13 Lösung - Übung 1 M. Brockschmidt, F. Emmes, C. Otto, T. Ströder

Tutoren Simon Andermatt Lukas Beck. Alexis Peter Thomas Ritter

6 Baumstrukturen. Formale Grundlagen der Informatik I Herbstsemester Robert Marti

9.4 Binäre Suchbäume. Xiaoyi Jiang Informatik II Datenstrukturen und Algorithmen

Tutoren Jan Ebbe Pat Mächler Valentino Rugolo Sascha Scherrer. Grundlagen der Programmierung (CS101) - Blatt 8 Theorie [4 Punkte] - Praxis [12 Punkte]

8 Baum in perfekter Komposition

JAVA - Methoden

JAVA - Methoden - Rekursion

Abstrakte Klassen und Induktive Datenbereiche

Aufgabe zu KD Bäumen

Zeichnen von Graphen. graph drawing

Kap. 4.4: B-Bäume Kap. 4.5: Dictionaries in der Praxis

t r Lineare Codierung von Binärbbäumen (Wörter über dem Alphabet {, }) Beispiel code( ) = code(, t l, t r ) = code(t l ) code(t r )

Beispielblatt VU Algorithmen und Datenstrukturen 1 VU 6.0

Name: Seite 2. Beantworten Sie die Fragen in den Aufgaben 1 und 2 mit einer kurzen, prägnanten Antwort.

EndTermTest PROGALGO WS1516 A

Student: Alexander Carls Matrikelnummer: Aufgabe: Beschreibung des euklidischen Algorithmus Datum:

3.2 Binäre Suche. Usr/local/www/ifi/fk/menschen/schmid/folien/infovk.ppt 1

1 Polymorphie (Vielgestaltigkeit)

Agenda. 1 Einleitung. 2 Binäre Bäume. 3 Binäre Suchbäume. 4 Rose Trees. 5 Zusammenfassung & Ausblick. Haskell Bäume. Einleitung.

Fakultät Wirtschaftswissenschaft

Kurs 1613 Einführung in die imperative Programmierung

Algorithmen und Datenstrukturen SS09

Transkript:

Datenstruktur Baum Software Entwicklung 1 Annette Bieniusa, Mathias Weber, Peter Zeller Bäume gehören zu den wichtigsten in der Informatik auftretenden Datenstrukturen. [Ottmann, Widmayer: Algorithmen und Datenstrukturen, 5. Auflage, Springer 2012] Baumstrukturen finden sich in verschiedenen Kontexten in der Informatik wieder: Syntaxbäume, Darstellung von Termen, Visualierung von Dateisystemen, Darstellung von Hierarchien (z.b. Typhierarchie) etc. Die Datenstruktur Baum spielt außerdem eine wichtige Rolle in der Implementierung von abstrakten Datentypen wie Maps und auch von Datenbanken. In diesem Kapitel geben wir eine Einführung in die Datenstruktur Baum und zeigen Varianten, wie Baumstrukturen implementiert werden können. Wir diskutieren außerdem, wie sortierte markierte Binärbäume die Suche von Elementen im Baum erleichtern. Lernziele dieses Kapitels: Die Definition wichtiger Begri e im Zusammenhand mit Bäumen zu kennen. Markierte Bäumen, insbesondere Suchbäume, in Java zu implementieren. Terminierungsbeweise für Methoden auf Bäumen durchzuführen. Baumstrukturen zur Darstellung der Syntax von arithmetischen Ausdrücken zu verwenden. 1 Grundkonzepte Führen wir zunächst einige Begri e ein: In einem endlich verzweigten Baum hat jeder Knoten endlich viele Kinder. Einen Knoten ohne Kinder nennt man ein Blatt, einen Knoten mit Kindern einen inneren Knoten oder Zweig. 1

Jeder Knoten (bis auf die Wurzel) hat genau einen Elternknoten. Den Knoten ohne Elternknoten nennt man Wurzel. i Zu jedem Knoten k gehört ein Unterbaum, nämlich der Baum, der k als Wurzel hat. In einem Binärbaum hat jeder Knoten maximal zwei Kinder. Frage 1: In unserer Vorlesung behandeln wir eine bestimmte Art von Bäumen, sogenannte gerichtete gewurzelte Bäume, bei denen wir stets einen Knoten als Wurzelknoten auszeichnen und die Verbindung von Eltern- zu Kindknoten (d.h. weg von der Wurzel) ausgerichtet ist. In der Graphentheorie werden allgemeinere Arten von Bäumen definiert. Dazu erfahren Sie mehr in den Vorlesungen FGdP oder Graphentheorie. Markieren Sie im folgenden Baum einen Wurzelknoten! Welche Knoten sind Blätter? Welche sind innere Knoten? Handelt es sich um einen Binärbaum? 1.1 Markierte Bäume Ein Baum heißt markiert, wenn jedem Knoten k ein Wert/eine Markierung m(k) zugeordnet ist. 2

:BinTree root 5 10 4 5 9 1 Um einen markierten Binärbaum mit ganzen Zahlen als Markierung zu implementieren, benötigen wir zunächst eine Klasse für die Baum-Knoten. Diese Knoten-Klasse erhält Attribute für die Markierung und die Referenzen auf die beiden Kindknoten left und right. // Repraesentiert die Knoten eines Baums mit Markierungen class TreeNode { private int mark; private TreeNode left; private TreeNode right; TreeNode( int mark, TreeNode left, TreeNode right) { this. mark = mark; this. left = left; this. right = right; // Liefert die Markierung eines Knotens int getmark() { return this. mark; // Liefert die Referenz auf den linken Kindknoten TreeNode getleft() { return this. left; // Liefert die Referenz auf den rechten Kindknoten TreeNode getright() {... return this. right; Die Implementierung des Baums hält dann die Referenz auf den Wurzelknoten root. Dieses Attribut ist anfangs mit null initialisiert. Von diesem Wurzelknoten sind alle anderen Knoten des Baumes erreichbar. Jeder Knoten des Baumes ist selbst wiederum Wurzelknoten des von ihm aufgespannten Unterbaums. // Repraesentiert einen markierten Binaerbaum public class BinTree { private TreeNode root;...

Bäume sind - wie auch verlinkte Listen - rekursive Datentypen. Bei rekursiven Datentypen wird der Datentyp selbst zu seiner eigenen Definition herangezogen. Eine Liste ist entweder leer oder besteht aus einem Element und einer (Rest-) Liste. Eine Baum ist entweder leer oder besteht aus einer Markierung und einem linken und rechten (Unter-) Baum. Bei einer Definition einer rekursiven Datenstruktur gibt es einen oder mehrere Basisfälle (z.b. leere Liste / leerer Baum) und Rekursionsfälle, die beschreiben, wie man aus kleineren Instanzen der Datenstruktur größere aufbaut. In der Implementierung in Java, die wir hier präsentieren, zeigt sich der rekursive Charakter von Bäumen dadurch, dass die Baumknoten als Attribute Referenzen auf Baumknoten haben. 1 Berechnungen auf rekursiven Datenstrukturen wie Bäumen und Listen lassen sich in der Regel auf eine Kombination der Berechnung für den aktuellen Knoten und des Ergebnisses der Berechnung für den rekursiven Teil der Datenstruktur zurückführen. Essentiell ist es dabei, die Berechnung für die Basisfälle nicht zu vergessen. Beispiel Um alle Markierungen in einem mit int-markierten Binärbaum aufzusummieren, addieren wir zu der Markierung des Knotens, der diesen Baum aufspannt, die Summe der Markierungen für den linken und für den rechten Unterbaum. Dazu berechnen wir die Summe zum Unterbaum des linken Kindknoten, dann zum rechten Kindknoten und addieren die jeweiligen Ergebnisse dann zur Markierung des aktuellen Knotens. Im Basisfall für den leeren Baum (Knoten ist gleich null) ist die Summe gleich 0. public class BinTree { private TreeNode root;... // Berechnet die Summe der Markierungen des Baums public int sum() { return sum( this. root); // Hilfsmethode private int sum( TreeNode node) { if ( node == null) { return 0; return node. getmark() + sum( node. getleft()) + sum( node. getright()); 1 In Java können Referenzen auf beliebige Objekt des zugehörigen Referenztyps verweisen. Dies kann zu unerwünschten zirkulären Objektgeflechten führen, die der obigen Definition von rekursiven Datentypen widersprechen. Wie bei der Implementierung der einfachverketteten Liste müssen wir auch hier sicherstellen, dass die Implementierung des Baumstruktur eine entsprechende Invariante garantiert. Wir gehen hier davon aus, dass innerhalb der hier beschriebenen rekursiven Datentypen unterschiedliche Referenzen nicht auf das gleiche Objekt verweisen. 4

Die ö entliche Methode sum() implementiert den beschriebenen Algorithmus. In ihr wird zunächst die Summation auf dem Wurzelknoten root aufgerufen in der privaten Hilfsmethode sum(treenode node) (Stichwort: Overloading). In der Implementierung verwenden wir die gleiche private Hilfsmethode sum(treenode node) für die Summation bei den Kindknoten. Die Methode sum(treenode node) ist rekursiv, sie ruft sich selbst erneut auf. Die rekursive Struktur des Baums spiegelt sich wider in diesen rekursiven Methodenaufrufen. apple Frage 2: enthält? Wie können wir testen, ob der Baum eine bestimmte Markierung Was ist das Ergebnis im Basisfall (leerer Baum)? Was ist das Ergebnis im Rekursionsfall? Schreiben Sie eine Methode public boolean contains(int x) für die Klasse BinTree, die true liefert, wenn einer der Knoten des Baumes mit x markiert ist! 1.2 Terminierungsbeweise auf rekursiven Datenstrukturen Um die Terminierung der Methode sum(treenode node) zu zeigen, gehen wir wieder nach dem bekannten Schema vor (siehe Kapitel zur Terminierung). 1. Da wir die Terminierung für alle gültigen Bäume mit node als Wurzelknoten beweisen wollen, schränken wir den Parameterbereich nicht weiter ein. 2. Damit ist der Beweis, dass der gültige Parameterbereich nicht verlassen wird, auch relativ einfach, da ein gültiger Wurzelknoten node einen gültigen linken und rechten Teilbaum besitzt.. Um den Terminierungsbeweis zu führen, müssen wir eine gültige Abstiegsfunktion wählen. Die Idee dabei ist, dass die linken und rechten Teilbäume immer kleiner sind als der Baum selbst. Die informelle Beschreibung der Abstiegsfunktion ist somit wie folgt: h : TreeNode! N 0 h(t) = Höhe des Baumes t Wir definieren die Höhe eines Blattes als 0 und die Höhe eines Zweiges als das Maximum der Höhe der Teilbäume um 1 erhöht. Somit ergibt sich die folgende Java-Methode, die die Höhe eines Baumes beginnend mit der Wurzel berechnet: public int height( TreeNode node) { if ( node == null) { return 0; 5

return 1 + Math. max( height( node. getleft()), height( node. getright())); Wir können nun die Abstiegsfunktion h definieren: h(t) =height(t) Die Abstiegsfunktion h bildet in die natürlichen Zahlen ab, da das Ergebnis für die kleinsten möglichen Bäume bestehend nur aus einem Blatt gleich 0 ist und für größere Bäume entsprechend größer. 4. Zu zeigen bleibt, dass die Parameter für rekursive Aufrufe echt kleiner werden. Aufruf sum(node.getleft()): h(node) =height(node) >height(node.getleft()) = h(node.getleft()) Dies gilt, da es sich bei node.getleft() um eine kleinere Instanz eines Baumes handelt, da node kein Blatt ist (wegen der if-bedingung). Formal bedeutet das, dass height(node) = 1+M ath.max(height(node.getleft()),height(node.getright())) 1+height(node.getLeft()) >height(node.getleft()) Aufruf sum(node.getright()): Gleiche Begründung wie oben. Somit haben wir gezeigt, dass die sum-methode für alle gültigen Bäume terminiert. i Da es sich bei height ebenfalls um eine rekursive Methode handelt, müsste man für diese Methode ebenfalls die Terminierung zeigen, um sie in der Abstiegsfunktion benutzen zu können. Der Beweis der Terminierung der height-methode ist auf die Baum- Invariante begründet, dass es keine Zyklen in dem Objektgeflecht der Baumknoten gibt. Im Rahmen dieser Vorlesung können Sie davon ausgehen, dass die Terminierung der height-methode gilt. 1. Sortierte markierte Bäume Definition Ein mit ganzen Zahlen markierter Binärbaum heißt sortiert, wenn für alle Knoten k gilt: Alle Markierungen der linken Nachkommen von k sind kleiner als oder gleich m(k). Alle Markierungen der rechten Nachkommen von k sind größer als m(k). 6

Frage : Ist dieser markierte Binärbaum sortiert? :BinTree root 5 2 1 4 10 15 Die Sortiertheitseigenschaft von sortierten markierten Binärbaum erleichtert es ungemein, Elemente in dem Baum zu suchen. Diese Bäume werden daher auch oft Suchbäume genannt. Um ein Element in dem Baum zu suchen, beginnen wir zunächst wieder mit dem root-knoten: In einem leeren Baum ist das Element nicht enthalten. Falls der aktuelle Knoten mit dem gesuchten Element markiert ist, ist es im Baum enthalten. Andernfalls suchen wir rekursiv beim linken Kindknoten, falls das gesuchte Element kleiner ist, als die Markierung des Knotens; ist das gesuchte Element größer, suchen wir rekursiv beim rechten Kindknoten. // Repraesentiert einen sortierten markierten Binaerbaum public class SortedBinTree { private TreeNode root; public SortedBinTree() { root = null; // prueft, ob ein Element im Baum enthalten ist public boolean contains( int element) { return contains(root, element); private boolean contains( TreeNode node, int element) { if ( node == null) { return false; if ( element < node. getmark()) { // kleinere Elemente links suchen return contains( node. getleft(), element); else if ( element > node. getmark()) { // groessere Elemente rechts suchen return contains( node. getright(), element); else { // gefunden! 7

return true; Einfügen in binären Suchbaum Damit die Suche korrekt funktioniert, muss auch hier wieder eine entsprechende Invariante, nämlich die Sortiertheit, sicher gestellt werden. Neue Knoten werden immer als Blätter eingefügt. Die Position des Blattes wird durch den Wert des neuen Eintrags festgelegt. Um Elemente in einen SortedBinTree einzufügen wählen wir das folgende algorithmische Vorgehen: Ist der Baum noch leer, wird der erste Eintrag als Wurzel des Baums hinzugefügt. Ein Knoten wird in den linken Unterbaum der Wurzel eingefügt, wenn sein Wert kleiner gleich ist als der Wert der Wurzel; in den rechten, wenn er größer ist. Dieses Verfahren wird rekursiv fortgesetzt, bis die Einfügeposition bestimmt ist. Beispiel: Beim Einfügen von in den skizzierten Baum wird - angefangen bei der Wurzel - die Markierung des Knotens mit verglichen. ist kleiner als 45; daher wird das Einfügen rekursiv auf dem linken Kindknoten fortgeführt; ist größer als 22; daher wird das Einfügen rekursiv auf dem rechten Kindknoten fortgeführt; ist kleiner als 42; da der Knoten mit Markierung 42 keinen linken Kindknoten hat, wird ein neuer Knoten mit Markierung als linker Kindknoten hier eingefügt. 45 22 57 17 42 52 65 49 Die Implementierung folgt diesem rekursiven Schema: 8

public void add( int element) { root = add(root, element); private TreeNode add( TreeNode node, int element){ if ( node == null) { return new TreeNode( element); else if ( element <= node. getmark()) { node. setleft( add( node. getleft(), element)); else { return node. setright( add( node. getright(), node; element)); Die Hilfsmethode add gibt jeweils die Referenz auf die neue Wurzel des Teilbaums nach Einfügen von element zurück. Damit ist es nicht nötig, zu unterscheiden, ob die Wurzel oder der linke oder rechte Teilbaum aktualisiert werden muss. Bemerkungen Die Reihenfolge des Einfügens bestimmt das Aussehen des sortierten markierten Binärbaums. Die folgenden Bäume sind durch die Einfügereihenfolge 2!! 1bzw.1!! 2bzw.1! 2! enstanden. 2 1 1 1 2 2 Wie man bei dem letzten der drei Beispiel sieht, entartet der Baum zur linearen Liste, wenn die Elemente in einer sortierten Reihenfolge einfügt werden. Eine solche Entartung kann man vermeiden, indem man den Suchbaum während des Einfügens umstrukturiert. Balancierte Bäume stellen sicher, dass sich für jeden Knoten die Tiefe des linken Teilbaums und die Tiefe des rechten Teilbaums nur um eine bestimmte Wert unterscheidet. Löschen in binärem Suchbaum Um Elemente aus einem binären Suchbaum zu entfernen, suchen wir zunächst den Eintrag im Baum. Haben wir den entsprechenden Knoten gefunden, machen wir folgende Fallunterscheidung: Knoten hat keine Kinder Der zu löschende Knoten wird einfach entfernt (d.h. die Referenz im Elternknoten wird durch null ersetzt). Knoten hat genau ein Kind Der Kindknoten wird an die Stelle gesetzt, an der der zu löschende Knoten war. 9

Knoten hat zwei Kinder Der zu löschende Knoten wird durch den am weitesten rechts liegenden Knoten seines linken Teilbaums ersetzt. 2 Dieser Knoten muss dann aus dem entsprechenden Teilbaum entfernt werden. Betrachten wir einmal folgenden Baum: 7 9 1 6 8 12 Nach Löschen von Element 7: 6 9 1 8 12 /** entfernt ein Vorkommen von element aus dem Baum */ public void remove( int element) { root = remove(root, element); /** Entfernt ein Vorkommen von element aus dem Teilbaum mit Wurzel node und liefert die Wurzel des veränderten Teilbaums */ private TreeNode remove( TreeNode node, int element) { if ( node == null) { return null; if ( element < node. getmark()) { TreeNode newleft = remove( node. getleft(), element); node. setleft( newleft); return node; else if ( element > node. getmark()) { TreeNode newright = remove( node. getright(), element); node. setright( newright); return node; else { // zu loeschendes Element gefunden if ( node. getleft() == null) { // wenn der linke Teilbaum leer ist, dann nur den rechten nehmen return node. getright(); else if ( node. getright() == null) { // analog zu node. left == null 2 Alternativ: den am weitesten links liegenden Knoten seines rechten Teilbaums 10

return node. getleft(); else { // wenn der Knoten zwei Kinder hat, den Knoten mit dem groessten // Element aus dem linken Teilbaum suchen: TreeNode maxnodeleft = maxnode( node. getleft()); // Die Markierung von diesem Knoten nehmen wir fuer den aktuellen: node. setmark( maxnodeleft. getmark()); // Und dann loeschen wir das Element aus dem linken Teilbaum: node. setleft( remove( node. getleft(), node. getmark())); return node; Frage 4: Ergänzen Sie die Implementierung der Methode TreeNode maxnode( TreeNode node). 2 Anwendungsbeispiel: Syntaxbäume Um die Struktur eines Wortes einer formalen Sprache darzustellen, werden Syntaxbäume verwendet. Die rekursive Art der Sprache, die oft durch eine kontextfreie Grammatik beschrieben ist, hat dabei ihre Entsprechung in der rekursiven Natur des Baums. In Kapitel 2 haben wir Syntaxbäume bereits kennengelernt. Wir betrachten hier den Ausschnitt für die arithmetischen Ausdrücke der Femto-Sprache. N = {Ausdruck T = {zahl, (, ), +, * 8 < = : Ausdruck! zahl ( Ausdruck + Ausdruck ) ( Ausdruck * Ausdruck ) Für den Ausdruck ((2 + 6) 4) ergibt sich folgender Syntaxbaum: 9 = ; Ausdruck ( Ausdruck * Ausdruck ) ( Ausdruck + Ausdruck ) 4 2 6 Der Syntaxbaum spiegelt direkt die Produktionen der Grammatik wider. Er beschreibt die Struktur des Ausdrucks in allen Details; so ist für jedes Terminalsymbol ein Blattknoten vorhanden. Für die Auswertung des Ausdrucks werden einige der Terminalsymbole, wie die Klammern, nicht benötigt. 11

Für die Analyse und Auswertung von Ausdrücken verwenden wir daher alternativ einen abstrakter Syntaxbaum, in dem diese für die weitere Verarbeitung unnötigen Knoten entfernt sind. Beispiel Für den Ausdruck (((5 2) + (4 2)) + ) ergibt sich folgender abstrakter Syntaxbaum: + + * * 5 2 4 2 2.1 Traversieren von Bäumen Es gibt verschiedene Möglichkeiten die Knoten eines Baums zu durchlaufen (traversieren). Ein Baum kann zum Beispiel zuerst in die Tiefe (depth-first, Tiefensuche) durchlaufen werden oder zuerst in der Breite (breadth-first, Breitensuche). Wir betrachten hier nur die Tiefensuche, für die es drei Varianten gibt: Vorordnung (preorder) 1. Betrachte den aktuellen Knoten. Das Vorgehen zum Durchlauf in Vorordnung ist: 2. Durchlaufe (rekursiv) den linken Teilbaum.. Durchlaufe (rekursive) den rechten Teilbaum. Das Ergebnis der Traversierung des Syntaxbaums oben ist: + + * 5 2 * 4 2 Diese Schreibweise eines arithmetischen Ausdrucks wird auch Präfix-Notation oder polnische Notation genannt. Nachordnung (postorder) 1. Durchlaufe (rekursiv) den linken Teilbaum. 2. Durchlaufe (rekursive) den rechten Teilbaum.. Betrachte den aktuellen Knoten. Das Vorgehen zum Durchlauf in Nachordnung ist: Das Ergebnis der Traversierung des Syntaxbaums oben ist: 5 2 * 4 2 * + + Diese Schreibweise eines arithmetischen Ausdrucks wird auch Postfix-Notation oder umgekehrte polnische Notation genannt. 12

Inordnung (inorder) Das Vorgehen zum Durchlauf in Inordnung ist: 1. Durchlaufe (rekursiv) den linken Teilbaum. 2. Betrachte den aktuellen Knoten.. Durchlaufe (rekursive) den rechten Teilbaum. Das Ergebnis der Traversierung des Syntaxbaums oben ist: 5 * 2 + 4 * 2 + 2.2 OO-Modellierung von Syntaxbäumen Die abstrakten Syntaxbäume für arithmetischen Ausdrücke können wir nun folgendermaßen in einem OO-Modell umsetzen: Ein arithmetischer Ausdruck ist vom Typ AExpr. Er soll Methoden bereitstellen zur Auswertung sowie zur Darstellung als String in den verschiedenen Notationen (Präfix-/Postfix-Notation, Inorder-Notation). Eine Summe, ein Produkt und eine int-konstante sind verschiedene Arten von arithmetischen Ausdrücken. Die Auswertung einer int-konstante soll den zugehörigen int-wert liefern. Bei Produkt und Summe wird zunächst der linke und rechte Teilausdruck ausgewertet und diese Teilergebnisse zusammen mit dem jeweiligen Operator ausgewertet. 1 // Schnittstelle fuer Arithmetische Ausdruecke 2 interface AExpr { // Wert des Ausdrucks 4 int evaluate(); 5 // String in Vorordnung 6 String printpreorder(); 7 // String in Nachordnung 8 String printpostorder(); 9 // String in Inordnung 10 String printinorder(); 11 1 class Sum implements AExpr { 2 private AExpr left; private AExpr right; 4 5 Sum ( AExpr left, AExpr right) { 6 this. left = left; 7 this. right = right; 8 9 10 public int evaluate() { 11 return left. evaluate() + right. evaluate(); 12 1 14 public String printpreorder() { 15 return "+ " + left. printpreorder() + " " + right. printpreorder(); 16 17 18 public String printpostorder() { 1

19 return left. printpostorder() + " " + right. printpostorder() + " +"; 20 21 22 public String printinorder() { 2 return left. printinorder() + " + " + right. printinorder() ; 24 25 26 public String tostring() { 27 return "(" + left. tostring() + " + " 28 + right. tostring() + ")"; 29 0 1 class Const implements AExpr { 2 private int value; 4 Const ( int value) { 5 this. value = value; 6 7 8 public int evaluate() { 9 return value; 10 11 12 public String printpreorder() { 1 return Integer. tostring( value); 14 15 16 public String printpostorder() { 17 return Integer. tostring( value); 18 19 20 public String printinorder() { 21 return Integer. tostring( value); 22 2 24 public String tostring() { 25 return Integer. tostring( value); 26 27 Im Gegensatz zu Prä- und Post-Notation, geht bei der Inorder-Notation für die Ausdrücke verloren, wie die ursprüngliche Baumstruktur und damit die ursprüngliche Klammerung des Ausdrucks war. Wir ergänzen die Implementierung daher noch um eine tostring()-methode, die den Ausdruck vollständig geklammert darstellt. 14