• Nie Znaleziono Wyników

Drzewa binarnych wyszukiwań umożliwiają wstawianie, wyszukiwanie i usuwanie elementów w czasie proporcjonalnym do wysokości drzewa. Jeśli drzewo zostało zbudowane przez wstawienie n danych z kluczami uporządkowanymi w szczególnie niekorzystny sposób, to wysokość drzewa może być równa n, a w takiej sytuacji zastosowanie drzewa nie daje żadnych korzyści w porównaniu z np. listą nieuporządkowaną z tymi danymi.

Oczywiście, maksymalną korzyść z zastosowania drzewa BST będziemy mieć, jeśli jego wysokość będzie jak najmniejsza. Drzewo binarne, które ma n wierzchołków, musi mieć wysokość co najmniej ⌊log2n⌋ + 1. Taką minimalną wysokość drzewo miałoby, gdyby było całkowicie zrównoważone, tj. gdyby wszystkie jego liście były na dwóch najwyższych poziomach, a wszystkie wierzchołki na poziomach

pozostałych miały oba poddrzewa niepuste.

Jeśli drzewo służy do zbudowania statycznego słownika, tj. zbiór danych w drzewie jest ustalony, a jedyną operacją (która będzie wykonywana wielokrotnie) jest wyszukiwanie, to można i warto zbudować takie drzewo. Jeśli jednak zawartość słownika zmienia się, to po wstawieniu lub usunięciu wierzchołka należałoby przywrócić zrównoważenie drzewa. Okazuje się, że koszt uporządkowania drzewa, który przywraca jego całkowite zrównoważenie, może być proporcjonalny do liczby wierzchołków. Takie postępowanie jest więc nieopłacalne.

Rozwiązania kompromisowe dopuszczają drzewa niezrównoważone, ale takie, których wysokość jest równa O(log n). Jednym z takich rozwiązań są drzewa AVL.

Nazwa tej struktury danych pochodzi od nazwisk jej wynalazców (Adelson-Velski, Landis).

Def. Drzewem AVL nazywamy drzewo binarnych wyszukiwań, które spełnia następujący warunek: dla każdego wierzchołka różnica wysokości poddrzew tego wierzchołka nie może być większa niż 1.

Okazuje się, że powyższy warunek zapewnia, że wysokość drzewa jest rzędu log n, a ponadto umożliwia wstawianie i usuwanie wierzchołków w czasie

proporcjonalnym do wysokości. Aby warunek był spełniony, wstawiając lub usuwając wierzchołek nie musimy nigdy przebudowywać całego drzewa; wystarczy tylko wykonać pewne operacje wzdłuż ścieżki od korzenia do odpowiedniego wierzchołka.

14.2

Udowodnimy, że wysokość drzewa AVL z n wierzchołkami jest rzędu log n.

Drzewo o wysokości 1 ma zawsze 1 wierzchołek (tj. korzeń). Drzewo o wysokości 2 ma 2 lub 3 wierzchołki. Drzewo o wysokości h ma co najmniej C(h) wierzchołków, przy czym dla h > 2

C(h) = C(h − 1) + C(h − 2) + 1.

Powyższy wzór wziął się stąd, że przyjmujemy, że wysokości poddrzew korzenia różnią się o 1 (tj. drzewo w korzeniu jest tak niezrównoważone, jak tylko dopuszcza to definicja drzewa AVL). Każde z poddrzew zawiera najmniejszą dopuszczalną liczbę wierzchołków, a ostatni składnik, tj. 1, uwzględnia wierzchołek w korzeniu.

Jawną postać funkcji C(h) otrzymamy rozwiązując równanie różnicowe.

Rozwiązaniem równania jednorodnego ah= ah−1+ ah−2jest ciąg ah= eλh1+ fλh2,

gdzie λ1=12(1 +√

5), λ2=12(1 −√

5)są pierwiastkami wielomianu charakterystycznego λ2− λ − 1, a e i f są dowolnymi liczbami. Ponieważ liczba µ = 1 nie jest pierwiastkiem tego wielomianu, więc istnieje rozwiązanie szczególne równania niejednorodnego, które jest wielomianem stopnia 0 (tj.

ciągiem stałym, C(h) = c). Po podstawieniu do tego równania otrzymujemy c = 2c + 1, czyli c = −1,

zatem ogólne rozwiązanie równania niejednorodnego ma postać C(h) = eλh1+ fλh2− 1.

Liczby e i f znajdziemy na podstawie warunków początkowych, C(1) = 1, C(2) = 2. Mamy stąd układ równań liniowych,

λ1e + λ2f = 2, λ21e + λ22f = 3,

którego rozwiązanie daje nam e =101(5 + 3√

5), f =101(5 − 3√

5). Zauważmy, że λ1> 1, |λ2| < λ1, oraz e > −f > 0. Liczbę n wierzchołków w drzewie AVL o wysokości h oszacujemy tak:

n≥ C(h) = eλh1+ fλh2− 1 > (e + f)λh1− 1.

Ponieważ e + f = 1, więc mamy stąd λh1< n + 1,

14.3

czyli

h <logλ1(n + 1) = O(log n).

Skoro udowodniliśmy, że wysokość drzewa AVL jest rzędu log n, pozostaje wykazać, że wstawianie i usuwanie wierzchołka może być wykonane w czasie proporcjonalnym do wysokości drzewa. Wstawianie wierzchołka do drzewa AVL zaczyna się od wstawienia wierzchołka w taki sam sposób, jak do zwykłego drzewa BST, a następnie należy przywrócić zrównoważenie drzewa, jeśli zostało ono naruszone. Podobnie, usunięcie wierzchołka może doprowadzić do niespełnienia warunku definiującego drzewo AVL, a zatem należy potem tak przekształcić drzewo, aby ten warunek przywrócić.

Elementarną operacją, która służy do przebudowywania drzewa jest tzw. rotacja.

Najlepiej wyjaśnia ją rysunek:

Przypuśćmy, że drzewo na początku ma wierzchołki i poddrzewa takie, jak na rysunku z lewej strony. Poddrzewa T1, T2i T3mogą być dowolne, także puste.

Rotacja w prawo przebuduje to drzewo tak, że powstanie układ z prawej strony.

Odwrotną operacją jest rotacja w lewo. Zauważmy, że jeśli przed wykonaniem rotacji klucze w wierzchołkach drzewa są uporządkowane zgodnie z zasadą budowy drzewa BST, to po wykonaniu rotacji drzewo dalej będzie poprawnym drzewem BST.

Każdy wierzchołek drzewa AVL ma zwykłe atrybuty wierzchołka drzewa binarnych wyszukiwań, tj. klucz, ewentualne informacje związane z kluczem i wskaźniki lewego i prawego poddrzewa (może też być wskaźnik „do góry”).

Do wykrywania sytuacji, gdy rotacje są potrzebne, służy dodatkowy atrybut wierzchołka, będący wskaźnikiem zrównoważenia. Przypuśćmy, że atrybut ten ma nazwę eqi. Jego wartością jest różnica wysokości lewego i prawego poddrzewa.

Zatem, w drzewie AVL wartość tego atrybutu jest równa 1, jeśli lewe poddrzewo danego wierzchołka jest wyższe, 0 jeśli oba poddrzewa mają tę samą wysokość i −1, jeśli wyższe jest poddrzewo prawe.

14.4

Wyprowadzimy wzory, umożliwiające obliczenie wskaźników zrównoważenia po wykonaniu rotacji w prawo na podstawie wskaźników zrównoważenia przed rotacją, a następnie napiszemy procedurę rotacji. Wysokości poddrzew T1, T2i T3 oznaczymy odpowiednio h1, h2, h3. Wskaźniki zrównoważenia wierzchołków a i b w początkowym drzewie oznaczymy symbolami a0i b0, a w końcowym drzewie symbolami a1i b1.

void RotacjaWPrawo ( pvertex *root ) {

pvertex a, b;

int a0, b0, b1;

b = *root; a = b->lewy; /* zakładamy, że a != NULL */

b->lewy = a->prawy; a->prawy = b;

*root = a; /* nowym korzeniem jest wierzchołek a */

a0 = a->eqi; b0 = b->eqi;

Procedura RotacjaWLewo wygląda podobnie, w związku z czym wyprowadzenie potrzebnych wzorów i jej napisanie pozostawiam jako ćwiczenie. Obie procedury zastosujemy teraz do wstawiania wierzchołka do drzewa AVL.

14.5

Przypuśćmy, że w pewnym wierzchołku drzewa BST jedno z poddrzew pewnego wierzchołka (dla ustalenia uwagi lewe) ma wysokość większą o 2 od drugiego (prawego) poddrzewa, przy czym oba te poddrzewa są drzewami AVL. Wtedy, zależnie od budowy lewego poddrzewa, należy dokonać jednej albo dwóch rotacji, aby otrzymać drzewo AVL. Pierwsza sytuacja (gdy wystarczy jedna rotacja) jest

na rysunku.

Jak widać, wystarczyło dokonać jednej rotacji w prawo, w wierzchołku c; nowym korzeniem stał się wierzchołek b. Jeśli lewe poddrzewo ma prawe poddrzewo wyższe, to potrzebna jest podwójna rotacja; najpierw trzeba dokonać rotacji lewego poddrzewa w lewo, co spowoduje powstanie drzewa o budowie jak z lewej strony na rysunku wyżej, a następnie można dokonać rotacji w prawo.

Zauważmy, że w pierwszym przypadku nie ma znaczenia, czy poddrzewa T1i T2

mają jednakową wysokość, czy jedno z nich jest o 1 wyższe. Podobnie, w drugim przypadku poddrzewa T2i T3mogą mieć jednakową wysokość, lub jedno z nich może być wyższe o 1, a końcowy wynik będzie poprawny. Jeśli prawe poddrzewo ma wysokość większą o 2 od poddrzewa lewego, to też mamy dwa sposoby przywracania zrównoważenia, które trzeba stosować w sytuacjach symetrycznych do narysowanych wyżej dwóch.

14.6

Podprogram wstawiania wierzchołka do drzewa AVL jest rekurencyjną procedurą, której wartość niezerowa oznacza, że wysokość drzewa wzrosła (oczywiście o 1).

char AVLInsert ( typklucza k, typdanej d, pvertex *root ) {

if ( !(*root) ) {

*root = malloc ( sizeof(vertex) );

(*root)->klucz = k; (*root)->dana = d;

(*root)->lewy = (*root)->prawy = NULL; (*root)->eqi = 0;

return 1;

if ( (*root)->lewy->eqi < 0 ) /* potrzebna jest podwójna */

/* rotacja */

RotacjaWLewo ( &(*root)->lewy );

RotacjaWPrawo ( root );

return 0; /* rotacja przywróciła początkową wysokość */

}

else return (*root)->eqi == 1;

}

else return 0;

} else {

... /* tu wstawiamy wierzchołek do prawego poddrzewa, */

/* co wymaga "symetrycznych" działań */

}

} /*AVLInsert*/

Podprogram usuwania wierzchołka z drzewa AVL jest bardziej skomplikowany (zobacz Uzupełnienia). Odnotujmy, że jeśli potrzebujemy „wyjąć” z poddrzewa wierzchołek z największym kluczem w tym poddrzewie (po to, aby stał się on korzeniem poddrzewa w miejsce usuwanego korzenia), to wierzchołek ten albo będzie liściem, albo będzie miał niepuste lewe poddrzewo złożone z jednego liścia.

Usunięcie wierzchołka może obniżyć wysokość poddrzewa (tylko o 1), dlatego też może zajść konieczność przywrócenia zrównoważenia drzewa we wszystkich wierzchołkach po drodze do korzenia całego drzewa.

14.7

Przykład. Do początkowo pustego drzewa AVL wstawimy wierzchołki z kluczami 7, 9, 10, 4, 3, 2, 6, 5. Na rysunku jest pokazana struktura drzewa przed i po każdej rotacji.

1. Napisz procedurę rotacji w lewo, która odpowiednio uaktualni wskaźniki zrównoważenia.

2. Napisz brakującą część podprogramu AVLInsert, tj. kod wstawiający wierzchołek do prawego poddrzewa i przywracający w razie potrzeby zrównoważenie drzewa.

3. Uzupełnij procedury rotacji tak, aby nadawały się do obsługi drzew, w których oprócz wskaźników lewy i prawy (do poddrzew), każdy wierzchołek ma wskaźnik gora, którego wartością jest najbliższy wierzchołek na drodze do korzenia (w korzeniu jego wartością ma być NULL).

4. Dla drzewa AVL, którego wierzchołki zawierają wskaźniki do góry, napisz nierekurencyjną procedurę wstawiania nowego wierzchołka.

5. Wykaż, że jeśli do początkowo pustego drzewa AVL wstawimy n = 2h− 1 wierzchołków, których klucze będą uporządkowane rosnąco, to otrzymane na końcu drzewo będzie całkowicie zrównoważone i jego wysokość będzie równa h.

6. Do drzewa, które było przykładem na wykładzie, został wstawiony klucz 8. Jakie rotacje muszą być wykonane, aby przywrócić zrównoważenie drzewa?

7. Narysuj drzewa AVL, które powstaną w wyniku usuwania z drzewa w poprzednim zadaniu kolejno wierzchołków zawierających najmniejszy w danej chwili klucz.

8. Pewne drzewo binarne spełnia następujący warunek: dla każdego wierzchołka wartość bezwzględna różnicy wysokości poddrzew tego wierzchołka jest nie większa niż 2. Czy stąd wynika, że istnieje stała c, taka że wysokość takiego drzewa o n wierzchołkach jest nie większa niż c log2n?.

14.9