Was bisher geschah I I Algorithmen Datentypen I I I I Abstrakter Datentyp: (funktionale) Signatur ΣF + Axiome Φ (Spezifikation) Konkreter Datentyp: ΣF -Algebra, die alle Axiome aus Φ erfüllt (d.h. Modell für Φ ist) (Realisierung) rekursive Datenstrukturen lineare Datenstrukturen: I I I einfach verkettete Liste Stack Queue 111 Wiederholung ADT Liste ADT Liste ohne Positionszugriff (Spezifikation, Auszug): Sorten: Bool, Element, Liste (hier kurz für ListhElementi) Signatur: nil : isEmpty : Liste → head : Liste → tail : Liste → contains : Liste × Element → cons : Element × Liste → append : Liste × Liste → Liste Bool Element Liste Bool Liste Liste Axiome . . . 112 Einfach verkettete Liste: rekursive Struktur induktive Definition: IA: Die leere Liste ist eine Liste. IS: Wird (ein Verweis auf) eine Liste an ein Element angehängt, entsteht eine Liste. rekursive Struktur einer einfach verketteten Liste L: ListhElementi = nil | cons ( Element, ListhElementi) class List<Element> { Element head; List<Element> tail; } 113 Einfach verkettete Liste: Durchquerungen Durchquerung aller Elemente: forward( List<Element> x ){ if ( x != null){ ... forward(x.tail) } } backward( List<Element> x ){ if ( x != null){ backward(x.tail) ... } } 114 Einfach verkettete Liste: size rekursiv: int size( List<Element> x ){ if ( x == null){ return 0; } else { return (1 + x.tail.size()); } } oder iterativ: int size(){ Element x = head; int s = 0; while ( x! = null){ x = x.tail; s++; } return s; } 115 Einfach verkettete Liste: contains contains(e,x): boolean contains(Element e, List<Element> x){ if ( x == null){ return false; } else { return (( x.val == e) || contains(e, x.tail)); } } 116 Einfach verkettete Liste: Laufzeiten I isEmpty, head, tail, cons: O(1) (Test oder Ersetzen von Verweisen am Anfang der Liste) I contains O(n) (Durchlauf aller Verweise bis gefunden) I remove O(n) (Suche und Ersetzen eines Verweises) I concat O(n) (Durchlauf aller Verweise der ersten Liste und Ersetzen eines Verweises) 117 Wiederholung: Bäume als Graphen Baum: azyklischer zusammenhängender Graph T = (V , E) hier außerdem I ausgezeichneter Knoten: Wurzel I gerichtete Kanten I Jeder Knoten hat höchstens einen Vorgänger. I Wurzel ist einziger Knoten ohne Vorgänger. I festgelegte Ordnung der Nachfolger (Kinder) jedes Knotens I Markierung der Knoten mit Werten, z.B. ∈ N Blatt: Knoten ohne Kinder (mit 0 Kindern) innerer Knoten: Knoten mit n > 0 Kindern 118 Eigenschaften von Bäumen Baum T = (V , E) |V | = |E| + 1 (Beweis durch Induktion) minimal zusammenhängend maximal kreisfrei I Zwischen je zwei Knoten existiert genau ein Pfad. I Spezialfall: Für jeden Knoten existiert genau ein Pfad zur Wurzel. Tiefe eines Knotens = Anzahl der Kanten zur Wurzel 119 Baumstrukturen Anwendungen z.B. in I Gliederung eines Buches (Teile, Kapitel, Abschnitte) I Unternehmenstruktur in der Informatik z.B. in I Struktur von Termen, z.B. arithmetische Terme, Formeln I Programmstruktur (Anweisungen, Blöcke, Unterprogramme) I Verzeichnisstruktur (Betriebssystem) I Prozessstruktur (Betriebssystem) I XML-Dokumente 120 Bäume induktive Definition: IA: der leere Baum ⊥ ist ein Baum IS: existiert ein n > 0 und sind t1 , . . . , tn Bäume und v ∈ M, dann ist auch node(v , (t1 , . . . , tn )) ein Baum graphische Darstellung (Tafel) rekursive Struktur eines Baumes: TreehElementi = ⊥ | Node (Element, ListhTreehElementii) class Tree<Element> { Element val; List<Tree<Element>> children; 121 Rekursive Funktionen auf Bäumen I Größe (Anzahl der Knoten) size(⊥) size(node(v , (t1 , . . . , tn ))) = 0 = 1+ n X size(ti ) i=1 I Tiefe (Länge des längsten Wurzel-Blatt-Pfades) height(⊥) height(node(v , (t1 , . . . , tn ))) = 0 = 1 + max {height(ti ) | i ∈ {1, . . . , n}} 122 Spezialfälle I Binärbäume (Grad aller inneren Knoten ∈ {1, 2}) I vollständige Binärbäume (Grad aller inneren Knoten 2) I Unärbäume (Grad aller inneren Knoten 1) Liste mit size = Länge 123 ADT Binärbaum mit Knotenmarkierungen vom Typ Element I Sorten: Bool, Element, BT (kurz für BinTreehElementi) I Signatur: ⊥: isEmpty : BT → Node : Element × BT × BT → Left, Right : BT → Val : BT → t, f : I BT Bool BT BT Element Bool Axiome, z.B. ∀v ∈ Element, t1 , t2 ∈ BT : isEmpty(⊥) : = t isEmpty(Node(v , t1 , t2 )) : = f Left(Node(v , t1 , t2 )) : = t1 Right(Node(v , t1 , t2 )) : = t2 Val(Node(v , t1 , t2 )) : = v 124 Rekursive Baumdurchquerungen (binär) Reihenfolge des Besuches aller Knoten eines Binärbaumes (z.B. zum Durchsuchen) BinTreehElementi = ⊥ | Node (Element, BinTreehElementi, BinTreehElementi) Preorder preorder(Node(v , l, r )) = v ◦preorder(l)◦preorder(r ) bei Ausgabe: v als Präfix Postorder postorder(Node(v , l, r )) = postorder(l)◦postorder(r )◦v bei Ausgabe: v als Postfix Inorder inorder(Node(v , l, r )) = inorder(l) ◦ v ◦ inorder(r ) bei Ausgabe: v als Infix 125 Rekursive Baumdurchquerungen (allgemein) TreehElementi = ⊥ | Node (Element, ListhBinTreehElementii) Preorder: preorder(Node(v , [t1 , . . . , tn ])) = v ◦ preorder(t1 ) ◦ · · · ◦ preorder(tn ) bei Ausgabe: v als Präfix Postorder: postorder(Node(v , [t1 , . . . , tn ])) = postorder(t1 ) ◦ · · · ◦ postorder(tn ) ◦ v bei Ausgabe: v als Postfix Inorder meist nicht sinnvoll 126 Rekursive Funktionen auf Bäumen Anzahl der Knoten: int size( BinTree<Element> x ){ if ( x == null){ return 0; } else { return (1 + size(x.left) + size(x.right)); } } Tiefe: int height( BinTree<Element> x ){ if ( x == null){ return 0; } else { return (1 + max(size(x.left), size(x.right))); } } 127 Binärbäume: contains boolean contains(Element e, BinTree<Element> x){ if ( x == null){ return false; } else { return ( ( x.val == e) || contains(e, x.left) || contains(e, x.right)); } } 128 Vollständige Binärbäume BinTree<int> fullBinTree(int n){ if ( n=0 ) { return nil; } else { return ( Node (n, fullBinTree(n-1) , fullBinTree(n-1) )); } } 129 Nichtrekursive Baumdurchquerungen (für Bäume beliebiger Knotengrade) Zwischenspeicher notwendig zur Verwaltung der Menge La der noch zu besuchenden Knoten Allgemeiner Durchquerungs-Algorithmus: 1. La = {s} (Wurzel s des zu durchquerenden Baumes) 2. solange La 6= ∅ wiederhole: Auswahl und Entfernen eines Knotens u aus La Einfügen aller Kinder von u in La 130 Nichtrekursive Baumdurchquerungen Datenstruktur für Zwischenspeicher La (entdeckte, aber noch nicht besuchte Knoten) bestimmt Durchquerungsreihenfolge. Tiefensuche Verwaltung von La als Stack Einfügen der Nachbarn: push I Auswahl des Knotens, der zuletzt in La eingefügt wurde (top / pop) (gleiche Reihenfolge wie preorder-Durchquerung) I I Breitensuche Verwaltung von La als Queue Einfügen der Nachbarn: enqueue I Auswahl des Knotens, der zuerst in La eingefügt wurde (dequeue) (level-order-Durchquerung) I I 131