B-Bäume – 1 B-Bäume (Bayer und McCreight, 1970) sind balancierte Suchbäume, die für das effiziente Arbeiten mit Magnetbändern oder anderen externen Speichern entwickelt wurden. Algorithmen und Datenstrukturen 11. Vorlesung Anders als Rot-Schwarz-Bäume (spez. binäre Suchbäume: ev. später) minimieren B-Bäume den Zugriff (Lesen/Schreiben) auf Magnetbänder. Viele Datenbanksysteme verwenden daher B-Bäume zum Speichern und Verwalten sehr großer Informationsmengen, die nicht in den Hauptspeicher passen. Karl-Heinz Niggl 13. Juni 2006 Der Verzweigungsgrad bei B-Bäumen (die Anzahl der Kinder eines Knotens) kann sehr groß sein (oft rund 1000) und variiert, bis auf die Wurzel, zwischen N −1 und 2N −1 Knoten, für eine Konstante N ≥ 2, die Ordnung des B-Baums. FG KTuEA, TU Ilmenau AuD – 13.6.2006 FG KTuEA, TU Ilmenau AuD – 13.6.2006 B-Bäume – 2 B-Bäume – 3 =⇒ Gegenüber Binärbäumen haben B-Bäume eine sehr geringe Tiefe. Bsp. B-Baum der Ordnung N = 3. Die Schüssel sind lateinische Großbuchstaben und wie üblich alphabetisch geordnet. =⇒ B-Bäume dienen hervorragend zur Implementierung von dynamischen Mengen: jede DynSet-Operation auf einem B-Baum mit n Knoten benötigt nur Zeit O(log n). Jeder innere Knoten x ungleich der Wurzel ist mit Ein innerer Knoten x mit n[x] Schlüsseln hat n[x]+1 Kinder. Nur die Wurzel darf auch weniger als N −1 Schlüssel besitzen. Alle Blätter liegen in der gleichen Schicht. root[T ] N −1 ≤ n[x] ≤ 2N −1 Schlüssel s1 ≤ . . . ≤ sx[n] M aus einer totalen Ordnung (U, <) beschriftet und besitzt n[x]+1 Kinder. D H Diese Schlüssel fungieren als Unterteilungspunkte der von x verwalteten Schlüssel in n[x]+1 Teilbereiche (-bäume). FG KTuEA, TU Ilmenau AuD – 13.6.2006 1 2 B C F FG KTuEA, TU Ilmenau G Q T J K L AuD – 13.6.2006 N P R S X V W Y Z 3 B-Bäume – 4 B-Bäume – 5 Frage: Warum werden Datenstrukturen mit Zugriff auf Magnetbänder anders bewertet als Datenstrukuren mit wahlfreiem (random-access) Hauptspeicherzugriff? Grundsätzlich: Der verfügbare Speicherplatz in einem Computer zerfällt grob in einen Hauptspeicher (üblicherweise aus einem Silikon-Chip bestehtend) und externe Speicher, kurz Disks (üblicherweise basierend auf Magnetbändern). Die Kosten für das Speichern eines Bits auf einem Silikon-Chip sind i.a. etwa doppelt so hoch wie bei Magnetbändern. Aber der externe Speicher ist i.a. mindestens doppelt so groß wie der Hauptspeicher. FG KTuEA, TU Ilmenau AuD – 13.6.2006 4 Ein typisches Laufwerk besteht aus verschiedenen Platten mit magnetisierbarer Oberfläche, die um eine gemeinsame Spindel mit konstanter Geschwindigkeit rotieren. Jede Platte besitzt einen Lese-/Schreibkopf und ist in sog. Spuren (Tracks) eingeteilt. Die Lese-/Schreibköpfe sind nur synchron innerhalb eines Zylinders bewegbar. Ein Zylinder ist dabei die Menge der übereinanderliegenden gleichen Spuren. =⇒ Der Zugriff auf externe Speicher ist viel langsamer, da mechanische Bewegung involviert ist: Rotation der Platten und Bewegung der Lese-/Schreibköpfe. FG KTuEA, TU Ilmenau AuD – 13.6.2006 5 B-Bäume – 6 B-Bäume – 7 Möglicherweise veraltet: Handelsübliche Disks vollführen 5400 bis 15000 Umdrehungen pro Minute. Um diesen Nachteil zu minimieren, wird Information in gleichgroßen Seiten (pages) auf mehreren Platten innerhalb eines b Lesen oder Schreiben Zylinders organisiert. 1 Diskzugriff = einer oder mehrerer Seiten. 1 Seite = b 211 bis 214 Bytes. Eine Umdrehung benötigt rund 8 Millisekunden. Das ist in etwa das Fünffache der üblichen Zugriffszeit (100 Nanosekunden) bei Silikonchips. Wenn man eine volle Umdrehung warten muß, damit der Lese/Schreibkopf einen bestimmten Spureintrag lesen kann, so kann man in dieser Zeitspanne beinahe 100000 Zugriffe auf den Hauptspeicher ausführen. Das Bewegen der Lese-/Schreibköpfe auf die angefragte Spur kostet 3–9 Millisekunden. FG KTuEA, TU Ilmenau AuD – 13.6.2006 6 Oft wird mehr Zeit für den Zugriff der Informationen einer Seite benötigt als diese dann im Hauptspeicher zu verarbeiten. Daher: Bei der Analyse der folgenden Implementierung von Wörterbuch-Operationen werden zwei Bestandteile der Laufzeit separt betrachtet: • Anzahl der Disk-Zugriffe • CPU-Rechenzeit FG KTuEA, TU Ilmenau AuD – 13.6.2006 7 B-Bäume – 8 B-Bäume – 9 Die Anzahl der Disk-Zugriffe = b Anzahl der gelesenen oder zurückgeschriebenen Seiten. Der Diskzugriff selbst ist keine Konstante, da er vom Abstand der zuletzt gelesenen und der angefragten Spur abhängt. Neben den üblichen Komponenten der Objekte x (Zeiger auf Knoten) in einem B-Baum existieren in unserem Pseudocode die folgenden Operationen: • disk-read(x): Liest das auf der Disk gespeicherte Objekt, auf das x zeigt, und holt es in den Hauptspeicher (erst dann kann auf die Komponenten von x zugegriffen werden!). Ein B-Baum speichert i.a. dramatisch viel mehr Informationen als der Hauptspeicher fassen kann. Daher werden von BBaum-Algorithmen nur soviel Seiten aus den Disks ausgelesen, in den Hauptspeicher geholt und bei Veränderung wieder zurückgeschrieben wie notwendig sind. Die folgenden Algorithmen sind so gestaltet, daß man zu jeden Zeitpunkt nur O(1) Seiten im Hauptspeicher benötigt. FG KTuEA, TU Ilmenau AuD – 13.6.2006 8 • disk-write(x): Schreibt das im Hauptspeicher befindliche Objekt, auf das x zeigt, auf die Disk zurück (nur anwenden, wenn sich x verändert hat!). Nun ist klar: Die Operationen disk-read und disk-write müssen möglichst effizient eingesetzt werden! FG KTuEA, TU Ilmenau AuD – 13.6.2006 9 B-Bäume – 10 B-Bäume – 11 Daher ist üblicherweise ein B-Baum-Knoten so groß wie eine Disk-Page und die Anzahl der Kinder eines Knotens ist daher auch durch die Größe einer Page beschränkt. Def. Ein B-Baum der Ordnung N , N ≥ 2, ist ein Baum T mit Wurzel root[T ] und den folgenden Eigenschaften: Bei einem großen B-Baum bewegt sich der Verzweigungsgrad üblicherweise zwischen 50 und 2000. Ein hoher Verzweigungsgrad reduziert drastisch sowohl die Höhe eines B-Baumes als auch die Anzahl der Disk-Zugriffe, um einen Schüssel zu finden. Bsp. Ein B-Baum der Höhe 2 mit Verzweigungsfaktor 1001, kann über einer Milliarde Schlüssel besitzen. Da die Wurzel stets im Hauptspeicher gehalten wird, braucht man maximal zwei Disk-Zugriffe, um einen Schlüssel zu finden. FG KTuEA, TU Ilmenau AuD – 13.6.2006 10 1. Jeder Knoten x (Zeiger auf) besitzt folgende Komponenten: n[x]: verwaltet die Anzahl der Schlüssel in x; für diese gilt: key1[x] ≤ . . . ≤ keyn[x][x] leaf[x]: Boolescher Wert mit leaf[x] = true ⇐⇒ x ist Blatt. 2. Jeder innere Knoten x besitzt n[x]+1 (Zeiger auf) Kinder c1[x], . . . , cn[x]+1[x]. Bei einem Blatt sind die Komponenten ci undefiniert (NIL). FG KTuEA, TU Ilmenau AuD – 13.6.2006 11 B-Bäume – 12 B-Bäume – 13 3. Jeder Schüssel keyi[x] besitzt einen linken Teilbaum Li[x] mit Wurzel ci[x] und einen rechten Teilbaum Ri[x] mit Wurzel ci+1[x] und es gilt folgende B-Baum-Eigenschaft: Bem. In einem B-Baum der Ordnung N besitzt jeder innere Knoten (außer der Wurzel) ≥ N und ≤ 2N Kinder. keys(Li[x]) ≤ keyi[x] ≤ keys(Ri[x]) für i = 1, . . . , n[x]. 4. Die Blätter von T liegen auf Schicht h(T ) := Höhe(T ). Bem. B-Bäume der Ordnung N ≥ 1 werden auch so definiert, daß jeder innere Knoten (außer der Wurzel) ≥ N und ≤ 2N Schlüssel und damit ≥ N +1 und ≤ 2N +1 Kinder besitzt. 5. Für jeden inneren Knoten x 6= root[T ] gilt: In einem solchen B-Baum der Ordnung N = 1 besitzt jeder innere Knoten (außer der Wurzel) zwei oder drei Kinder. Man spricht hier von einem 2-3-Baum. N −1 ≤ n[x] ≤ 2N −1 Für x = root[T ] mit T 6= NIL gilt: 1 ≤ n[x] ≤ 2N −1 FG KTuEA, TU Ilmenau AuD – 13.6.2006 In einem B-Baum der Ordnung N = 2 besitzt also jeder innere Knoten, mit Ausnahme der Wurzel, zwei, drei oder vier Kinder. Man spricht hier von einem 2-3-4-Baum. 12 FG KTuEA, TU Ilmenau AuD – 13.6.2006 13 B-Bäume – 14 B-Bäume – 15 Satz (Höhe v. B-Bäumen). Sei T ein B-Baum der Ordnung N mit n Schlüsseln. Dann gilt: h(T ) ≤ logN n+1 2 Konventionen für die folgenden Operationen b-tree-search, b-tree-create, b-tree-insert, b-tree-delete: • root[T ] befindet sich stets im Hauptspeicher. disk-read(root[T ]) ist damit überflüssig; disk-write(root[T ]) ist nur erforderlich, falls sich die Wurzel verändert hat. Beweis. Sei h := h(T ). Nach Def. gilt: root[T ] besitzt ≥ 1 und jeder andere innere Knoten ≥ N −1 Schlüssel. =⇒ Schicht 1 besitzt mindestens 2·N 0 Knoten. Schicht 2 besitzt mindestens 2·N 1 Knoten. Schicht 3 besitzt mindestens 2·N 2 Knoten . . . Schicht h besitzt mindestens 2·N h−1 Knoten. h X 2·N i−1 =⇒ n ≥ 1 + (N −1)· i=1 = 1 + 2·(N −1)· FG KTuEA, TU Ilmenau AuD – 13.6.2006 Nh − 1 = 2·N h − 1 N −1 • Für jeden Knoten (Zeiger) x, der als Argument an eine Prozedur übergebenen wird, muß vorher einmal disk-read(x) erfolgt sein. Beachte: Alle Operationen werden so implementiert, daß man sich stets von der Wurzel in Richtung eines Blattes bewegt. 14 FG KTuEA, TU Ilmenau AuD – 13.6.2006 15 B-Bäume – 16 B-Bäume – 17 b-tree-search(x, k) liefert einen Zeiger y auf einen Knoten sowie einen Index i mit keyi[y] = k, falls sich Schlüssel k im Teilbaum mit Wurzel x befindet, und NIL sonst. 1: 2: 3: 4: 5: 6: 7: 8: AuD – 13.6.2006 1.Fall: i ≤ n[x] und k = keyi[x]. OK! 2.Fall: x ist ein Blatt. OK! 3.Fall a): i = n[x] + 1 =⇒ k > keyn[x][x] und k kann sich höchstens im rechten Teilbaum Rn[x][x] mit Wurzel ci[x] befinden. OK! procedure b-tree-search(x, k) i←1 while (i ≤ n[x] ∧ k > keyi[x] do i←i+1 if (i ≤ n[x] ∧ k = keyi[x]) then return (x, i) if leaf[x] then return NIL else disk-read(ci[x]) b-tree-search(ci[x], k) FG KTuEA, TU Ilmenau Korrektheit: Nach Abbruch der while-Schleife. 3.Fall b): i ≤ n[x] und k ≤ keyi[x] =⇒ k kann sich höchstens im linken Teilbaum Li[x] mit Wurzel ci[x] befinden. OK! Laufzeit: Die Anzahl der Disk-Zugriffe ist O(h) = O(logN n); die CPU-Zeit beträgt O(N ·h) = O(N ·logN n). 16 FG KTuEA, TU Ilmenau AuD – 13.6.2006 17 B-Bäume – 18 B-Bäume – 19 b-tree-create(T ) liefert einen Zeiger T auf die Wurzel eines leeren B-Baums in CPU-Zeit O(1) und O(1) Disk-Zugriffen. Einfügen eines Schlüssels in B-Baum: Sei k ein Schlüssel und T ein B-Baum der Ordnung N . Wie bei Binärbäumen suchen wir zunächst ein geeignetes Blatt, um darin x einzufügen. Hilfsprozedur allocate-node() allokiert auf der Disk eine Page für einen neuen Knoten. allocate-node() benötigt keinen Aufruf disk-read. 1: 2: 3: 4: 5: 6: procedure b-tree-create(T ) x ← allocate-node() leaf[x] ← true n[x] ← 0 disk-write(x) root[T ] ← x FG KTuEA, TU Ilmenau AuD – 13.6.2006 18 Aber: Anders als bei Binärbäumen können wir nicht einfach ein neues Blatt erzeugen und dort k einfügen. Bedingung 4. als auch Bedingung ≥ N −1 Schlüssel“ wäre i.a. verletzt. ” Stattdessen fügen wir k in ein existierendes Blatt x ein. Falls x voll ist, verletzten wir dabei Bedingung ≤ 2N −1 Schlüssel“. ” Naive Lösung: Wir spalten x um Medianschlüssel keyN [x] in zwei neue Blätter mit N −1 Schlüssel; keyN [x] wandert als neuer Unterteilungspunkt in den Vaterknoten y von x. FG KTuEA, TU Ilmenau AuD – 13.6.2006 19 B-Bäume – 20 B-Bäume – 21 Problem: Nun könnte ein Overflow in y vorliegen. Also müsste man y ebenfalls aufspalten, u.s.w. Schlimmstenfalls würde die Behandlung eines entstehenden Overflows bis hoch zur Wurzel propagiert werden. Dies kann doppelt so viele Disk-Zugriffe als nötig verursachen! Clevere Lösung: Innerhalb des Suchpfades in T nach einem Einfügeblatt für x wird jeder volle Knoten aufgespalten. Bsp. Splitting eines Knoten in B-Baum der Ordnung N = 4. AuD – 13.6.2006 20 x ··· D W ··· ··· D S y = ci [x] P U V 7→ P T Q R U V T5 T6 T7 T8 T1 T2 T3 T4 root[T ] s H root[T ] r A D F H L N P T1 T2 T3 T4 T5 T6 T7 T8 FG KTuEA, TU Ilmenau 7→ r A D F L N P T1 T2 T3 T4 T5 T6 T7 T8 AuD – 13.6.2006 21 B-Bäume – 23 b-tree-split-child(x, i, y) erwartet als Input: • einen nicht vollen inneren Knoten x (im Hauptspeicher) • einen Index i • einen vollen Knoten y (im Hauptspeicher) mit y = ci[x]. Die Prozedur spaltet y in zwei Knoten y und z mit jeweils N−1 Schlüssel; der Medianschlüssel keyN [x] wird neuer Schüssel von x mit linkem Teilbaum Ty und rechtem Teilbaum Tz . Spalten der Wurzel: root[T ] wird zunächst zum einzigen Kind eines neuen, leeren Wurzelknotens s. root[T ] wird nun durch Aufruf b-tree-split-child(s, 1, root[T ]) gespalten. Dies ist die einzige Möglichkeit, h(T ) zu vergößern! AuD – 13.6.2006 T Bsp. Splitting der Wurzel eines B-Baums der Ordnung N = 4. B-Bäume – 22 FG KTuEA, TU Ilmenau W ··· y = ci [x] Q R S T1 T2 T3 T4 T5 T6 T7 T8 Wirkung: Keine Split-Operation erzeugt einen Overflow! Die Einfüge-Operation erfolgt in einer Bewegung von der Wurzel in Richtung eines Blattes. Ein Rücklauf (back up) ist nicht erforderlich. FG KTuEA, TU Ilmenau x 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: procedure b-tree-split-child(x, i, y) z ← allocate-node() leaf[z] ← leaf[y] n[z] ← N −1 for j ← 1 to N −1 do keyj [z] ← keyj+N [y] if not leaf[y] then for j ← 1 to N do cj [z] ← cj+N [y] n[y] ← N −1 for j ← n[x] + 1 downto i + 1 do cj+1[x] ← cj [x] ci+1[x] ← z for j ← n[x] downto i do keyj+1[x] ← keyj [x] keyi[x] ← keyN [y] disk-write(y); disk-write(z); disk-write(x) Laufzeit: CPU-Zeit O(N ) und O(1) Disk-Zugriffe 22 FG KTuEA, TU Ilmenau AuD – 13.6.2006 23 B-Bäume – 24 B-Bäume – 25 b-tree-insert(T, k) fügt Schlüssel k in den B-Baum T ein. Bsp. Einfügen in einen B-Baum der Ordnung N = 3. G M P Der Fall einer vollen Wurzel wird separat behandelt: Für nicht volle Knoten ist b-tree-insert-nonfull zuständig. procedure b-tree-insert(T, k) r ← root[T ] if n[r] = 2N −1 then s ← allocate-node() root[T ] ← s leaf[s] ← false; n[s] ← 0 c1[s] ← r b-tree-split-child(s, 1, r) b-tree-insert-nonfull(s, k) 10: else b-tree-insert-nonfull(r, k) 1: 2: 3: 4: 5: 6: 7: 8: 9: FG KTuEA, TU Ilmenau A C D E A B C D E A B C D E FG KTuEA, TU Ilmenau B-Bäume – 26 P B C D E J K L Einfügen von F: T N O Q R S A B FG KTuEA, TU Ilmenau X U V Y Z D E F J AuD – 13.6.2006 K L T N O Q R S J K N O J K N O U V Y Z X R S G M P T T T U V Y Z U V Y Z X Q R S AuD – 13.6.2006 25 V Y Z X U 1: 2: 3: 4: 5: procedure b-tree-insert-nonfull(x, k) i ← n[x] if leaf[x] then ⊲ Füge k in Blatt x ein. while i ≥ 1 und k < keyi[x] do ⊲ Korrekt, da x nicht voll! keyi+1[x] ← keyi[x]; i ← i−1 6: 7: keyi+1[x] ← k n[x] ← n[x]+1; disk-write(x) else while i ≥ 1 und k < keyi[x] do i ← i−1 ⊲ Suche nach geeignetem Teilbaum i ← i+1 ⊲ mit Wurzel ci[x]. disk-read(ci[x]) if n[ci[x]] = 2N −1 then b-tree-split-child(x, i, ci[x]) if k > keyi[x] then i ← i+1 b-tree-insert-nonfull(ci[x], k) 8: 9: 10: 11: 12: 13: 14: 15: P C G M R S B-Bäume – 27 Bsp. fortgesetzt A N O G M P Einfügen von Q: 24 G M K Einfügen von B: AuD – 13.6.2006 Einfügen von L: J X 26 FG KTuEA, TU Ilmenau AuD – 13.6.2006 27 B-Bäume – 28 B-Bäume – 29 Laufzeit: Die Anzahl der Disk-Zugriffe ist O(h) = O(logN n); die CPU-Zeit beträgt O(N ·h) = O(N ·logN n). Problem: Dadurch könnte nun in y ein Underflow vorliegen. Also müsste man auch y schlimmstenfalls über seinen Vater ausgleichen, u.s.w. Schlimmstenfalls würde die Behandlung eines Underflows bis hoch zur Wurzel propagiert werden. Löschen eines Schlüssels in einem B-Baum: Komplizierter, da zu löschender Schlüssel in einem beliebigen Knoten sein kann. Sei k ein Schlüssel und T ein B-Baum der Ordnung N . Dies kann doppelt so viele Disk-Zugriffe als nötig verursachen! Analog zum Einfügen muß nun beim Löschen von k aus einem Knoten x 6= root[T ] die Bedingung ≥ N −1 Schlüssel“ ” sichergestellt werden. Clevere Lösung: b-tree-delete(x, k) löscht Schlüssel k aus Teilbaum mit Wurzel x derart, daß vor jedem (rekursiven) Aufruf n[x] ≥ N gilt. Naive Lösung: Tritt nach dem Löschen von k ein Underflow auf, so holt man sich schlimmstenfalls einen Schlüssel aus dem Vaterknoten y und gleicht aus. Wirkung: b-tree-delete erzeugt keinen Underfow und erfolgt (ohne Rücklauf) in einer Bewegung von der Wurzel in Richtung eines Blattes. FG KTuEA, TU Ilmenau AuD – 13.6.2006 28 FG KTuEA, TU Ilmenau AuD – 13.6.2006 B-Bäume – 30 B-Bäume – 31 Arbeitsweise von b-tree-delete(x, k): (Ohne Pseudocode) 1.Fall: leaf[x], k ∈ keys(x) und n[x] ≥ N Bsp. Fall 2a) im Bild: i =⇒ Lösche k aus x. ··· k Für x = root[T ], k = key1[x] und n[x] = 1 ist T danach leer. a) Für den linken Teilbaum Li[x] von k mit Wurzel y := ci[x] gilt n[y] ≥ N . ··· x 30 · · · k′ ··· x y = ci [x] k′ Bestimme den Predecessor k ′ von k (= max keys(Li[x])). Lösche rekursiv k ′ aus Li[x] und ersetze k durch k ′. (Dies erfordert eine eigene, analoge Delete-Prozedur!) AuD – 13.6.2006 i y = ci [x] 2.Fall: ¬leaf[x], k = keyi(x) und n[x] ≥ N . FG KTuEA, TU Ilmenau 29 FG KTuEA, TU Ilmenau AuD – 13.6.2006 31 B-Bäume – 32 B-Bäume – 33 b) Für den rechten Teilbaum Ri[x] von k mit Wurzel z := ci+1[x] gilt n[z] ≥ N . c) k besitzt einen linken Teilbaum mit Wurzel y und einen rechten Teilbaum mit Wurzel z und n[y] = N −1 = n[z]. Bestimme den Successor k ∗ von k (= min keys(Ri[x])). Lösche rekursiv k ∗ aus Ri[x] und ersetze k durch k ∗. (Dies erfordert eine eigene, analoge Delete-Prozedur!) i i ··· k ··· x · · · k∗ · · · x z = ci+1 [x] z = ci+1 [x] Verschmelze y und z zu einem vollen Knoten y mit Medianschlüssel k (z wird dann freigegeben). Dann erfolgt Aufruf b-tree-delete(y, k). i y · · · k− k k+ · · · x ··· z ··· R k∗ FG KTuEA, TU Ilmenau AuD – 13.6.2006 32 FG KTuEA, TU Ilmenau B-Bäume – 34 Verschmelze y und z zu einem vollen, neuen Wurzelknoten y mit Medianschlüssel k (z wird dann freigegeben). Dann erfolgt Aufruf b-tree-delete(y, k). y x ··· R FG KTuEA, TU Ilmenau L AuD – 13.6.2006 z L R z L AuD – 13.6.2006 33 k y R 3.Fall: ¬leaf[x], k ∈ / keys[x] und n[x] ≥ N . Suche Wurzel ci[x] desjenigen Teilbaums Ti, der k enthalten muß (wenn k überhaupt im Baum sein soll). a) n[ci[x]] ≥ N . =⇒ Aufruf b-tree-delete(ci[x], k). b) n[ci[x]] = N −1 und Ti besitzt einen rechten Teilbaum mit Wurzel ci+1[x] und n[ci+1[x]] ≥ N . root[T ] ··· k y x B-Bäume – 35 d) x = root[T ], k = key1[x], n[x] = 1 und n[y] = N −1 = n[z] k · · · k− k+ · · · =⇒ Schlüssel ki := keyi[x] wandert in ci[x] hinab als letzter Schlüssel mit aus ci+1[x] geborgtem rechten Teilbaum L des ersten Schlüssels s aus ci+1[x]. Schlüssel s wandert nach oben in x anstelle von ki. Dann erfolgt Aufruf b-tree-delete(ci[x], k). z L 34 FG KTuEA, TU Ilmenau AuD – 13.6.2006 35 B-Bäume – 36 i x ··· ki · · · B-Bäume – 37 i ··· s ··· x i x ··· Ti s s+ · · · ··· ··· ki s+ i ki · · · L L L x ··· ··· Ti s− ··· ki + L R− R c) n[ci[x]] = N −1 und Ti besitzt einen linken Teilbaum mit Wurzel ci−1[x] und n[ci−1[x]] ≥ N . AuD – 13.6.2006 R− R d) n[ci[x]] = N −1 und Ti besitzt linken Teilbaum L mit Wurzel y := ci−1[x] und rechten Teilbaum R mit Wurzel z := ci+1[x] und n[y] = N −1 = n[z] =⇒ Schlüssel ki := keyi[x] wandert in ci[x] hinab als erster Schlüssel mit aus ci−1[x] geborgtem linken Teilbaum R des letzten Schlüssels s aus ci−1[x]. Schlüssel s wandert nach oben in x anstelle von ki. Dann erfolgt Aufruf b-tree-delete(ci[x], k). FG KTuEA, TU Ilmenau s ··· ··· · · · s− s + ··· Verschmelze ci[x] und z zu einem vollen Knoten ci[x] mit Medianschlüssel ki (z wird dann freigegeben). Dann erfolgt Aufruf b-tree-delete(ci[x], k). 36 FG KTuEA, TU Ilmenau AuD – 13.6.2006 B-Bäume – 38 37 B-Bäume – 39 Bsp. Löschen in einem B-Baum der Ordnung N = 3. Bsp. Fall 3d) im Bild: P i x C G M · · · ki− ki ki− · · · x A B L ci [x] R L T ci [x] ki D E F J K L N O Q R S V Y Z P C G M Laufzeit: Die Anzahl der Disk-Zugriffe ist O(h) = O(logN n); die CPU-Zeit beträgt O(N ·h) = O(N ·logN n). AuD – 13.6.2006 U R Löschen von F: Fall 1 FG KTuEA, TU Ilmenau X · · · ki− ki− · · · 38 A B FG KTuEA, TU Ilmenau D E J K L AuD – 13.6.2006 T N O Q R S X U V Y Z 39 B-Bäume – 40 B-Bäume – 41 Löschen von D: Fall 3b) Bsp. fortgesetzt C L P Löschen von M: Fall 2a) A B C G L T D E J K N O Q R S U V Y E J K P FG KTuEA, TU Ilmenau D E J K AuD – 13.6.2006 T N O Q R S U V Y Z E J K N O T X Q R S U V Y Z Löschen von B: Fall 3a) C L A B N O C L P Z A B Löschen von G: Fall 2c) X X Baum schrumpft A B T P Q R S E L P X U V Y Z A C 40 FG KTuEA, TU Ilmenau J K AuD – 13.6.2006 N O T X Q R S U V Y Z 41