7.2 Suchbäume = Bäume zur Repräsentation linear geordneter Mengen Binäre Bäume: • unbalanciert • balanciert: AVL-Baum Rot-Schwarz-Baum Vielweg-Suchbäume: • 2-3-Baum • B-Baum • B*-Baum .... alp3-7.2 1 7.2.1 Binäre Suchbäume Beispiel: { 1, 3, 4, 5, 7, 9, 11 } kann dargestellt werden als Abstraktionsfunktion 5 3 1 Invariante: 7 4 11 9 alp3-7.2 Die Wurzel ist größer als alle Elemente im linken Teilbaum und kleiner als alle Elemente im rechten Teilbaum, und die Teilbäume sind ebenfalls binäre Suchbäume. 2 Suchen: Iterativ von der Wurzel nach unten, wobei an jedem Scheideweg die Entscheidung aufgrund der Suchbaum-Eigenschaft getroffen wird. Einfügen: ebenso, mit Anhängen eines neuen Blatts (falls Element noch nicht vorhanden) Achtung: Auch hier ist die Abstraktionsfunktion nicht injektiv: ein und dieselbe Menge kann durch verschiedene Bäume dargestellt werden: MO DI SA MI DO alp3-7.2 MI DO SO DI FR SA FR MO SO 3 7.2.1.1 Suchbaum funktional module TreeSets(TreeSet, contains, add, remove, card, traverse) where data Ord t => TreeSet t = E | N (TreeSet t) t (TreeSet t) abs E = {} -- pseudo-Haskell abs(N l x r) = abs l ++ {x} ++ abs r inv E = True inv(N l x r) = all(<x)(abs l) && all(>x)(abs r) && inv l && inv r contains ... alp3-7.2 4 contains E x = False contains(N l y r) x | x<y = contains l x | x==y = True | x>y = contains r x add x E = N E x add x(N l y r) | x<y = | x==y = | x>y = E N (add x l) y r N l y r N l y (add x r) card E = 0 card(N l _ r) = 1 + card l + card r -- cf. 6.2.1 flatten E = [] -- inorder -- cf. 6.4.1 flatten(N l x r) = flatten l ++ [x] ++ flatten r -- traversieren, liefert Elemente in sortierter Reihenfolge! alp3-7.2 5 remove ... ? 9 2 2 4 4 8 8 6 5 alp3-7.2 6 5 7 7 (falls nur rechter Teilbaum: entsprechend) 6 remove ... ? 9 2 12 4 8 6 5 alp3-7.2 7 7 remove ... ? (oder spiegelbildlich) 2 9 2 12 4 8 4 6 8 5 6 5 alp3-7.2 7 12 7 Nachteil: drohende Unausgeglichenheit 8 remove ... ? 9 2 12 4 8 6 5 alp3-7.2 7 9 remove ... ? 9 2 8 12 4 2 12 4 8 6 5 alp3-7.2 6 7 5 7 (oder spiegelbildlich) 10 remove x E = E remove | | -| | | alp3-7.2 x(N l y r) x<y = N(remove x l) y r x>y = N l y(remove x r) x==y l==E = r r==E = l otherwise = N l' (max l) r where l' = remove(max l)l max(N l x E) = x max(N l x r) = max r 11 7.2.1.2 Suchbaum imperativ Effiziente Implementierung des Suchbaums typischerweise unter Verzicht auf zusätzliche Datenabstraktion („abstrakte Menge als konkretes Geflecht dargestellt“) ! class TreeSet<T extends Comparable<T>> implements Set<T> { protected class Node { T value; Node left, right; Node(T v) { value = v; } } protected Node root = null; // inv ... * // abs root = { x | x<- trav root} // where trav n = if n==null then [] // else trav(n.l) ++ [n] ++ trav(n.r) public boolean contains(T x) ... public void add(T x) ... ..... } *Übung - Baumstruktur sicherstellen und Duplikate ausschließen! alp3-7.2 12 public void add(T x) throws NullPointerException { if(x==null) throw new NullPointerException(); Node n = new Node(x); if(root==null) { root = n; return; } Node current=root; // iterative navigation do{ int compare = current.value.compareTo(x); if(compare==0) return; // x in set if(compare>0) if(current.left == null) { current.left = n; return; } else current = current.left; else/*compare<0*/ if(current.right == null) { current.right = n; return; } else current = current.right; }while(true); } alp3-7.2 13 public boolean contains(T x) ... ähnlich, etwas einfacher public void remove(T x) ... schwieriger, vergleiche 7.2.1.1 public int card() ... rekursiv, vergleiche 7.2.1.1 ( daher empfiehlt sich, card als zusätzliches Attribut zu führen!) public void traverse(Operation<T> op) ... interner Iterator, rekursiv, vergleiche 6.4.1.1 public Iterator<T> iterator() ... externer Iterator, vergleiche 6.4.3 Die Iteratoren beachten die Ordnung auf T. alp3-7.2 14 7.2.1.3 Komplexität Suchen, Einfügen, Löschen - alle Operationen verfolgen einen Weg von der Wurzel nach unten (d.i. kein Traversieren!). Die Länge eines solchen Wegs ist bei vollständigen Bäumen mit n Knoten höchstens log2(n+1) - 1 (6.1.3.3). Der Aufwand für die Operationen ist also bei vollständigen Bäumen O(log n) - selbst im ungünstigsten Fall. alp3-7.2 15 7.2.1.3 Komplexität Suchen, Einfügen, Löschen - alle Operationen verfolgen einen Weg von der Wurzel nach unten (d.i. kein Traversieren!). Die Länge eines solchen Wegs ist bei vollständigen Bäumen mit n Knoten höchstens log2(n+1) - 1 (6.1.3.3). Der Aufwand für die Operationen ist also bei vollständigen Bäumen O(log n) - selbst im ungünstigsten Fall. Aber: je ungleichgewichtiger der Baum ausfällt, desto höher kann er werden; im Extremfall ist die Höhe n - z.B. wenn beim Einfügen der Werte in sortierter Reihenfolge ein Kette entsteht: DI DO FR alp3-7.2 MI ... 16 Aufwand für erfolglose Suche: Baum ist vollständig Schritte log2(n+1) Komplexität O(log n) entartet: Kette n O(n) zufällig (Mittelwert für große n) ≈ 1,39 log2 n - 1,85 O(log n) ! (siehe z.B. Güting, 4.2) alp3-7.2 17 7.2.2 Ausgeglichene Bäume (balanced trees) ... sind „möglichst“ vollständig, mit garantierter Komplexität O(log n) Def.: Ein binärer Suchbaum heißt k-ausgeglichen (k ∈ N), wenn für jeden Knoten die Höhen seiner beiden Teilbäume um höchstens k voneinander abweichen. alp3-7.2 18 7.2.2.1 AVL-Bäume (Adelson-Velskij/Landis 1962) Def.: Ein AVL-Baum ist ein 1-ausgeglichener Suchbaum. Bemerkung 1: Ein vollständiger Baum (6.1.2) ist ein AVL-Baum. Bemerkung 2: Ein AVL-Baum ist nicht notwendig vollständig, z.B. +1 -1 +1 +1 Höhendifferenz: Höhe linker Teilbaum minus Höhe rechter Teilbaum Bemerkung 3: Ein AVL-Baum ist ein Kompromiss zwischen einem beliebigen Baum und einem vollständigen Baum. alp3-7.2 19 Abstraktionsfunktion: wie bei allen binären Suchbäumen Invariante: inv aus 7.2.1.1 wird zu avl erweitert (mit h t = Höhe von t ) : avl E = True avl(N l x r) = inv(N l x r) && -- from 7.2.1.1 abs(h l - h r) <= 1 && avl l && avl r Aufrechterhaltung der Invariante: 1. Einfügen/Löschen wie bekannt ... 2. ... außerdem gegebenenfalls ausgleichen (rebalancing). alp3-7.2 20 x -2 -1 -1 Wenn ein Knoten aus dem Gleichgewicht gerät, ist die Höhe seines größeren Teilbaums um 1 gewachsen. Nur die Knoten auf dem Weg vom eingefügten Blatt zur Wurzel können betroffen sein; der erste solche Knoten sei x. alp3-7.2 21 x -2 -1 -1 Der x-Baum kann so rearrangiert werden, dass er wieder die alte Höhe hat (denn er war nicht vollständig besetzt!). Alle Knoten von x bis zur Wurzel sind damit wieder balanciert (denn sie waren es auch vorher). alp3-7.2 Somit genügt es, den x-Baum auszugleichen. 22 Fall 1: x A <x alp3-7.2 -2 y -1 B >x <y C >y 23 Fall 1: x A <x „Rotation“ -2 y y -1 B >x <y x C A >y <x B >x <y C >y ... und spiegelbildlich ebenso alp3-7.2 24 Fall 1: x A <x „Rotation“ -2 y y +1 B >x <y x C A >y <x B >x <y C >y ... scheitert aber bei Erweiterung von B ! alp3-7.2 25 Fall 2a,b: x -2 +1 z +1 y A B1 alp3-7.2 B2 C 26 „Doppelrotation“ Fall 2a,b: x y A z B1 x B2 -2 +1 C z +1 x alp3-7.2 z y -1 A B1 y B2 C A B1 ... und spiegelbildlich ebenso B2 C 27 Präzisierung mit Haskell (vgl. 7.2.1.1): module TreeSets(TreeSet, add, remove, contains, ...) where data Ord t => TreeSet t = E | N (TreeSet t) t (TreeSet t) contains ..... -- wie für einfache Suchbäume l(N l _ r) = l r(N l _ r) = r bal E = 0 -- balance bal(N l _ r) = h l - h r add x E = N E x E add x(N l y r) = if abs(bal new)<2 then new else rot new where new | x<y = N(add x l) y r | x==y = N l y r | x>y = N l y(add x r) alp3-7.2 28 rot t | bal t == -2 && bal(r t) == -1 where N a x(N b y c) = t | bal t == 2 && bal(l t) == 1 where N(N c y b)x a = t = N(N a x b) y c -- Fall 1 = N c y(N b x a) -- gespiegelt | bal t == -2 && bal(r t) == 1 = N(N a x b1) y (N b2 z c) where N a x(N(N b1 y b2) z c) = t -- Fall 2 | bal t == 2 && bal(l t) == -1 = N(N c z b2) y (N b1 x a) where N(N c z(N b2 y b1)) x a = t -- gespiegelt ! Prägnant, aber ineffizient - nur als Spezifikation, nicht als Implementierung geeignet. (Geschickter: add-Version, die new samt der Balance ermittelt.) alp3-7.2 29 Löschen von Knoten: erfordert i.a. wiederholtes Ausgleichen mehrerer Unterbäume Effiziente imperative Implementierung mit erweiterter Repräsentation: die Balance in jedem Knoten permanent speichern, Rückverkettung zum Eltern-Knoten. ( Abstraktion wenig geändert, Invariante deutlich erweitert) alp3-7.2 30 Ungünstigste Komplexität aller Operationen ist O(h). Wie groß kann h - in Abhängigkeit von n - schlimmstenfalls werden? (Zur Erinnerung: h(n) = log2(n+1) - 1 bei vollständigem Baum.) Äquivalent: wie klein kann n - in Abhängigkeit von h - schlimmstenfalls werden? Die minimale Knotenanzahl eines AVL-Baums bezeichnen wir mit N(h): alp3-7.2 31 N(0) = 1 N(1) = 2 N(2) = 4 N(3) = 7 N(4) = 12 alp3-7.2 allgemein N(i) = 1 + N(i-1) + N(i-2) 32 Zur Erinnerung - die Fibonacci-Zahlen: F(k) = F(k-1) + F(k-2) , F(0) = 0 , F(1) = 1 Beh.: N(i) = F(i+3) - 1 Bew.: (vollständige Induktion) I. i=0: N(0) = 1 = 2 - 1 = F(3) - 1 i=1: N(1) = 2 = 3 - 1 = F(4) - 1 II. Die Behauptung gelte bis zu einem bestimmten i≥1. Dann ist N(i+1) = 1 + N(i) + N(i-1) alp3-7.2 (nach Definition von N) = 1 + F(i+3) - 1 + F(i+2) - 1 (nach Induktionsvor.) = F(i+4) - 1 (nach Definition von F) QED. 33 Für jeden AVL-Baum mit n Knoten und Höhe h gilt also n ≥ N(h) = F(h+3) - 1 , also F(h+3) ≤ n+1 also h ≤ ?? Explizite Darstellung der Fibonacci-Zahlen: F(i) = ( Φi - Φi ) / √5 mit Φ, Φ = (1 ± √5)/2 ≈ 1,62 (-0.62) Somit gilt ( Φh+3 - Φh+3 ) / √5 ≤ n+1 also Φh+3 ≤ √5 ( n + 1 + Φh+3/√5 ) ≤ √5 (n + 3/2) |.....| ≤ 1/2 ( denn Φ ≈ -0.62 ) alp3-7.2 34 Logarithmieren ergibt h + 3 ≤ logΦ √5 + logΦ (n + 3/2) h ≤ logΦ n + C h ≤ (logΦ 2) (log2 n) + C ≈ 1,44 der Baum ist höchstens 44% höher (vgl. S. 17!) als ein vollständiger Baum mit gleicher Knotenzahl garantierte Komplexität O(log n) für Suchen/Einfügen (und auch für Löschen) alp3-7.2 35 7.2.2.2 Rot-Schwarz-Bäume (Bayer 1972) = binäre Suchbäume mit folgender Invariante: Jeder Knoten ist entweder rot oder schwarz. null gilt als schwarzes Blatt. Die beiden Kinder eines roten Knotens sind schwarz. Alle Wege von einem gegebenen Knoten x zu einem Blatt (null) enthalten die gleiche Anzahl schwarzer Knoten - genannt Schwarz-Höhe des Knotens s(x) (ohne x selbst). alp3-7.2 36 w h=5 alp3-7.2 s(w) = 3 37 Beh.: Die Höhe h eines Rot-Schwarz-Baums mit n eigentlichen Knoten (ohne null) ist höchstens 2 log2 (n+1) (also höchstens 100% höher als ein vollständiger Baum mit gleicher Knotenzahl; vgl. S. 17, S. 35). Bew.: Wir betrachten einen Baum mit Wurzel w und SchwarzHöhe s(w). Da alle Wege von der Wurzel zu einem null-Blatt mindestens s(w) eigentliche Knoten enthalten, ist der Baum eine Erweiterung eines vollständigen Baums der Höhe s(w)-1 und hat somit (6.1.3) mindestens 2s(w) - 1 eigentliche Knoten. Weil jeder rote Knoten schwarze Kinder hat, gilt s(w) ≥ h/2. Somit ist n ≥ 2s(w) - 1 ≥ 2h/2 - 1 , also n+1 ≥ 2h/2 , also h ≤ 2 log2 (n+1) QED. alp3-7.2 38 Repräsentation: - Geflecht - Färbung - schwarze Wurzel - Verweis auf Vorgänger alp3-7.2 Suchen: Einfügen: mit Umfärben und Rotieren Löschen: mit Umfärben und Rotieren 39 Einfügen eines Knotens x: Schritt 1: Einfügen wie bei einfachem Suchbaum, rot färben; fertig, wenn Vater schwarz. Schritt 2: Solange x≠Wurzel und Vater rot: falls auch Onkel rot: Vater und Onkel schwärzen, Großvater röten, x = Großvater; sonst (Onkel ist schwarz): falls x rechter Sohn: x = Vater, Linksrotation x; (x ist linker Sohn) Vater schwärzen, Großvater röten, Rechtsrotation Großvater. (Schleife bricht ab!) Schritt 3: Wurzel schwärzen. alp3-7.2 40 Java Collections Framework in java.util: interface SortedSet<E> extends Set<E> ... class TreeSet<E> implements SortedSet<E> ... verwendet Rot-Schwarz-Baum; die Iteratoren beachten die Ordnung auf E. E extends Comparable<E> wird nicht gefordert: der Benutzer kann wählen zwischen den Konstruktoren public TreeSet() public TreeSet(Comparator<? super E> c) (5.4.3, S. 10) ... Bei müssen die eingefügten Elemente Comparable sein. Bei wird mit Hilfe eines Comparator-Objekts verglichen, das eine Methode int compare(T x1, T x2) bereitstellen muss. alp3-7.2 41 Einige bemerkenswerte Methoden von SortedSet : <T> T[] toArray(T[] a) (von Set geerbt) traversiert die Menge gemäß der vorgegebenen Ordnung, speichert die Elemente im Feld a oder - falls a nicht groß genug ist - in einem neu erzeugten Feld und liefert dieses Feld SortedSet<E> subSet(E fromElement, E toElement) liefert eine Sicht (view) auf die Menge, die die Elemente fromElement (inklusive) bis toElement (exklusive) enthält; jede Änderung in der Menge wirkt sich auch in der Sicht aus - und umgekehrt! Iterator<E> descendingIterator() (wie der Name sagt) alp3-7.2 42 7.2.3 B-Bäume (Bayer/McCreight 1972) Def.: Vielweg-Suchbaum (multi-way search tree): Typ B<T> mit linear geordnetem T: ( ) ∈ B<T> Seien xi ∈ T mit xi < xi+1 (i=1,2,...,n; n>0) und bk ∈ B<T> (k=0,1,..,n) . Dann ist b = (b0, x1, b1, x2, ... ,xn, bn) ∈ B<T> genau dann, wenn mit x0 = A (z.B. -∞), xn+1 = Ω (z.B. +∞) gilt: ∀0≤i≤n ∀y∈α(bi) xi < y < xi+1 mit α( ) = { } α( b0, x1, b1, x2, ... ,xn, bn ) = {x1,...,xn} ∪ ∪0≤i≤n α(bi) alp3-7.2 43 Bemerkung: Damit wird einerseits ein Baum-Modell, andererseits eine (abstrakte) Repräsentation für Mengen spezifiziert - mit Abstraktionsfunktion α. Die Definition enthält die Invariante. Haskell: alp3-7.2 data Ord t => Btree = Node[t][Btree t] inv ... abs ... 44 Bemerkung: Damit wird einerseits ein Baum-Modell, andererseits eine (abstrakte) Repräsentation für Mengen spezifiziert - mit Abstraktionsfunktion α. Die Definition enthält die Invariante. Haskell: data Ord t => Btree = Node[t][Btree t] inv ... abs ... (vergleiche mit 7.2.1.1, S. 4!) o 10 o 20 o o4o7o o5o alp3-7.2 o 15 o 17 o o8o9o o 27 o o 22 o 23 o 24 o o 21 o 45 Spezialfälle: n=1 überall im Baum: Binärer Suchbaum B-Baum der Ordnung m (m≥1): a) Für jedes Nichtblatt sind alle Teilbäume nichtleer. b) Alle Wege von der Wurzel zu einem Blatt sind gleich lang. c) Jeder Knoten - außer der Wurzel - enthält zwischen m und 2m Werte. Motivation: externe Speicherung in Blöcken + Ausgeglichenheit 2-3-Baum: B-Baum der Ordnung 1 Motivation: Ausgeglichenheit alp3-7.2 2-4-Baum: ist kein B-Baum, aber verwandt mit Rot-Schwarz-Baum (s.u.) 46 Beispiel: B-Baum der Ordnung 2: o 11 o o 16 o 21 o 25 o o5o8o o1 o 2 o 3 o 4 o o6o7o alp3-7.2 o 9 o 10 o o 12 o 13 o 14 o o 17 o 18 o 19 o 20 o o 22 o 23 o 24 o 47 Wie hoch wird ein B-Baum im ungünstigsten Fall? Minimale Belegung: m Werte in jeder Nichtwurzel, 1 Wert in Wurzel: nmin = 1 + l + r Im linken Teilbaum (und rechts entsprechend): l = m + m(m+1) + m(m+1)2 + ... + m(m+1)h-1 = m ( 1 + (m+1) + ... + (m+1)h-1 ) = m ( (m+1)h - 1 ) / ( (m+1) - 1 ) = (m+1)h - 1 Also gilt n ≥ nmin = 1 + l + r = 2(m+1)h - 1 also (m+1)h ≤ (n+1)/2 und somit h ≤ logm+1 ((n+1)/2) = (logm+1 2) (log2 (n+1) - 1) = O(log n) 1 0,63 0,50 0,43 0,39 . . . . . alp3-7.2 48 Mengenoperationen auf B-Bäumen: Suchen: Von der Wurzel nach unten, mit Einschachteln (!) in jedem Knoten (dort Aufwand ≤ log2 2m) Einfügen: Wenn Element nicht bereits vorhanden, endet Suche in Blatt; dort einfügen, bei Überlauf Blatt splitten! Löschen: Wenn Element in Nichtblatt gefunden wird, zunächst Nachfolger (in Blatt!) mit gefundenem Wert vertauschen. Wert in Blatt löschen, bei Unterlauf mit NachbarBlättern ausgleichen oder Blätter verschmelzen. Wegen h ≤ O(log n) stets garantierte Komplexität O(log n). alp3-7.2 49 Überlauf-Behandlung beim Einfügen: Fall 1: Nichtwurzel läuft über (zunächst Blatt): y z x1 . . . . a . . . . x2m x2m+1 alp3-7.2 50 Überlauf-Behandlung beim Einfügen: Fall 1: Nichtwurzel läuft über (zunächst Blatt): y z x1 . . . . a . . . . x2m x2m+1 y xm+1 z x1 . . . xm * xm+2 . x2m+1 Höhe bleibt unverändert! Wenn * überläuft, dort splitten usf. . . . alp3-7.2 51 Fall 2: Wurzel läuft über: xm+1 x1 . . . . a . . . . x2m x2m+1 x1 . . . xm xm+2 . x2m+1 Baum zerfällt durch Splitten in zwei Teilbäume, die durch neue Wurzel zusammengefasst werden. Höhe wächst um 1. (Löschen: siehe z.B. Güting 8.1) alp3-7.2 52