B-drzewa
: wtorek 17 gru 2019, 01:53
Koncepcja, filozofia, własności, struktura
Rozważania są kontynuacją tematu
dotyczącego drzew binarnych,
jednak ze względu na swoje indywidualne cechy
jest wydzielone jako niezależny temat.
Filozofia działania b-drzew jest bardzo podobna do filozofii drzew binarnych. Tu również typowym rozwiązaniem jest zastosowanie algorytmów rekurencyjnych. Podstawową różnicą pomiędzy elementem węzła drzewa binarnego i elementu b-drzewa jest liczba kluczy przechowywanych w pojedynczym elemencie. W przypadku drzew binarnych, każdy element przechowuje jeden element wraz z konsekwencjami tego faktu: ma wskazanie do elementów młodszych oraz starszych. W przypadku b-drzewa, każdy element zawiera kilka kluczy. By nie wnosić nieporozumień, element struktury b-drzewa będę nazywał stroną. Istotną cechą strony b-drzewa jest jej wielkość, czyli liczba kluczy wraz z informacjami stowarzyszonymi. Taki element w dalszej części będzie nazywany Item'em. Ważną cechą strony jest liczba Item'ów zawartych na pojedynczej stronie. W rzeczywistości istotna jest liczba Item'ów tworzących półstronę, gdyż ta wielkość na istotne znaczenie dla algorytmów równoważenia b-drzewa. Można na to spojrzeć również z z takiej perspektywy, że strona b-drzewa zawiera 2n elementów (Item'ów). Cechy charakterystyczne dla b-drzewa są następujące:
Moja pierwsza implementacja algorytmów b-drzewa była zrealizowana w języku MODULA-2 (to taka ulepszona wersja PASCAL'a). Zawierała ona kompletną realizację operacji związanych z przetwarzaniem stron b-drzewa. Powyższe definicje typów mogą być następujące:Język MODULA-2 jest bardzo wdzięcznym językiem do programowania, zapisy algorytmów są zwięzłe i eleganckie. Jednak ma on jedną wadę: jest mało popularny. Narzędzia do tworzenia oprogramowania (kompilator) dotyczą jedynie komputerów PC. Chcąc mieć możliwość szerszego wykorzystania swego czasu dokonałem transkrypcji z języka MODULA-2 na język C. Niestety w kilku miejscach „elegancja” zapisu modulowego poszła „w pole” (pewnych konstrukcji z PASCALA czy MODULI nie daje się przenieść do języka C), rozwiązanie jest trochę mniej eleganckie (jednak technicznie sprawne). Powyższe w języku C wygląda następująco:Dodanie elementu do b-drzewa nie odbiega znacząco od rozwiązań z drzewami binarnymi. Algorytm jest identycznie (rekurencyjnie) zrealizowany. Dodanie pierwszego elementu generuje nową stronę i jej wskazanie doczepia się do wskazania na szczyt drzewa (jakkolwiek będzie on reprezentowany: jako zmienna w pamięci RAM, jakieś „znane” miejsce w pliku podające ofset, gdzie znajduje się treść strony).Dodanie kolejnego elementu poszerza tablicę Item'ów i zwiększa licznik „ważnych” elementów.Czasem, a właściwie w większości wypadków, dodanie kolejnego elementu wymaga przesunięć w tablicy Item'ów. Te zawsze muszą byś uporządkowane a dodawany element nie zawsze musi się wpasować na koniec tablicy. W przypadku wkładania nowego Item'u do środka, konieczne jest zrobienie miejsca nowemu, co sprowadza się do przepisania niektórych Item'ów o jedną pozycję.Dodanie nowego nie zaburza dotychczasowego uporządkowania.Dodanie kolejnych elementów doprowadzi do poniższego stanu.W przypadku dodania elementu do strony, która zawiera zajęte wszystkie Item'y, dochodzi do przenosin (czasem jest to związane ze zbudowaniem nowego lokum). W przypadku budowy nowej strony efekt będzie jak na ilustracji niżej. Tworzony jest nowy poziom b-drzewa, który będzie zawierał jeden element (środkowy z dotychczas znajdujących się). Ponieważ nowa liczba elementów jest nieparzysta, łatwo jest wydzielić dokładnie środkowy (liczba elementów na stronie 2n jest parzysta plus dodany jeden nowy).W przypadku, gdy istnieje bardziej rozbudowana struktura (istnieje strona bezpośrednio nadrzędna do bieżącej), element środkowy przeprowadza się do strony nadrzędnej. Zachodzi przypadek swoistego dodania elementu w strukturze nadrzędnej. Oczywiście może zdarzyć się przypadek, że strona bezpośrednio nadrzędna również jest w 100% wypełniona, więc fraktalnie również zostaje wydzielony element środkowy, który przechodzi do kolejnej strony nadrzędnej. W szczególnej sytuacji, gdzie wszystkie strony są wypełnione w 100% (a za taki przypadek można uważać jeden z powyższych rysunków), dochodzi to utworzenia kolejnej „warstwy” b-drzewa (jak na powyższym rysunku).
W przypadku drzew binarnych, istnieją sytuacje, gdzie poszczególne gałęzie mają różną długość: przykładowo drzewo o 4 elementach ma jednego „rodzica”, dwoje „dzieci” i jednego „wnuka”. Jak na to nie patrzeć, istnieje gałąź o innej długości od każdej pozostałej. W przypadku b-drzewa ten przypadek nie zaistnieje i uzyskuje się go przez zmienny stopień wypełnienia strony. Poprawnie zbudowane b-drzewo ma zawsze wszystkie gałęzie identycznej długości.
Rozważania są kontynuacją tematu
dotyczącego drzew binarnych,
jednak ze względu na swoje indywidualne cechy
jest wydzielone jako niezależny temat.
Filozofia działania b-drzew jest bardzo podobna do filozofii drzew binarnych. Tu również typowym rozwiązaniem jest zastosowanie algorytmów rekurencyjnych. Podstawową różnicą pomiędzy elementem węzła drzewa binarnego i elementu b-drzewa jest liczba kluczy przechowywanych w pojedynczym elemencie. W przypadku drzew binarnych, każdy element przechowuje jeden element wraz z konsekwencjami tego faktu: ma wskazanie do elementów młodszych oraz starszych. W przypadku b-drzewa, każdy element zawiera kilka kluczy. By nie wnosić nieporozumień, element struktury b-drzewa będę nazywał stroną. Istotną cechą strony b-drzewa jest jej wielkość, czyli liczba kluczy wraz z informacjami stowarzyszonymi. Taki element w dalszej części będzie nazywany Item'em. Ważną cechą strony jest liczba Item'ów zawartych na pojedynczej stronie. W rzeczywistości istotna jest liczba Item'ów tworzących półstronę, gdyż ta wielkość na istotne znaczenie dla algorytmów równoważenia b-drzewa. Można na to spojrzeć również z z takiej perspektywy, że strona b-drzewa zawiera 2n elementów (Item'ów). Cechy charakterystyczne dla b-drzewa są następujące:
- klucze zawarte w strukturach elementów Item na danej stronie są uporządkowane w określony sposób (najczęściej rosnący ale to nie jest jedyne możliwe rozwiązanie),
- każda strona (z wyjątkiem strony będącej szczytem drzewa) zawiera od n do 2n elementów, czyli stopień wypełnienia tablicy Item'ów na danej stronie jest równy lub większy od 50%.
- ItemsOnPage - liczba ważnych Item'ów na stronie, nie ma obowiązku, by cała strona, czyli cała tablica Item'ów była wypełniona ważnymi informacjami, toteż przechowywana jest informacja o liczbie Item'ów zawierających istotne dane,
- BackwardPage - wskaźnik przeniesienia do elementów młodszych, filozoficznie odpowiadający wskaźnikowi LeftLink w drzewach binarnych, tutaj BackwardPage jest wskazaniem na stronę zawierającej klucze młodsze od klucza zapisanego na pozycji Item[0],
- Items - tablica Item'ów (na powyższej ilustracji n=3, wielkość półstrony wynosi 3 elementy, cała strona to 2n=6 elementów).
- Key - klucz, informacja zależna od danego zastosowania: może być tablicą znakową przechowującą napis i wtedy jako funkcja porządkująca Item'y na stronie musi być funkcją porównującą napisy dającą wynik w sensie kolejności alfabetycznej; może być liczbą binarną, wtedy funkcja porządkująca musi traktować dane jako liczba o określonej liczbie bajtów,
- DatRef - wskaźnik użytkowy o znaczeniu zależnym od zastosowania, przykładowo może być wskazaniem na położenie skojarzonego z kluczem rekordu bazy danych,
- ForwardPage - wskaźnik do strony zawierającej klucze starsze od klucza zapisanego w danym Item'ie (jednak młodsze od klucza zapisanego w następnym Itemie), jest filozoficznym odpowiednikiem wskaźnika RightLink w drzewie binarnym,
- DuplPage - wskaźnik do listy liniowej duplikatów, ten element nie jest obowiązkowy a jego istnienie wynika z zastosowania algorytmu b-drzewa do określonych celów.
Moja pierwsza implementacja algorytmów b-drzewa była zrealizowana w języku MODULA-2 (to taka ulepszona wersja PASCAL'a). Zawierała ona kompletną realizację operacji związanych z przetwarzaniem stron b-drzewa. Powyższe definicje typów mogą być następujące:
Kod: Zaznacz cały
DEFINITION MODULE UBTreeTypes ;
( . . . )
CONST
BTreeNil = 0FFFFFFFFH ;
(. . . )
TYPE
InxBufferType = ARRAY [ 0 .. 9999 ] OF CHAR ;
InxBufferTypePtr = POINTER TO InxBufferType ;
ItemType = RECORD
DataRef : LONGCARD ;
PageRef : LONGCARD ;
EPageRef : LONGCARD ;
Key : InxBufferType ;
END (* RECORD *) ;
ItemTypePtr = POINTER TO ItemType ;
PageType = RECORD
ItemsOnPage : CARDINAL ;
BackwardPage : LONGCARD ;
ItemArray : InxBufferType ;
END (* RECORD *) ;
PageTypePtr = POINTER TO PageType ;
BaseRecord = RECORD
RecordStatus : LONGCARD ;
RecordData : InxBufferType ;
END (* RECORD *) ;
( . . . )
END UBTreeTypes.
Kod: Zaznacz cały
#define BTreeNil 0x0FFFFFFFFL
typedef struct {
USHORT CRCSum ;
ULONG DataRef ;
ULONG ForwardRef ;
} DuplPageType ;
typedef DuplPageType * PtrDuplPageType ;
typedef struct {
ULONG DataRef ;
ULONG PageRef ;
ULONG EPageRef ;
UCHAR Key [ ] ;
} BTItemT ;
typedef BTItemT * PtrBTItemT ;
typedef struct {
USHORT CRCSum ;
USHORT ItemsOnPage ;
ULONG BackwardPage ;
UCHAR ItemArray [ ] ;
} BTPageT ;
typedef BTPageT * PtrBTPageT ;
W przypadku drzew binarnych, istnieją sytuacje, gdzie poszczególne gałęzie mają różną długość: przykładowo drzewo o 4 elementach ma jednego „rodzica”, dwoje „dzieci” i jednego „wnuka”. Jak na to nie patrzeć, istnieje gałąź o innej długości od każdej pozostałej. W przypadku b-drzewa ten przypadek nie zaistnieje i uzyskuje się go przez zmienny stopień wypełnienia strony. Poprawnie zbudowane b-drzewo ma zawsze wszystkie gałęzie identycznej długości.