B-Bäume Udo Kelter 22.10.2005 Zusammenfassung dieses Lehrmoduls B-Bäume bzw. B*-Bäume sind eine der wichtigsten Erfindungen der Informatik. Sie implementieren den generischen abstrakten Datentyp “Verzeichnis”. Im Gegensatz zu den hauptspeicherorientierten binären Bäumen sind B-Bäume plattenorientiert. Weiterhin weisen sie die Besonderheit auf, nicht zu degenerieren und Suchzeiten in der Größenordnung von log(n) zu garantieren. B*-Bäume verbessern die Suchgeschwindigkeit weiter, indem die Nutzdaten nur noch in den Blättern des Suchbaums gespeichert werden, wodurch die Indexknoten einen höheren Verzweigungsgrad haben können. Vorausgesetzte Lehrmodule: keine Stoffumfang in Vorlesungsdoppelstunden: 1 1.0 B-Bäume 2 Inhaltsverzeichnis 1 Historischer Hintergrund 3 2 Verzeichnisse 2.1 Generische ADT . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Der generische ADT directory [S,I] . . . . . . . . . . . . . 3 3 5 3 B-Bäume 3.1 Grundlegende Implementierungsentscheidungen 3.2 Vielweg-Suchbäume . . . . . . . . . . . . . . . 3.3 Merkmale von B-Bäumen . . . . . . . . . . . . 3.4 Primärschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 7 9 10 4 Algorithmen 4.1 Suche . . 4.2 Einfügung 4.3 Löschung 4.4 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 11 13 15 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 B*-Bäume 16 Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Glossar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 18 18 c 2005 Udo Kelter Stand: 22.10.2005 Dieser Text darf für nichtkommerzielle Nutzungen als Ganzes und unverändert in elektronischer oder gedruckter Form beliebig weitergegeben werden und in WWW-Seiten, CDs und Datenbanken aufgenommen werden. Jede andere Nutzung, insb. die Veränderung und Überführung in andere Formate, bedarf der expliziten Genehmigung. Die jeweils aktuellste Version ist über http://kltr.de erreichbar. B-Bäume 1 3 Historischer Hintergrund B-Bäume sind eine der wichtigsten Erfindungen der Informatik. BBäume entstanden im Kontext der Entwicklung relationaler Datenbanken [BaM72]; ohne B-Bäume wären die heute allgegenwärtigen relationalen Datenbanksysteme nicht denkbar. In der Architektur von DBMS realisieren sie eine Direktzugriffsmethode für Speichersätze. B-Bäume sind Suchbäume, sie gehören zu den grundlegenden Datenstrukturen, sie werden daher oft in Informatik-Grundvorlesungen vorgestellt. Im Gegensatz zu den hauptspeicherorientierten binären Bäumen sind B-Bäume plattenorientiert. Weiterhin weisen sie (ähnlich wie AVL-Bäume) die Besonderheit auf, nicht zu degenerieren; es ist garantiert, daß die Höhe eines B-Baums logarithmisch von der Zahl der enthaltenen Elemente abhängt. B-Bäume können im Detail recht unterschiedlich implementiert sein, sogar die Schnittstellen können viele fallspezifische Besonderheiten aufweisen. Um von diesen Besonderheiten zu abstrahieren, führen wir zunächst den Begriff generischer abstrakter Datentyp (gADT) ein und beschreiben einen B-Baum als einen gADT. Dieser gADT beinhaltet natürlich eine Operation, die in einem Datenbestand nach einzelnen Elementen sucht, daneben aber auch Operationen zum Einfügen und Löschen von Datenelementen. Das entscheidende Problem ist hierbei, die Balancierung des Suchbaums zu erhalten. In diesem Lehrmodul beschreiben wir die Techniken, die dieses Ziel erreichen. Abschließend gehen wir noch kurz auf B*-Bäume ein; diese optimieren die Suchgeschwindigkeit durch Trennung der Indexstrukturen von den Datenblöcken weiter. 2 2.1 Verzeichnisse Generische ADT B-Bäume realisieren eine Zugriffsstruktur, die man Verzeichnis nennt. “Verzeichnis” ist wiederum ein wichtiger generischer abstrakter Dac 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 4 tentyp. Ein generischer abstrakter Datentyp (gADT, auch Typkonstruktor genannt) ist ein abstrakter Datentyp, in dessen Schnittstelle, insb. bei den Parametern der Operationen, ein bestimmter Basisdatentyp (ggf. auch mehrere) offenbleiben; durch Einsetzen eines konkreten Basisdatentyps wird der generische abstrakte Datentyp zu einem einfachen abstrakten Datentyp, von dem Instanzen gebildet werden können. gADT kommen versteckt schon in der Informatik-Grundausbildung vor: dort werden z.B. Listen, Stapel, Suchbäume usw. als Datenstrukturen eingeführt, i.d.R. aber für einen bestimmten Typ von darin enthaltenen Datenelementen, z.B. ganze Zahlen oder Zeichenketten; diesen Typ nennt man auch den Basisdatentyp. Man macht sich leicht klar, daß der Basisdatentyp für die Funktionsweise einer “abstrakten” Liste oder eines Stapels völlig unerheblich ist. Aus einer Implementierung einer Liste von ganzen Zahlen kann man leicht eine Implementierung einer Liste von Zeichenketten machen, indem man überall dort, wo der Basisdatentyp auftritt, den passenden neuen Typ einsetzt1 . Der gADT Liste abstrahiert gerade von den Differenzen dieser Listenarten und könnte Liste[B] genannt werden, um auszudrücken, daß ein formaler Typ-Parameter B vorhanden ist. Der Begriff gADT bezieht sich nur auf die Syntax und Semantik der Schnittstelle, also die exportierten Typen und Operationen, und die Wirkung der Operationen, nicht hingegen auf die Implementierung. Man kann in manchen Programmiersprachen generische Implementierungen realisieren, die Details sind unterschiedlich und hier irrelevant. Man kann jedenfalls wie üblich bei abstrakten Datentypen verschiedene Implementierungen derselben Schnittstelle realisieren. Arrays lassen sich ebenfalls als gADT auffassen; daß man ihre Implementierung nicht sieht, weil sie als Teil der Programmiersprache implementiert sind, ist dabei unerheblich. Arrays haben zwei formale Typ-Parameter: (a) den Indexbereich N, der immer ein endliches In1 Ferner können typspezifische Kopieroperationen auftreten und andere Details anzupassen sein, diese Details spielen im weiteren aber keine Rolle. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 5 tervall der ganzen Zahlen ist, und (b) der Typ der Arrayelemente B. Man könnte also vom gADT array [N,B] reden. 2.2 Der generische ADT directory [S,I] Ein Verzeichnis (directory) ist eine Datenstruktur, die Elemente (“Sätze”) enthält, die aus einem Schlüsselwert und zugeordneten Daten (dem “Inhalt”) bestehen. Ein Beispiel für ein solches Element ist eine Personenbeschreibung, wobei die Personalnummer der Schlüsselwert ist und diverse Angaben zur Person die zugeordneten Daten. Der gADT directory [S,I] hat also zwei Basisdatentypen, für die folgendes gilt: – S ist der Typ der Schlüsselwerte; er muß eine Operation größer als(S,S), die zwei Schlüsselwerte vergleicht, anbieten. Implementierungen von Directories haben meist zusätzliche implementierungsspezifische Restriktionen für diesen Typ, z.B. könnten nur ganze Zahlen oder Strings der Länge 8 zulässig sein. – I ist der Typ des Inhalts eines Eintrags. I hat keinen Einfluß auf die Funktionslogik eines directory. Auch hier kann es implementierungsspezifische Restriktionen geben. Der gADT directory [S,I] bietet folgende Operationen auf Verzeichnissen an (die Kleinbuchstaben v, s und i bezeichnen den übergebenen Wert bzw. die übergebene Referenz; hinter dem Doppelpunkt steht ggf. der Typ des Rückgabewerts): create(): V Anlegen eines leeren Verzeichnisses dispose(V) Löschen des Verzeichnisses v insert(V,S,I) Einfügen bzw. Überschreiben des Satzes mit dem Schlüsselwert s in v; der neue Inhalt ist i delete(V,S) Löschen des Satzes mit Schlüsselwert s in v read(V,S): I Lesen des Satzes mit Schlüsselwert s in v; zurückgegeben wird der Satzinhalt. read liefert einen Fehler, wenn kein Satz mit Schlüsselwert s in Verzeichnis v vorhanden ist. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 6 Die vorstehenden Operationen stellen einen Minimalumfang dar. Darüberhinaus bieten manche Implementierungen des gADT Verzeichnis zusätzliche Operationen an: next key(V,S) liefert den nächstgrößeren Schlüsselwert nach s in v. Diese Operation ermöglicht es, alle Einträge des Verzeichnisses v sequentiell zu durchlaufen. read interval(V,S,S): liste[I] liest alle Sätze mit einem Schlüsselwert zwischen den beiden übergebenen Schlüsselwerten in Verzeichnis v. Diese Operation kann im Prinzip auch unter Benutzung von next key realisiert werden, allerdings kann sie effizienter implementiert werden als die sonst notwendigen Einzelzugriffe. Ein B-Baum ist eine effiziente, plattenorientierte Implementierung des gADT directory, incl. sequentiellem Durchlauf und Intervallabfrage. 3 3.1 B-Bäume Grundlegende Implementierungsentscheidungen Zwei Optimierungsziele stehen hier im Vordergrund: – Bei der Suche nach dem Satz mit dem Schlüsselwert s sollen möglichst wenige Seiten besucht (also Blöcke übertragen) werden. Der bei hauptspeicherorientierten Suchstrukturen im Vordergrund stehende Rechenaufwand spielt hier keine Rolle, da ein Plattenzugriff in der Größenordnung von 10 Millisekunden dauert, also ca. 106 bis 107 mal mehr als ein Rechenschritt der CPU. – Der Platz auf der Platte soll möglichst gut ausgenutzt werden. Das Verhältnis von den Nutzdaten zu dem Brutto-Platzbedarf sollte über 50 % liegen. Unter Nutzdaten verstehen wir hier die Schlüsselwerte und die zugehörigen Satzinhalte, die ja auf jeden Fall gespeichert werden müssen. Hinzu kommen Zeigerstrukturen und c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 7 sonstige Hilfsdaten und insb. Speicherbereiche, die aus technischen Gründen reserviert werden müssen. Der Brutto-Platzbedarf ist die Gesamtgröße der letztlich auf der Platte für das Verzeichnis benutzten Sektoren. 3.2 Vielweg-Suchbäume Binäre Suchbäume sind in ihrer Grundform hauptspeicherorientiert, d.h. man unterstellt eine Speicherverwaltung, bei der einzelne Knoten in Abschnitten des Hauptspeichers liegen, die direkt adressierbar sind. Eine sehr simple Methode, die Struktur eines binären Suchbaums auf der Platte zu realisieren, besteht darin, einfach jeden Knoten des Suchbaums in einen eigenen Block zu schreiben. Im Vergleich zu einer Hauptspeicherimplementierung wären die Verweise auf die Unterbäume, die in jedem Knoten stehen, keine Hauptspeicheradressen mehr, sondern Nummern von Blöcken auf der Platte (“Medienadressen”). Die grundlegenden Algorithmen zum Suchen, Einfügen und Löschen in Bäumen können ansonsten unverändert bleiben. Dieser simple Ansatz hat indes den gravierenden Nachteil, i.a. den Platz auf der Platte schlecht auszunutzen. Bei einem binären Suchbaum enthält ein Knoten folgenden Daten: – die Zeiger auf linken und rechten Unterbaum – den Schlüsselwert – den Inhalt mit Nutzdaten, von dem wir annehmen, daß er in einem Bytefeld fester Länge gespeichert werden kann Knoten im Baum s linker Unterbaum Nutzdaten rechter Unterbaum Nehmen wir z.B. folgende Größen an: c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume b t k i 8 Blockgröße in Bytes Platzbedarf für eine Medienadresse (Verweis auf Teilbaum) Platzbedarf für einen Schlüsselwert Platzbedarf für Satzinhalt z.B. b = 2048 z.B. t = 8 z.B. k = 8 z.B. i = 110 Jeder Block wäre in unserem Beispiel also nur zu 134/2028 oder ca. 6.6 % gefüllt. Dies entspricht nicht unseren obigen Optimierungszielen. Um den Füllungsgrad der Blöcke zu verbessern, müssen mehrere Sätze und Verweise auf Unterbäume in einem Block gepackt werden. Wir sprechen dann von einem Vielweg-Suchbaum. Um die Funktion eines Vielweg-Suchbaums zu verstehen, betrachten wir noch einmal die Struktur eines Knotens in einem binären Suchbaum. Der in einem Knoten enthaltene Schlüsselwert s teilt den Schlüsselwertbereich in zwei Intervalle. Alle Schlüsselwerte, die im linken bzw. rechten Unterbaum vorkommen, liegen im unteren bzw. oberen Intervall. Ein Vielweg-Suchbaum verallgemeinert nun die Idee des binären Suchbaums dahingehend, nicht nur zwei Intervalle des Schlüsselwertbereichs und zugehörige Unterbäume zu haben, sondern n > 2. Für einen Vielweg-Suchbaum gilt daher: – Ein Knoten besteht aus – n Verweisen auf Teilbäume T1 , ..., Tn und – n − 1 Schlüsselwerten s1 , ..., sn−1 und zugehörigen Inhalten. s1 T1 s2 T2 s3 T3 s4 T4 T5 – Die Schlüsselwerte s1 , ..., sn−1 teilen den gesamten Schlüsselwertbereich in n Intervalle auf. Deshalb bezeichnen wir sie oft als Trennschlüsselwerte. Sei Ki die Menge der im Teilbaum Ti auftretenden Schlüsselwerte. Alle x ∈ Ki liegen im i-ten Intervall, also: c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume – ∀x ∈ K1 : 9 x < s1 – ∀x ∈ Ki , 1 < i < n : si−1 < x < si – ∀x ∈ Kn : sn−1 < x Der Platzbedarf für einen Knoten eines Vielweg-Suchbaums steigt linear mit n an. Bei gegebener Blockgröße und gegebenem Platzbedarf für eine Medienadresse, einen Schlüsselwert und einen Satzinhalt (s.o.) kann man die Zahl der Schlüsselwerte, die maximal in einen Block passen, mit folgender Formel berechnen: ⌊(b − t)/(k + i + t)⌋ In unserem obigen Beispiel (b=2048; t=8; k=8; i=110) ergibt sich n=16. 3.3 Merkmale von B-Bäumen B-Bäume sind spezielle Vielweg-Suchbäume. Ihre besonderen Eigenschaften sind: 1. Alle Knoten mit Ausnahme der Wurzel sind wenigstens zur Hälfte gefüllt. Man spricht von einem B-Baum der Ordnung m, wenn in jedem Knoten (mit Ausnahme der Wurzel) mindestens m und maximal 2m Schlüsselwerte auftreten. Für die Wurzel gilt: entweder ist sie ein Blatt (d.h. der Baum hat ≤ 2m Knoten), oder sie hat wenigstens 2 Unterbäume. 2. Alle Pfade von der Wurzel zu einem Blatt sind gleich lang. 3. Ein innerer Knoten mit n Schlüsselwerten hat n+1 nichtleere Unterbäume, d.h. innere Knoten haben keine leeren Unterbäume. Bild 1 zeigt einen B-Baum der Ordnung 2. Abschätzung der Suchgeschwindigkeit: B-Bäume sind sehr effiziente Datenstrukturen, die Zeit zum Auffinden eines Datenelements anhand seines Schlüsselwerts hängt nur logarithmisch ab von der Zahl der Datenelementen in dem B-Baum. Um dies zu zeigen, untersuchen c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 10 12 2 5 34 17 19 76 42 50 59 70 83 102 Abbildung 1: B-Baum der Ordnung 2 wir zunächst, wieviele Schlüsselwerte bzw. Sätze ein Baum der Höhe h und Ordnung m mindestens enthält. Ebene wenigstens 2 Teilbäume 0 1 Sei h die Höhe des B-Baums (also die Zahl der Ebenen ohne Wurzelebene). Dann ist die Zahl der Knoten eines Teilbaums der Ebene 1 2 jeweils > m Teilbäme h = = 1 + (m + 1) + (m + 1)2 + .... + (m + 1)h−1 (m+1)h −1 (m+1)−1 Die Zahlh der in einem Teilbaum der Ebene 1 enthaltenen Schlüssel −1 h ist m∗ (m+1) (m+1)−1 = (m + 1) − 1. Da mindestens 2 Teilbäume der Ebene 1 vorhanden sind, enthält der gesamte Baum n > 2 ∗ ((m + 1)h − 1) + 1 Schlüssel. Wenn wir diese Formel nach h auflösen, erhalten wir: h ≤ logm+1 n+1 2 Beispiel: Für n = 1.000.000 Sätze und m = 8 ergibt sich 1000000+1 ≈ 5.97 bzw. aufgerundet h < 6. Für das Durchh ≤ log9 2 laufen des Baumes von der Wurzel bis zu einem Blatt werden also maximal 7 Seitenzugriffe benötigt. 3.4 Primärschlüssel Bei einem B-Baum bilden die Daten zur Realisierung der Baumstruktur und die Nutzdaten eine untrennbare Einheit. Anders gesehen sind c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 11 die Indexstrukturen in Primärdaten eingebettet. Man spricht deshalb hier von einem Primärindex. Der durch den Primärindex unterstützte Suchschlüssel wird Primärschlüssel genannt. Da die Einträge im B-Baum in aufsteigender Reihenfolge gemäß dem Primärschlüssel sortiert sind, ist für einen Datenbestand nur ein Primärindex möglich. 4 Algorithmen Im folgenden beschreiben wir die Algorithmen, mit denen die Verzeichnis-Operationen in einem B-Baum realisiert werden. Bei Bäumen als Suchstrukturen steht man immer von dem Problem, daß der Suchbaum degenerieren kann, wenn z.B. Elemente in sortierter Reihenfolge eingefügt werden. Die Suchzeiten können dadurch sehr schlecht werden. B-Bäume adressieren dieses Problem dadurch, daß der Baum balanciert wird. 4.1 Suche Der Suchalgorithmus ist eine direkte Verallgemeinerung des Suchalgorithmus für binäre Bäume: bei binären Bäumen durchläuft man den Baum von der Wurzel aus und wandert bei einem Knoten, der den Schlüsselwert s enthält, in den linken bzw. rechten Teilbaum, wenn der gesuchte Eintrag einen Schlüsselwert < s bzw. > s ist. Die Teilbäume stehen für die Schlüsselbereichsintervalle [0,s) und (s,∞]. Anders gesagt wandert man in dasjenige Intervall, in dem der gesuchte Eintrag liegen muß. Analog geht man bei Vielweg-Suchbäumen vor, nur daß hier mehrere (disjunkte) Intervalle zur Auswahl stehen. 4.2 Einfügung Die grundlegende Vorgehensweise bei insert(v,s,i) ist: – Knoten suchen, in dem Satz mit Schlüsselwert s sein müßte – Satz mit Schlüsselwert s und Inhalt i dort einfügen c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 12 – wenn 2m+1 Sätze in der Seite, dann Überlaufbehandlung Das eigentliche Problem - insb. hinsichtlich der Balancierung des Baums - ist also die Überlaufbehandlung. Bäume wachsen normalerweise nach unten2 . Der geniale Einfall bei B-Bäumen besteht darin, den Baum zunächst in die Breite und ggf. oben an der Wurzel wachsen zu lassen. Im einzelnen wird ein Überlauf wie folgt behandelt: – Wir fügen den Satz gedanklich an der richtigen Stelle im Knoten ein (in Wirklichkeit geht das nicht, weil der Knoten nur 2m Einträge aufnehmen kann), so daß jetzt 2m+1 Sätze vorhanden sind. – den übergelaufenen Knoten teilen wir in zwei neue, minimal gefüllte Knoten mit jeweils m Sätzen auf; der eine enthält die Sätze 1 bis m, der andere die Sätze m+2 bis 2m+1 (s. Bild 2) ..... x s 1 ..... s m s m+1 übergelaufener Knoten y ..... s m+2 ..... s 2m+1 ..... x s 1 ..... s m s m+1 y ..... s m+2 ..... s 2m+1 Abbildung 2: Überlaufbehandlung – Im Elternknoten des übergelaufenen Knotens wird der bisher vorhandene Verweis auf den übergelaufenen Knoten ersetzt durch (a) zwei Verweise auf die beiden neuen Knoten und (b) den mittleren 2 Dies gilt natürlich nur in der Informatik, wo Bäume unnatürlicherweise die Wurzel “oben” haben. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 13 (m+1.) Satz, der den Trennschlüssel für die beiden neuen Teilbäume enthält. Im Elternknoten ist danach die Zahl der Sätze um 1 erhöht. – Falls auch der Knoten in der nächsthöheren Ebene überläuft, wird auch dieser Überlauf nach dem gleichen Schema behandelt. Der Überlauf kann sich so nach oben bis zur Wurzel fortsetzen. Im Extremfall läuft die bisherige Wurzel über, und der Baum wächst um eine Ebene. Er wächst also an der Wurzel! 4.3 Löschung Die grundlegende Vorgehensweise in delete(v,s) ist: – Knoten N suchen, in dem der Satz mit Schlüsselwert s enthalten ist (Fehler, falls nicht vorhanden) – sofern N ein Blatt ist, den Satz dort löschen. Andernfalls, also wenn N ein innerer Knoten ist, den zu löschenden Satz dort mit dem nächsten Satz überschreiben (der nächste Satz hat den nächstgrößeren nach s auftretenden Schlüsselwert; er steht im “rechts folgenden” Unterbaum im Blatt “unten links”); anschließend diesen nächsten Satz löschen und Knoten N – nunmehr ein Blatt – entsprechend neu festlegen. – sofern N nur noch m-1 Sätze enthält und nicht die Wurzel ist, Unterlaufbehandlung durchführen. Das entscheidende Problem bei Löschungen ist natürlich, ein Degenerieren des Baums zu verhindern. Analog zur Überlaufbehandlung gehen wir hier so vor, daß wir zunächst die Breite des Baumes reduzieren und ggf. sogar die Höhe. Ein Unterlauf wird nach folgendem Verfahren behandelt: – sofern es einen Nachbarknoten von N mit k + m, k ≥ 1, Sätzen gibt (oBdA sei dies der rechte Nachbar; wir bezeichnen ihn i.f. mit R), Ausgleich zwischen N und R durchführen – andernfalls Verschmelzen von N und R c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 14 Ausgleich zwischen N und R: Die naheliegende Idee ist hier, den untergelaufenen Knoten aufzufüllen mit Sätzen, die einer der Nachbarn entbehren kann3 . Konkret gehen wir wie folgt vor (s. Bild 3): x N ... m−1 ... y ... s ... R ... m ... N .. m−1 .. x .. s1 .. R .. s2 .. ... m ... .. s1 .. y .. s2 .. Abbildung 3: Unterlaufbehandlung – Der Satz (mit Schlüsselwert x) im Elternknoten von N, der Verweise auf N und R abgrenzt, wird nach N verschoben. – Wenn R insg. k+m Sätze enthält, dann (k-1)/2 (ab- oder aufgerundet) Sätze aus R an das Ende in N verschieben – nächsten Satz aus R im Elternknoten als neuen Trennsatz zwischen N und R eintragen Verschmelzen von N und R: In diesem Fall enthalten N und R m-1 bzw. m Sätze. Zusammen mit dem Satz im Elternknoten, der die Einträge für N und R trennt, haben wir genau 2m Sätze. Diese Sätze bilden einen neuen Knoten, der die bisherigen Knoten N und R ersetzt. Im Elternknoten reduziert sich die Zahl der Einträge hierdurch um 1. Sofern im Elternknoten ebenfalls ein Unterlauf eintritt, wird dieser nach dem gleichen Schema wie der ursprüngliche Unterlauf behandelt. Dies kann sich rekursiv bis zur Wurzel fortsetzen. Im Extremfall wird die Wurzel gelöscht und die Höhe des Baums sinkt um eine Ebene. 3 Die Idee des Ausgleichs liegt auch beim Einfügen nahe, d.h. man könnte einen übergelaufenen Knoten zunächst mit einem schlecht gefüllten Nachbarknoten ausgleichen. Dies führt aber zu vielen fast vollen Blöcken und macht Blocküberläufe häufiger, speziell unter der meist zutreffenden Annahme, daß ein Datenbestand tendenziell wächst. Blocküberläufe sind jedoch unerwünscht, da statt einem Block mit einem Blattknoten zwei geschrieben werden müssen (sowohl beim Ausgleich als auch bei einer Teilung); zusätzlich ist wenigstens ein innerer Knoten zu behandeln. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 15 x N ... m−1 ... R ... m ... N .. m−1 .. x ... m ... R (freigeben) Abbildung 4: Verschmelzung 4.4 Beispiel Als Beispiel betrachten wir einen B-Baum mit Ordnung 2. Ausgehend von einem leeren Baum fügen wir wie folgt Sätze ein (angegeben sind immer nur deren Schlüsselwerte). – Einfügen von Sätzen mit den Schlüsselwerten 50, 102, 34, 19 und 5. Bei der letzten Einfügung läuft der bisher einzige Knoten über (im folgenden Bild ist links der zu große Knoten gezeigt) und muß geteilt werden. Die Mitte bildet der Schlüsselwert 34; dieser wandert in die neu zu bildende Wurzel, die linke und rechte Hälfte bilden jeweils neue Knoten. 34 [5] 19 34 50 102 5 19 50 102 – Nach dem Einfügen von Sätzen mit den Schlüsselwerten 76, 42, 2 und 83 tritt erneut ein Überlauf ein. 34 2 5 19 34 42 50 76 [83] 102 2 5 19 76 42 50 83 102 – Nach dem Einfügen von Sätzen mit den Schlüsselwerten 59, 70, 12 und 17 tritt erneut ein Überlauf ein. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 16 34 76 2 5 12 [17] 19 12 83 102 42 50 59 70 2 5 34 76 42 50 59 70 17 19 83 102 – Wenn nun der Satz mit Schlüsselwert 83 gelöscht wird, kann der betroffene Knoten mit seinem linken Nachbarn ausgeglichen werden. 12 2 5 34 76 12 42 50 59 70 17 19 83 102 34 2 5 70 42 50 59 17 19 76 102 – Wenn der Satz mit Schlüsselwert 2 gelöscht wird, muß der betroffene Knoten mit seinem rechten Nachbarn verschmolzen werden 12 2 5 34 34 42 50 59 17 19 5 70 76 102 70 42 50 59 5 12 17 19 76 102 B*-Bäume Bei der Suche nach einem Satz müssen alle Ebenen eines B-Baums durchlaufen werden. Für jede Ebene ist je ein Block zu übertragen. Wie schon früher erwähnt ist die Reduktion der Zahl der Blockübertragungen das wichtigste Optimierungsziel. Die Höhe des Baums können wir nur reduzieren, wenn wir den Verzweigungsgrad (bzw. die Ordnung m) erhöhen. In B*-Bäumen c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 17 erreicht man dies – unter Inkaufnahme eines geringen Ausmaßes an Redundanz – dadurch, daß in den inneren Knoten der Baumstruktur nur die Schlüsselwerte gespeichert, der Satzinhalt also weggelassen wird. Komplette Sätze (Schlüsselwert und Inhalt) stehen stehen nur noch in den Blättern der Baumstruktur, also der untersten Ebene. Alle Nicht-Blattknoten sind reine Indexknoten. Der Satz, der zu einem Schlüsselwert gehört, der in einem NichtBlattknoten auftritt, kann z.B. jeweils im “rechts” anschließenden Unterbaum untergebracht werden. Der Unterbaum zwischen zwei Trennschlüsseln s1 und s2 enthält also alle auftretenden Schlüsselwerte im halboffenen Intervall [s1,s2). Wenn bei der Suche ein Schlüsselwert in einem Nicht-Blattknoten gefunden wird, muß dementsprechend im “rechts” anschließenden Unterbaum weitergesucht werden. Die Schlüsselwerte in den inneren Knoten werden in den Blättern noch einmal gespeichert. Es liegt somit in geringem Ausmaß Redundanz vor (Schlüsselwerte sind i.a. kurz). Der entscheidende Vorteil von B*-Bäumen liegt darin, daß wesentlich mehr Einträge in einen Block passen. Hierzu betrachten wir erneut unser früheres Beispiel in Abschnitt 3.2. Mit i=0 (wegen des fehlenden Satzinhalts) und den unveränderten Werten b=2048, t=8 und k=8 und ergeben sich (b-t)/(k+i+t) = 127 Einträge pro Block. Für die Höhe des Suchbaums aus in Abschnitt 3.3 dem Beispiel 1000000+1 ≈ 2.8, d.h. wir sparen ergibt sich bei nunmehr h ≤ log128 2 rund die Hälfte der Blockübertragungen ein. Bei B*-Bäumen wird ferner im Vergleich zu B-Bäumen die Zahl der Nicht-Blattknoten erheblich reduziert, im vorstehenden Beispiel ca. um den Faktor 8. Hierdurch ist es fast immer möglich, alle inneren Knoten im Hauptspeicher zu puffern, d.h. bei einer Suche brauchen keine Indexblöcke übertragen zu werden, sondern nur noch ein einziger (Daten-) Block! Dieser Wert kann nicht weiter verbessert werden. Ein weiterer Vorteil von B*-Bäumen besteht darin, daß Sätze variabler Länge leicht handhabbar sind. Aufgrund der diversen Vorteile werden in der Praxis nur B*-Bäume eingesetzt. c 2005 Udo Kelter Stand: 22.10.2005 B-Bäume 18 Literatur [BaM72] Bayer, R.; McCreight, E.M.: Organization of large ordered indexes; Acta Informatica 1, p.173-189; 1972 Glossar B-Baum: entweder abstrakte Implementierung des generischen abstrakten Datentyps Verzeichnis oder konkrete Implementierung für konkrete Basisdatentypen B*-Baum: Variante des B-Baums, bei der Nutzdaten nur auf der untersten Baumebene gespeichert werden und alle oberen Ebenen reine Indexknoten enthalten generischer abstrakter Datentyp: abstrakter Datentyp, in dessen Schnittstelle, insb. bei den Parametern der Operationen, ein Basisdatentyp (ggf. auch mehrere) offenbleibt; durch Einsetzen eines konkreten Basisdatentyps wird der generische abstrakte Datentyp zu einem einfachen abstrakten Datentyp, von dem Instanzen gebildet werden können Ordnung (eines B-Baums): Minimalzahl der Trennschlüssel in einem Knoten; die Maximalzahl ist genau doppelt so hoch Überlauf (beim Einfügen in einen B-Baum): Überschreiten der Maximalzahl an Einträgen in einem Knoten eines B-Baums Unterlauf (beim Löschen in einem B-Baum): Unterschreiten der Minimalzahl an Einträgen in einem Knoten eines B-Baums Verzeichnis (directory): generischer abstrakter Datentyp mit zwei Basisdatentypen, die den Schlüsselwertebereich bzw. den Inhalt eines Eintrags definieren; grundlegende Operationen sind das Einfügen, Löschen und Auslesen von Einträgen; optional sind Operationen für ein sequentielles Durchlaufen bzw. Auslesen mehrerer Einträge Vielweg-Suchbaum: Suchbaum, der den binären Suchbaum insofern verallgemeinert, daß jeder Knoten nicht nur einen, sondern n Einträge bzw. (Trenn-) Schlüsselwerte enthält, und nicht 2, sondern n+1 Unterbäume hat; die n+1 Unterbäume enthalten Einträge mit solchen Schlüsselwerten, die vor dem ersten, zwischen dem i-ten und i+1-ten bzw. nach dem letzten Trennschlüsselwert liegen c 2005 Udo Kelter Stand: 22.10.2005 Index B*-Baum, 16, 18 B-Baum, 18 Ausgleich zwischen Knoten, 13, 16 Einfügung, 11 Löschung, 13 Merkmale, 9 Optimierungsziele, 6, 8, 9, 16 Ordnung, 9, 18 Suche, 11 Suchgeschwindigkeit, 9, 17 Teilen von Knoten, 12, 15 Überlauf, 11, 12, 18 Unterlauf, 13, 18 Verschmelzen von Knoten, 13, 14, 16 Basisdatentyp, 4 Baum, siehe Suchbaum Blockübertragung, 6 Inhalt, 5, 16 Schlüsselwert, 5 Schlüssel Primärschlüssel, 10 Schlüsselwert, 5 Schlüsselwertbereich, 8 Intervall, 8 Suchbaum, 4 Balancierung, 12 Effizienz, 3, 13 Verzweigungsgrad, 16 Vielweg-∼, 7, 8 directory [S,I], siehe Verzeichnis Verzeichnis, 3, 5, 18 ∼-Operationen, 5 Vielweg-Suchbaum, 8, 18 Trennschlüsselwert, 8 Typ-Parameter, 4 Typkonstruktor, 4 Überlauf, siehe B-Baum Unterlauf, siehe B-Baum gADT, 4 generischer abstrakter Datentyp, 4, 18 directory [S,I], 5 Typ-Parameter, 4 Indexknoten, 17 Nutzdaten, 6 Ordnung, siehe B-Baum Primärindex, 11 Satz 19