Kapitel 12: Induktive Datenstrukturen Felix Freiling Lehrstuhl für Praktische Informatik 1 Universität Mannheim Vorlesung Praktische Informatik I im Herbstsemester 2009 Folien nach einer Vorlage von H.-Peter Gumm, Philipps-Universität Marburg Überblick Induktive Datenbereiche Listen, verkettete Listen Doppelt verkettete Listen LinkedList Bäume, Binärbäume Praktische Informatik I, HWS 2009, Kapitel 12 Seite 2 Induktiv definierte Daten Die natürlichen Zahlen sind induktiv definiert 0 ist eine natürliche Zahl Wenn N eine natürliche Zahl ist, dann auch N+1 Binärzahlen kann man induktiv definieren Der leere String “ “ ist ein String Ist S ein String und z ein Zeichen, dann ist S z ein String ““, “a“, “b“, ..., “aa“, “ab“, ... , “ba“, “bb“, ... “aaa“, “aab“, ... Listen B0 und B1. Strings kann man induktiv definieren 0, 1, 00, 01, 10, 11, 000, 001, 010, 011, Jede der Ziffern 0 und 1 ist eine Binärzahl Ist B eine Binärzahl, dann auch 0, |, ||, |||, ||||, |||||, ||||||, ... die leere Liste [ ] ist eine Liste ist e ein Element und L = [ x1, …, xn ] eine Liste, dann ist auch cons(e,L) := [ e, x1, …, xn ] eine Liste. [ ], [ 2,3,5 ], [1,6, 19, 24, 0, 42 ] Binärbäume (ohne Blätter) Der leere Baum ist ein Binärbaum Sind B1 und B2 Binärbäume, dann auch Praktische Informatik I, HWS 2009, Kapitel 12 B1 B2 Seite 3 Listen, rekursiv definiert Eine Liste ist entweder die leere Liste oder sie hat public class Liste<E> { // Objektfelder: private E inhalt; private Liste rest; ein erstes Element und eine Rest-Liste Objektreferenz null bedeutet leere Liste new Liste<E>(e) ergibt Referenz auf einelementige Liste des Typs E Praktische Informatik I, HWS 2009, Kapitel 12 Liste definiert mittels Liste // Konstruktoren: public Liste(E e) { inhalt = e; rest = null; } public Liste(E e, Liste l) { inhalt = e; rest = l; } ... } Seite 4 ... und Test ... „verkettete Liste“ Praktische Informatik I, HWS 2009, Kapitel 12 Seite 5 Länge berechnen, typisch rekursiv Länge einer Liste: 1 falls Liste nur ein Element enthält sonst: Länge der Restliste +1 public class ... public int if (rest return } else { return } } ... } Liste<E> { laenge() { == null) { 1; 1+rest.laenge(); Wie programmiert man das mit einer Schleife? Praktische Informatik I, HWS 2009, Kapitel 12 Seite 6 hinten Anhängen (append) Hänge e hinten an die Liste an Typisch rekursiv: Falls Liste nur ein Element hat, dann hänge e an dieses Element an Ansonsten, hänge e ans Ende der Restliste an Einfach erweiterbar auf hintenAnhaengen(Liste x) Wie programmiert man das mit einer Schleife? Praktische Informatik I, HWS 2009, Kapitel 12 public void hintenAnhaengen(E e) { if (rest == null) { rest = new Liste(e); } else { rest.hintenAnhaengen(e); } } Seite 7 Suchen in einer Liste public boolean enthalten(E e) { if (inhalt.equals(e)) { return true; } else { if (rest == null) { return false; } else { return rest.enthalten(e); } } } Prüft, ob ein Element e in der Liste enthalten ist Wieder typisch rekursiv Praktische Informatik I, HWS 2009, Kapitel 12 Warum nicht inhalt == e ? Seite 8 Kopieren rekursiv: deep copy gegeben Listen a, b a = b; kopiert Referenzen a = b.kopie(); Soll eine komplette Kopie der Liste b erstellen Natürliche rekursive Formulierung Problem: Was ist, wenn inhalt auch eine Referenz ist? public Liste kopie() { if (rest == null) { return new Liste(inhalt); } else { return new Liste(inhalt, rest.kopie()); } } inhalt.kopie() aufrufen?! muss definiert sein, mehr dazu später Rekursives Kopieren alle Felder entlang der Referenzstrukturen nennt man deep copy Praktische Informatik I, HWS 2009, Kapitel 12 Seite 9 Leere Liste? Bisher leere Liste mittels einer Nullreferenz dargestellt Problem: Leere Liste und nichtleere Liste müssen unterschiedlich behandelt werden Liste a = null; Was ergibt a.laenge() ... ? Lösung: Verwende Listenkopf Liste ist jetzt immer ein Objekt (auch eine leere Liste) Bessere Lösung: Verwende abstrakte Klassen (siehe später) Praktische Informatik I, HWS 2009, Kapitel 12 Seite 10 Entfernen aus einer Liste? Gegeben eine Liste a, darin ein Element e Wie entferne ich e aus a? Um ein Element zu entfernen, benötigt man eine Referenz auf das davor liegende Listenelement Praktische Informatik I, HWS 2009, Kapitel 12 Seite 11 Doppelt verkettete Liste Idee: Speichere nicht nur Referenz auf die Restliste (Suffix) sondern auf den Präfix der Liste public class Liste<E> { // Objektfelder: private Liste praefix private E inhalt; private Liste suffix; // Konstruktor: public Liste(E e) { inhalt = e; praefix = null; suffix = null; } } public void entfernen(E e) { if (inhalt.equals(e)) { praefix.suffix = suffix; return; } if (suffix != null) { suffix.entfernen(e); } } Praktische Informatik I, HWS 2009, Kapitel 12 Seite 12 java.util.LinkedList Universelle Klasse für Listen Basiert intern auf doppelt verketteter Liste mit Kopf (header) Praktische Informatik I, HWS 2009, Kapitel 12 Seite 13 Vorteile induktiver Datenstrukturen Induktive (selbstbezügliche) Datenstrukturen haben keine (wirkliche) Größenbeschränkung Im Gegensatz zu Arrays kann eine Liste beliebig groß machen Und zwar ohne Umkopieren Induktive Datenstrukturen ermöglichen elegante, einfache, rekursive Methoden Nachteil: Solche Datenstrukturen sind „langsamer“ als Arrays Man kann z.B. nicht direkt an einen Index springen In Java-Bibliothek gibt es verschiedene Implementierungen von Listen: java.util.ArrayList : Listen auf Basis von Arrays java.util.LinkedList : Listen auf Basis von doppelt verketteten Listenelementen Klassen bieten die gleiche Schnittstelle an, sind aber anders implementiert Implementierung hat Auswirkung auf die Effizienz der Programme (siehe Kapitel 16) Praktische Informatik I, HWS 2009, Kapitel 12 Seite 14 Baum (mathematisch) Ein Baum B = (V, E) ist ein Tupel bestehend aus einer Menge V = { v1, v2, ..., vn } von Knoten und einer Menge E ⊆ V × V von (gerichteten) Kanten Definition aus Kapitel 6 mit folgenden Eigenschaften: Induktive Definition: Genau ein Knoten hat keine eingehende Kante (Wurzel). Alle Knoten ausser der Wurzel haben genau eine eingehende Kante. Es gibt keine Zyklen (Rundwege aus Kanten). Ein “leerer Baum” ist ein Baum Gegeben Bäume B1, ..., Bd, dann ist ein neuer Knoten mit ausgehenden Kanten nach B1 und Bd ebenfalls ein Baum Wir betrachten zunächst Bäume, die in ihren Knoten Werte speichern (analog zu Listen) und die maximal zwei „Unterbäume“ haben Praktische Informatik I, HWS 2009, Kapitel 12 Seite 15 Experiment Wie eine Liste, bei der jedes Listenelement zwei Suffixe hat Praktische Informatik I, HWS 2009, Kapitel 12 Seite 16 Baum-Terminologie Die Knoten enthalten Datenelemente (bei uns: ganze Zahlen) Ein Pfad ist eine Liste von Knoten im Baum, in der aufeinander folgende Knoten durch Kanten verbunden sind Die Länge eines Pfades ist die Anzahl der Knoten im Pfad Die Pfadlänge eines Baumes ist die Summe aller Pfadlängen von einem Knoten zur Wurzel. Jeder Knoten (außer der Wurzel) hat genau einen Elternknoten Die durch ausgehende Kanten mit ihm verbundenen Knoten sind seine Kinder Ein Knoten, der keine Kinder hat, heißt Blattknoten Jeder Knoten ist die Wurzel eines Unterbaumes Die Tiefe eines Baumes ist die Länge des längsten Pfades von der Wurzel zu einem Blatt Ein Baum heißt vom Rang d, wenn jeder Knoten maximal d Kinder hat Ein Baum heißt Binärbaum genau dann, wenn er vom Rang 2 ist, d.h. wenn jeder Elternknoten maximal zwei Kinder hat. Praktische Informatik I, HWS 2009, Kapitel 12 Beobachtung: Zwischen der Wurzel des Baumes und jedem anderen Knoten gibt es genau einen Pfad. Seite 17 Doppelte Rekursion: Tiefe Berechnung der Tiefe eines Binärbaums Tiefe = Länge des längsten „Asts“ von der Wurzel aus Warum geht das so nicht? Praktische Informatik I, HWS 2009, Kapitel 12 Seite 18 Programmtechnische Darstellung von Bäumen Bisher implementiert: Binärbaum Wie implementiert man allgemeine Bäume von Rang d? Problem: Jeder Knoten kann eine andere Anzahl von Kindern haben, maximal d. Lösung 1: Parent-Link-Darstellung Lösung 2: Explizite Speicherung der Kinder In jedem Knoten wird nur der Zeiger auf seinen Elternknoten gespeichert. Ist in der Regel nur sinnvoll, wenn der Baum nur von unten nach oben durchlaufen werden soll In einer verketteten Liste oder einem Array Falls ein Array benutzt wird, muss maximaler Rang des Baumes bei der Implementierung bekannt sein Lösung 3: Listendarstellung der Kinder Jeder Knoten hat zwei Zeiger: einen zu seinem ganz linken Kind, falls es ein solches gibt einen zu seinem rechten Geschwister oder zum Elternknoten, falls er keinen rechten Geschwister hat Allgemeiner Baum dargestellt als Binärbaum Praktische Informatik I, HWS 2009, Kapitel 12 Seite 19 Ordnungen von Bäumen Eine Ordnung (order) ist eine Abbildung eines Baumes auf eine lineare Struktur ("Plattklopfen des Baumes") Man bildet eine eindeutigen Knotenfolge derart, dass jeder Knoten genau einmal darin vorkommt Bei Binärbäumen unterscheidet man vier wichtige Ordnungen: 1 A Die Ordnung gibt an, an welcher Stelle die Wurzel im Bezug zum linken und rechten Unterbaum vorkommen soll. Preorder Inorder Postorder Die Definition der Ordnung ist rekursiv. Beispiel: Preorder Erst Wurzel, dann linker Teilbaum, dann rechter Teilbaum Praktische Informatik I, HWS 2009, Kapitel 12 2 B 3 C 5 D 4 E Preorder: A B C E D Seite 20 Inorder und Postorder 1. Linker Unterbaum Regel: 2. Wurzel 2. Rechter Unterbaum 3. Rechter Unterbaum 3. Wurzel 4 5 A A 2 B 1 C 1. Linker Unterbaum Regel: 3 B 5 D 3 E Inorder: C B E A D Praktische Informatik I, HWS 2009, Kapitel 12 1 C 4 D 2 E Postorder: C E B D A Seite 21 Beispiel: Operatorbaum * 5 Inorder: 5 * (((9 + 8) * (4 * 6)) + 7) + 7 * * + 9 8 4 Klammer auf, wenn man eine Ebene absteigt, Klammer zu, wenn man eine Ebene aufsteigt 6 Postorder: 598+46**7+* Darstellung ohne Klammern trotzdem eindeutig Der Sytnaxbaum dient zur maschineninternen Darstellung von int-Ausdrücken Darstellung des Baumes mittels Inorder ergibt unsere vertraute Darstellung Darstellung des Baumes mittels Postorder ergibt „polnische Notation“ Kann verwendet werden, um mittels eines Stack den Wert des Ausdrucks zu berechnen (siehe Übung) Praktische Informatik I, HWS 2009, Kapitel 12 Seite 22 Operationen auf rekursiven Daten Funktionen auf induktiv definierten Daten sind rekursiv am einfachsten Beispiel : male() Hilfsfunktion male(int n) Malt einen Baum ab Spalte n Rote Verbindungslinien nur zur weiteren Hervorhebung In Blatt: println(info); In Knoten: rechts.male(n+5); einruecken(n,“+“); links.male(n+5); Praktische Informatik I, HWS 2009, Kapitel 12 Seite 23 Zusammenfassung Induktive Datenstrukturen referenzieren sich selbst Standardbeispiele: Bäume, Listen Induktive Datenbereiche bearbeitet man natürlich rekursiv Praktische Informatik I, HWS 2009, Kapitel 12 Seite 24