Was bisher geschah I Algorithmen I Datentypen I I Abstrakter Datentyp (Spezifikation): (funktionale) Signatur ΣF + Axiome Φ Konkreter Datentyp (Realisierung, Implementierung): ΣF -Algebra, die alle Axiome aus Φ erfüllt (d.h. Modell für Φ ist) I ADT Menge I lineare Datenstrukturen: I ADT Folge (mit und ohne Positionszugriff) Realisierungen: I I I I I Arrays einfach verkettete Liste doppelt verkettete Liste ADT Stack, ADT Queue rekursive Datenstrukturen (Listen) 29 Wiederholung ADT ADT für Himmelsrichtungen Norden, Osten, Süden, Westen mit Operationen für Rechts- und Linksabbiegen und Umlenken Spezifikation des ADT Himmelsrichtungen: Sorten: Himmelsrichtung (HR) = {N, O, S, W} Signatur: N, O, S, W : rechts, links, um : HR → HR HR Axiome: rechts(N) = O, links(N) = W, um(N) = S, . . . ∀h ∈ HR : rechts(links(h)) = h, ∀h ∈ HR : links(rechts(h)) = h, ∀h ∈ HR : rechts(rechts(h)) = um(h), . . . in Haskell: data hr = N | O | S | W rechts :: hr -> hr rechts N = O ... 30 Wiederholung ADT Folge ADT Folge ohne Positionszugriff (Spezifikation, Auszug): Sorten: Bool, Element, Folge (hier kurz für FolgehElementi) Signatur: nil : head : Folge → tail : Folge → contains : Folge × Element → cons : Element × Folge → append : Folge × Folge → isEmpty : Folge → Folge Element Folge Bool Folge Folge Bool Axiome isEmpty(nil) = t, ∀x∀lisEmpty(cons(x, l)) = f, . . . Beispiel (Sorten und Signatur): Java-Interface List public interface List { Element head (); List tail (); ... } 31 Einfach verkettete Liste: rekursive Struktur induktive Definition einer einfach verketteten Liste von Elementen vom Typ a (polymorph): IA: Die leere Liste ist eine Liste von Elementen vom Typ a. IS: Ist e ein Element vom Typ a und l eine Liste von Elementen vom Typ a, dann ist auch das Paar (e, l) eine Liste von Elementen vom Typ a. rekursiver Datentyp einfach verkettete Liste von Elementen vom Typ a: in Haskell: data List a = Nil {} | Cons { head :: a, tail :: List a } oft kürzer (Nil 7→ [], Cons 7→ :, ohne Komponentennamen): data [a] = [] | a : [a] 32 Einfach verkettete Liste: Durchquerungen rekursive Durchquerungen aller Elemente in Haskell: forward :: [a] -> [a] forward l = case l of [] -> [] (x : xs) -> x : (forward xs) backward :: [a] -> [a] backward l = case l of [] -> [] (x : xs) -> (backward xs) ++ [x] 33 Einfach verkettete Liste: Durchquerungen rekursive Durchquerungen aller Elemente in Java: forward( List<Element> x ){ if ( x != null){ ... forward(x.tail) } } backward( List<Element> x ){ if ( x != null){ backward(x.tail) ... } } 34 Einfach verkettete Liste: size (rekursive Berechnung der Länge der Liste) in Haskell: size :: [a] -> Int size l = case l of [] -> 0 (x : xs) -> 1 + (size xs) in Java: int size( List<Element> x ){ if ( x == null){ return 0; } else { return (1 + x.tail.size()); } } 35 Einfach verkettete Liste: contains in Haskell: contains :: Eq a => [a] -> a -> Bool contains l y = case l of [] -> False (x : xs) -> (x == y) || (contains xs y) in Java: boolean contains(List<A> x, A y){ if ( x == null){ return false; } else { return (( x.val == y) || contains(x.tail, y)); } } 36 Einfach verkettete Liste: remove Entfernen aller Vorkommen eines Elementes aus einer Liste in Haskell: remove :: Eq a => [a] -> a -> [a] remove l y = case l of [] -> [] (x : xs) -> if (x == y) then (remove xs y) else x : (remove xs y) 37 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 von Verweisen bis Ende) I concat O(n) (Durchlauf aller Verweise der ersten Liste und Ersetzen eines Verweises) 38 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 39 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 40 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 41 Bäume induktive Definition: IA: der leere Baum Leaf ist ein Baum IS: existiert ein n > 0 und sind t1 , . . . , tn Bäume und v ∈ M, dann ist auch Branch(k , (t1 , . . . , tn )) ein Baum graphische Darstellung (Tafel) rekursive Struktur eines Baumes: in Haskell: data Tree a = Leaf | Branch { key :: a, sons :: [Tree a] } in Java: class Tree<A> { A val; List<Tree<A>> sons; } 42 Spezialfälle I Binärbäume (Grad aller inneren Knoten 2) I Unärbäume (Grad aller inneren Knoten 1) Liste mit size = Länge 43 ADT Binärbaum mit Knotenmarkierungen vom Typ Element I Sorten: Bool, Element, BT (kurz für BinTreehElementi) I Signatur: Leaf : isEmpty : BT → Branch : Element × BT × BT → left, right : BT → key : BT → add : BT × Element → contains : BT × Element → t, f : I BT Bool BT BT Element BT Bool Bool Axiome, z.B. ∀k ∈ Element, l, r ∈ BT : isEmpty(Leaf) : = t isEmpty(Branch(k , l, r )) : = f left(Branch(k , l, r )) : = l right(Branch(k , l, r )) : = r key(Branch(k , l, r )) : = k 44 Rekursive Funktionen auf Binärbäumen data BinTree a = Leaf | Branch {key::a, left::BinTree a, right::BinTree a} deriving Show I Größe (Anzahl der Knoten) size :: BinTree a -> Int size t = case t of Leaf -> 0 (Branch k l r) -> 1 + (size l) + (size r) I Höhe (Länge des längsten Wurzel-Blatt-Pfades) height :: BinTree a -> Int height t = case t of Leaf -> 0 (Branch k l r) -> 1 + max (height l) (height r) 45 Rekursive Durchquerungen von Binärbäumen Reihenfolge des Besuches aller Knoten eines Binärbaumes data BinTree a = Leaf | Branch {key::a, left::BinTree a, right::BinTree a} Preorder preorder :: BinTree a -> [a] preorder t = case t of Leaf -> [] Branch k l r -> [k] ++ (preorder l) ++ (preorder r) bei Ausgabe: Wurzel als Präfix Postorder postorder :: BinTree a -> [a] postorder t = case t of Leaf -> [] Branch k l r -> (postorder l) ++ (postorder r) ++ [k] bei Ausgabe: Wurzel als Postfix Inorder inorder :: BinTree a -> [a] inorder t = case t of Leaf -> [] Branch k l r -> (inorder l) ++ [k] ++ (inorder r) bei Ausgabe: Wurzel als Infix 46 Vollständige Binärbäume praktisch zur Erzeugung von Beispielen und Testen der Funktionen: Erzeugung vollständiger Binärbäume beliebiger Höhe Spezifikation der Funktion fullBinTree: V: Eingabe n ∈ N N: falls n > 0: vollständiger Binärbaum der Höhe n, sonst ⊥ in Haskell: fullBinTree :: Int -> BinTree Int fullBinTree n = if n <= 0 then Leaf else Branch n (fullBinTree (n-1)) (fullBinTree (n-1)) 47 Binärbäume: contains Spezifikation der Funktion contains: V: Eingabe (x1 , . . . , xn ) ∈ A∗ , y ∈ A N: ja, falls ∃i ∈ {1, . . . , |x|} : y = xi nein, sonst in Haskell: contains :: Eq a => contains t x = case Leaf -> (Branch k l r) -> BinTree a -> a -> Bool t of False (k == x) || contains l x || contains r x 48 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 49 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 (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 50