• Nie Znaleziono Wyników

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;