B-Bäume

Werbung
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.
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
h ≤ log9
≈ 5.97 bzw. aufgerundet h < 6. Für das Durch2
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
Herunterladen