Was bisher geschah rekursive Datenstrukturen: I lineare Datenstrukturen: I I I I verkettete Liste Stack Queue hierarchische Datenstrukturen: Bäume I I I allgemeine Bäume Binäre Bäume Unäre Bäume = Listen 132 Wiederholung ADT Menge Ziel: Verwaltung einer (Teilmenge einer total geordneten) Menge Extra: sortierte Ausgabe ADT (Spezifikation): I Sorten: Bool, Element, Menge (kurz für MengehElementi), Folge (kurz für FolgehElementi) I Signatur: emptyset : isempty : Menge → add : Menge × Element → remove : Menge × Element → contains : Menge × Element → sorted : Menge → I Menge Bool Menge Menge Bool Folge Axiome, z.B. ∀s ∈ Menge, ∀E ∈ Element): contains(add(s, e), e) = t, contains(remove(s, e), e) = f, isempty(add(s, e)) = f, add(add(s, e), e0 ) = add(add(s, e0 ), e), add(add(s, e), e) = add(s, e), . . . head(sorted(s)) < head(tail(sorted(s))), . . . 133 Wiederholung: ADT Binärbaum mit Knotenmarkierungen vom Typ Element I Sorten: Bool, Element, BinTree (kurz für BinTreehElementi), I Signatur: ⊥: isEmpty : BinTree → Node : Element × BinTree × BinTree → Left, Right : BinTree → Val : BinTree → t, f : I BinTree Bool BinTree BinTree Element Bool Axiome, z.B. ∀v ∈ Element ∀t1 ∈ BinTree ∀t2 ∈ BinTree: 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 134 Binäre Suchbäume Binärer Baum t hat die Suchbaum-Eigenschaft gdw. für jeden Teilbaum t 0 = Node(x, l, r ) von t gilt: I für jeden Schlüsselwert y eines inneren Knotens von l gilt y <x I für jeden Schlüsselwert v 0 eines inneren Knotens von r gilt x <y Ergebnis der Inorder-Durchquerung jedes binären Suchbaumes ist eine aufsteigend sortierte Folge Sortierte Ausgabe aller im Suchbaum mit n Knoten enthaltenen Daten in O(n) 135 Suche in binären Suchbäumen Spezifikation: gegeben: binärer Suchbaum t (Wurzel) Schlüsselwert x gesucht: Knoten t 0 in t mit Val(t 0 ) = x, ⊥, falls t keinen Knoten mit dem Schlüsselwert x enthält contains(t,x): if (x = Val(t) || isEmpty(t)) return x else if x < Val(t) return contains(Left(t), x) else return contains(Right(t), x) 136 Laufzeit der Suche Laufzeit von contains(t,x) abhängig von Anzahl n der Knoten in t: I falls t einen Knoten t 0 mit Val(t 0 ) = x enthält: Tiefe des Knotens t 0 I falls t keine Knoten t 0 mit Val(t 0 ) = x enthält: Tiefe des Baumes t 0 höchstens Tiefe des Baumes t (log n ≤ tiefe(t) ≤ n) Suche im Suchbaum t in O(tiefe(t)) (i.A. schneller als lineare Suche in einer Folge) 137 Anwendung von Suchbäumen Binäre Suchbäume sind geeignet zum Speichern und schnellen Wiederfinden von Daten anhand ihnen zugeordneten Schlüsselwerten Binäre Suchbäume: I Implementierung des ADT Menge für linear geordnete Mengen I Implementierung von Wörterbüchern: Daten mit zugeordnetem Schlüsselwert (aus einer linear geordneten Menge) Operationen: contains, add, remove z.B. Telefonbucheinträge über Namen (alphabetisch geordnet), Studenten über Studentennummern 138 Extremwerte in binären Suchbäumen Spezifikation: gegeben: binärer Suchbaum t (Wurzel) gesucht: Knoten in t mit dem minimalen Schlüsselwert, d.h. Knoten s, wobei für jeden Knoten r gilt Val(s) ≤ Val(r) ⊥, falls t = ⊥ Minimaler Schlüsselwert binären Suchbaum steht im äußeren linken Knoten minimum(t): if isEmpty(t) return Nil else if Left(t) = Nil return t else return minimum(Left(t)) Minimum lässt sich ohne Schlüsselvergleich bestimmen. Laufzeit höchstens Tiefe des Baumes t (log n ≤ tiefe(t) ≤ n) Minimum-Suche im Suchbaum mit n Knoten in O(tiefe(t)) Maximum analog 139 Einfügen in binäre Suchbäume Spezifikation: gegeben: binärer Suchbaum t mit den Schlüsseln {x1 , . . . , xn } Schlüsselwert x gesucht: binärer Suchbaum t 0 mit den Schlüsseln {x1 , . . . , xn , x} add(t,x): if isEmpty(t) return Node(x,Nil,Nil) else if x < Val(t) return add (Left(t),x) else return add (Right(t),x) 140 Iteriertes Einfügen Beim Einfügen der Elemente der Menge {2, 3, 5, 7} in verschiedenen Reihenfolgen in ⊥ entstehen i.A. veschiedene Bäume Beispiel (Tafel): I 3,7,5,2 I 5,3,7,2 I 2,3,5,7 Daraus lässt sich ein Sortierverfahren ableiten. Sortieren durch Einfügen in binären Suchbaum: 1. schrittweises Einfügen aller Elemente einer Menge in einen zu Beginn leeren binären Suchbaum 2. Sortierte Ausgabe durch Inorder-Durchquerung des so entstandenen binären Suchbaumes 141 Laufzeit Einfügen Idee: neues Blatt mit Schlüsselwert x wird dort eingefügt, wo es der Suchalgorithmus finden würde selbe Laufzeit wie Suche: höchstens Tiefe des Baumes t (log n ≤ tiefe(t) ≤ n) Einfügen in einen binären Suchbaum mit n Knoten in O(tiefe(t)) 142 Löschen aus binären Suchbäumen Spezifikation: gegeben: binärer Suchbaum t mit den Schlüsseln {x1 , . . . , xn } Schlüsselwert x gesucht: binärer Suchbaum t 0 mit den Schlüsseln {x1 , . . . , xn } \ {x} mögliche Fälle: 1. x kommt nicht in t vor: Ergebnis t 0 = t 2. x ist Schlüsselwert eines Knotens s in t mit zwei leeren Kindern: Löschen des Knotens s (Ersetzen durch ⊥) 3. x ist Schlüsselwert eines Knotens s in t mit einem leeren Kind: Ersetzen des Knotens s durch sein einziges nichtleeres Kind 4. x ist Schlüsselwert eines Knotens s in t mit zwei nichtleeren Kindern: Tausch der Schlüsselwerte in s und dem linken äußeren Knoten r des rechten Kindes von s (Minimum des rechten Kindes) Löschen des Knotens r (rekursiv) Warum hat der so entstandene Baum die Suchbaumeigenschaft? Laufzeit für Löschen aus binärem Suchbaum t: O(tiefe(t)) 143 Laufzeiten I contains I add I remove in O(tiefe(t)) Laufzeiten in Abhängigkeit von der Knotenzahl n = size(t): Extremfälle: I für Pfade (entartete Bäume) tiefe(t) = size(t) Laufzeit der Operationen O(n) I für balancierte Bäume tiefe(t) = log size(t) Laufzeit der Operationen O(log n) 144 Balancierte binäre Suchbäume Laufzeit für Suche, Einfügen, Löschen in Baum t mit n Knoten: O(tiefe(t)) Ziel: Laufzeit für Suche, Einfügen, Löschen O(log(n)) Idee: balancierte Suchbäume, in denen die Höhe jedes Blattes (etwa) gleich ist 145 Vollständig balancierte Bäume Binärer Suchbaum t heißt vollständig balanciert, wenn in jedem Knoten Node(x, l, r ) in t gilt: |size(l) − size(r )| ≤ 1 Tiefe jedes Knotens ≤ blog(n)c + 1 Beispiel (Tafel) 146 Operationen in vollständig balancierten Bäumen I Suche O(log(n)) I Einfügen und Löschen: zunächst Einfüge- und Löschoperation für binäre Suchbäume danach muss vollständige Balance wieder hergestellt werden (Rebalancieren) i.A. aufwendig 147 AVL-Bäume (Adelson-Velskii, Landis, 1962) Binärer Suchbaum t heißt AVL-Baum, wenn in jedem Knoten Node(x, l, r ) in t gilt: |tiefe(r ) − tiefe(l)| ≤ 1 (AVL-Eigenschaft) Optimierung: Speichern der Balance tiefe(r ) − tiefe(l) in jedem Knoten erlaubt schnellen Test I Suche O(log(n)) I Einfügen und Löschen: zunächst Einfügeoperationen für binäre Suchbäume danach Wiederherstellung der AVL-Eigenschaft 148 Rebalancieren in AVL-Bäumen nach Einfügen (als neues Blatt) oder Löschen (rekursives Ersetzen durch einziges Kind oder Minimum im rechten Teilbaum) Verletzung der AVL-Eigenschaft im Knoten (x, l, r ) mit |tiefe(l) − tiefe(r )| = 2 evtl. Verletzung in mehreren Vorgängern des eingefügten Knotens Fälle vor dem Einfügen: I I Balance 0: tiefe(l) = tiefe(r ) nach Einfügen keine Verletzung der AVL-Eigenschaft Balance −1: tiefe(l) = tiefe(r ) + 1 I I I nach Einfügen in r keine Verletzung der AVL-Eigenschaft nach Einfügen in l evtl. Verletzung der AVL-Eigenschaft Balance +1: tiefe(l) + 1 = tiefe(r ) I I nach Einfügen in l keine Verletzung der AVL-Eigenschaft nach Einfügen in r evtl. Verletzung der AVL-Eigenschaft (symmetrisch) 149 (Einfache) Rotation in AVL-Bäumen bei Verletzungen der AVL-Eigenschaft im tiefsten Knoten k der Form k = Node(x, Node(y , l, r ), s) mit tiefe(l) − 1 = tiefe(r ) = tiefe(s) (Balance von x: −2, Balance von y: −1) Ersetzung von k durch lrotate(k ) = Node(y , l, Node(x, r , s)) (Linksrotation) analog (symmetrischer Fall): Verletzungen der AVL-Eigenschaft im tiefsten Knoten k der Form k = Node(x, l, Node(y , r , s)) mit tiefe(l) = tiefe(r ) = tiefe(s) − 1 (Balance von x: +2, Balance von y: +1) Ersetzung von k durch rrotate(k ) = Node(y , Node(x, l, r ), s) (Rechtsrotation) 150 Doppel-Rotation in AVL-Bäumen bei Verletzungen der AVL-Eigenschaft im tiefsten Knoten der Form k = Node(x, Node(y , l, Node(z, r , s)), t) mit Balance x: −2, y: +1 Ersetzung von k durch lrrotate(k ) = Node(z, Node(y , l, r ), Node(x, s, t)) Links-Rechts-Rotation: zwei aufeinanderfolgende Rotationen: rrotate(Left(k )) danach lrotate(k ) symmetrischer Fall: k = Node(x, t, Node(y , Node(z, l, r ), s)) mit (Balance x: +2, y: −1) Ersetzung von k durch rlrotate(k ) = Node(z, Node(x, t, l), Node(y , r , s)) Rechts-Links-Rotation: zwei aufeinanderfolgende Rotationen: lrotate(Right(k )) danach rrotate(k ) 151 Laufzeit der Operationen in AVL-Bäumen Rebalancieren (Rotationen): O(1) Tiefe von AVL-Bäumen mit n Knoten: O(log(n)) I Suche: O(log(n)) I Einfügen: O(log(n)) I Löschen: O(log(n)) 152