Zadanie: Marsjańskie mapy
6.4. Wzbogacane drzewa binarne
Po wnikliwej analizie poprzednich rozdziałów uważny czytelnik Literatura [WDA] - 15 [SZP] - 6.2.2, 6.2.3 zaobserwuje, że istnieje duże podobieństwo pomiędzy
poszczegól-nymi rodzajami prezentowanych drzew. Pomimo różnych operacji przez nie realizowanych, w większości przypadków sposób wyz-naczania dodatkowych informacji, które umożliwiaj¸a efektywn¸a
re-alizację tych operacji, jest taki sam — wartość informacji w węźle wewnętrznym drzewa jest zależna wył¸acznie od tego węzła oraz od jego synów. Można w takim wypadku stworzyć sz-ablon, pozwalaj¸acy na konstruowanie różnego rozdziału drzew, których funkcje wstawiania oraz usuwania węzłów będ¸a identyczne, a jedyne różnice polegać będ¸a na zasobie informacji przechowywanych w węzłach oraz na sposobie wyliczania ich wartości. Różnice pojawiać się również będ¸a w zakresie zbioru funkcji, pozwalaj¸acych na realizację dodatkowych operacji.
Implementacja tych funkcji jest jednak prosta, gdyż nie s¸a w nich dokonywane modyfikac-je struktury drzewa (dodawania i usuwania wierzchołków). W niniejszym rozdziale przed-stawimy dwie takie struktury danych. Pierwsz¸a z nich będ¸a wzbogacane drzewa binarnych poszukiwań, o których wiadomo, że w pesymistycznych przypadkach czas wykonywania op-eracji może być liniowy. Następnie przedstawimy modyfikację tych drzew, które będ¸a różniły się realizacj¸a funkcji wstawiania nowych węzłów — modyfikacja polega na wykonywaniu losowych zmian struktury drzewa, maj¸acych na celu zapobieganie zbytniemu rozrastaniu się drzewa w gł¸ab.
Przedstawiona na listingu 6.19 implementacja strukturyBSTstanowi klasyczn¸a realizację binarnego drzewa wyszukiwań. Główn¸a zalet¸a tej implementacji jest możliwość określenia dodatkowych informacji, które należy przechowywać w wierzchołkach drzewa. Podczas kon-strukcji drzewa BST podaje się strukturę element, która ma być części¸a tworzonych wierz-chołków oraz określa się dwie funkcje f i g. Zadaniem funkcji f o sygnaturze int f(const element&, constelement&)jest określanie liniowego porz¸adku na zbiorze elementów prze-chowywanych w drzewie. Jeśli element określony przez pierwszy parametr jest większy od el-ementu drugiego, to funkcja ta powinna zwracać wartość 1. Jeśli element drugi jest mniejszy od pierwszego, to wartości¸a zwracan¸a jest 0, natomiast jeśli elementy s¸a równe, to funkc-ja powinna zwrócić −1. Druga funkcfunkc-ja — g, o sygnaturze void g(element*, element*, element*) przyjmuje jako parametry trzy wskaźniki na wierzchołki drzewa — odpowied-nio ojca oraz jego lewego i prawego syna (w przypadku, gdy któryś z synów nie istnieje, to odpowiedni wskaźnik ma wartość 0). Zadaniem funkcji jest zaktualizowanie informacji wierzchołka-ojca na podstawie informacji z jego synów. Przykładowa implementacja drzew pozycyjnych, pozwalaj¸aca na wyszukiwanie k-tego najmniejszego elementu w drzewie, która bazuje na strukturzeBST została przedstawiona na listingu 6.20.
Listing 6.19: Implementacja strukturyBST // Implementacja struktury BST
01 template <typename T> struct BST { // Struktura węzła drzewa
02 struct Ve {
// Wskaźniki na lewego i prawego syna
03 Ve *s[2];
// Pole e zawiera element przechowywany w węźle
04 T e;
05 Ve() {
06 s[0] = s[1] = 0;
Listing 6.19: (c.d. listingu z poprzedniej strony) 07 }};
// sygnatury funkcji do porównywania elementów oraz do aktualizacji ich // zawartości
08#define Less int (*Less)(const T&, const T&) 09#define Upd void (*Upd)( T*, T*, T*) 10 Less;
11 Upd;
// Korzeń drzewa oraz dodatkowa zmienna pomocnicza 12 Ve *root, *v;
// Wektor wskaźników do węzłów drzewa, wykorzystywany do wyznaczania ścieżki od // przetwarzanego węzła do korzenia drzewa
13 vector<Ve *> uV;
// Funkcja usuwa dany węzeł wraz z jego całym poddrzewem 14 void Rem(Ve * p) {
15 if (p) {
16 REP(x, 2) Rem(p->s[x]);
17 deletep;
18 }
19 }
// Destruktor drzewa zwalnia zaalokowaną pamięć 20 ∼BST() {
21 Rem(root);
22 }
// Konstruktor drzewa przyjmuje jako parametry dwie funkcje - funkcję
// określającą porządek na elementach oraz funkcję służącą do aktualizowania // informacji elementów
23 BST( Less, Upd) : Less(Less), Upd(Upd) { 24 root = 0;
25 }
// Funkcja aktualizuje wartości elementów, znajdujących się na ścieżce od // ostatnio przetwarzanego węzła do korzenia drzewa
26 void Update() {
27 for(int x = SIZE(uV) - 1; x >= 0 && (v = uV[x]); x--)
28 Upd(&v->e, v->s[0] ? &(v->s[0]->e) : 0, v->s[1] ? &(v->s[1]->e) : 0);
29 uV.clear();
30 }
// Funkcja pomocnicza wyszukująca węzeł w drzewie zawierający zadany element 31 Ve *Find(const T & e, bool s) {
// Funkcja wyszukuje zadany element w drzewie 40 T *Find(const T & e) {
Listing 6.19: (c.d. listingu z poprzedniej strony) 41 return (v = Find(e, 0)) ? &(v->e) : 0;
42 }
// Funkcja wstawia zadany element do drzewa 43 voidInsert(const T & e) {
// Jeśli drzewo nie jest puste...
44 if (root) {
// Znajdź odpowiednie miejsce w drzewie dla dodawanego elementu i go dodaj 45 Find(e, 1);
46 v = uV.back();
47 intcmp = Less(v->e, e);
48 if (cmp != -1) uV.PB(v = v->s[cmp] = newVe);
49 }
// Stwórz nowy korzeń drzewa z zadanym elementem 50 else uV.PB(v = root = new Ve);
51 v->e = e;
// Zaktualizuj elementy na ścieżce od dodanego elementu do korzenia 52 Update();
53 }
// Funkcja usuwa z drzewa węzeł zawierający zadany element 54 boolDelete(const T & e) {
55 Ve *c = Find(e, 1), *k;
// Jeśli elementu nie ma w drzewie, to przerwij 56 if (!c) {
57 uV.resize(0);
58 return 0;
59 }
60 if (c != root) uV.PB(c);
// Jeśli węzeł ma obu synów, to musi nastąpić zamiana elementów w węźle // aktualnym z kolejnym elementem...
61 if (c->s[0] && c->s[1]) {
62 for (k = c->s[1]; k->s[0]; k = k->s[0])
63 uV.PB(k);
64 uV.PB(k);
65 c->e = k->e;
66 c = k;
67 }
// Usunięcie odpowiednich węzłów z drzewa 68 if (SIZE(uV) > 1)
69 (k = uV[SIZE(uV) - 2])->s[k->s[0] != c] = c->s[0] ? c->s[0] : c->s[1];
70 else root = c->s[0] ? c->s[0] : c->s[1];
// Aktualizacja elementów na ścieżce 71 Update();
72 deletec;
73 return 1;
74 } 75};
Listing 6.20: Przykład wykorzystania strukturyBST.
Dodawanie do drzewa elementu 10 Dodawanie do drzewa elementu 15 1 statystyka pozycyjna to: 10 2 statystyka pozycyjna to: 15 Dodawanie do drzewa elementu 1 1 statystyka pozycyjna to: 1 2 statystyka pozycyjna to: 10 3 statystyka pozycyjna to: 15
W drzewie nie ma wezla bedacego 4 statystyka pozycyjna Usuwanie z drzewa elementu 10
2 statystyka pozycyjna to: 15
Listing 6.21: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 6.20. Pełny kod źródłowy programu znajduje się w pliku bst.cpp
// Element stanowiący zawartość wierzchołków drzewa 01 struct Element {
02 int val, stat;
03};
// Funkcja określająca porządek na wierzchołkach drzewa // w kolejności rosnących wartości val
04 intCmp(const Element &a, const Element &b) { 05 return (a.val == b.val) ? -1 : a.val < b.val;
06}
// Funkcja aktualizująca wartość stat wierzchołka 07 void Update(Element *p, Element *ls, Element *rs){
08 p->stat = 1 + (ls ? ls->stat : 0);
09}
// Funkcja zwraca wskaźnik do elementu drzewa, // stanowiącego k-tą statystykę pozycyjną 10Element* Stat(BST<Element> &t, int k) { 11 VAR(v, t.root);
12 while(v && k != v->e.stat) {
13 if (v->s[0] && v->s[0]->e.stat >= k) v = v->s[0];
14 else {
15 if (v->s[0]) k -= v->s[0]->e.stat;
16 k--;
17 v = v->s[1];
18 }
19 }
20 return v ? &(v->e) : 0;
21} 22
23 intmain() {
// Skonstruuj drzewo binarne wzbogacając je obiektami typu Element // oraz wykorzystując funkcje Cmp i Update
24 BST<Element> Tree(Cmp,Update);
Listing 6.21: (c.d. listingu z poprzedniej strony) 25 Element e, *p;
26 intcmd;
// Wczytaj operacje do wykonania i je wykonaj 27 while(cin >> cmd >> e.val) {
28 if (cmd == 0) { 29 Tree.Insert(e);
30 cout << "Dodawanie do drzewa elementu " << e.val << endl;
31 } else if(cmd == 1) {
32 cout << "Usuwanie z drzewa elementu " << e.val << endl;
33 Tree.Delete(e);
34 } else {
35 p = Stat(Tree, e.val);
36 if (!p)
37 cout << "W drzewie nie ma wezla bedacego " << e.val <<
38 " statystyka pozycyjna" << endl;
39 else cout << e.val << " statystyka pozycyjna to: " << p->val << endl;
40 }
41 }
42 return0;
43}
Implementacja randomizowanych drzew binarnych, których kod źródłowy przedstawiony jest na listingu 6.22, różni się odBSTtym, że podczas wykonywania operacji wstawiania do drzewa nowych elementów, wykonywane s¸a losowe rotacje. W pozostałych operacjach, wykorzystanie drzewRBSTjest dokładnie takie samo jak BST — przykład przedstawiony został na listingu 6.23.
Listing 6.22: Implementacja strukturyRBST // Implementacja struktury RBST
001 template <typename T> struct RBST { // Struktura węzła drzewa
002 struct Ve {
// Wskaźniki na lewego i prawego syna 003 Ve *s[2];
// Pole e zawiera element przechowywany w węźle
004 T e;
// Zmienna wykorzystywana do realizacji rotacji na drzewie 005 int w;
006 Ve(){s[0] = s[1] = 0;}
007 };
// Sygnatury funkcji do porównywania elementów oraz do aktualizacji ich zawartości 008#define Less int (*Less)(const T&, const T&)
009#define Upd void (*Upd)( T*, T*, T*) 010 Less;
011 Upd;
// Korzeń drzewa oraz dodatkowa zmienna pomocnicza 012 Ve *root, *v;
Listing 6.22: (c.d. listingu z poprzedniej strony)
// Wektor wskaźników do węzłów drzewa wykorzystywany do wyznaczania ścieżki // od przetwarzanego węzła do korzenia drzewa
013 vector<Ve*> uV;
// Funkcja usuwa dany węzeł wraz z jego całym poddrzewem 014 voidRem(Ve* p) {
// Destruktor drzewa zwalnia zaalokowaną pamięć 020 ∼RBST() {
021 Rem(root);
022 }
// Konstruktor drzewa przyjmuje jako parametry dwie funkcje - określającą porządek // na elementach oraz służącą do aktualizowania informacji elementów
023 RBST(Less, Upd) : Less(Less), Upd(Upd) { 024 root = 0;
025 }
// Funkcja aktualizuje wartości elementów znajdujących się na ścieżce od ostatnio // przetwarzanego węzła do korzenia drzewa
026 voidUpdate(vector<Ve*> &uV) {
027 for(intx = SIZE(uV) - 1; x >= 0 && (v = uV[x]); x--) {
028 v->w = 1 + (v->s[0] ? v->s[0]->w : 0) + (v->s[1] ? v->s[1]->w : 0);
029 Upd(&v->e, v->s[0] ? &(v->s[0]->e) : 0, 030 v->s[1] ? &(v->s[1]->e) : 0);
031 }
032 uV.clear();
033 }
// Funkcja pomocnicza wyszukująca węzeł w drzewie zawierający zadany element 034 Ve* Find(const T& e, bool s) {
035 v = root;
// Funkcja wyszukuje zadany element w drzewie 043 T* Find(const T& e) {
044 return (v = Find(e,0)) ? &(v->e) : 0;
045 }
// Ponieważ operacja wykonywania rotacji na drzewie wymaga przechowywania // w węzłach informacji na temat wielkości poddrzew, zatem informację tą można // wykorzystać do wyznaczania statystyk pozycyjnych
046 T* StatPos(intnr) { 047 Ve* v = root;