|
Serwis Edukacyjny w I-LO w Tarnowie
Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2026 mgr Jerzy Wałaszek
|
Drzewo binarnych wyszukiwań BST (ang. BST – Binary Search Tree) jest dobrą strukturą danych do szybkiego
wyszukiwania elementów. Jeśli wysokość drzewa jest minimalna, to operacja
wyszukiwania elementu posiada pesymistyczną klasę złożoności obliczeniowej
Niestety, drzewa BST w klasycznej postaci posiadają nieprzyjemną wadę
– mogą się degenerować do list liniowych, a wtedy tracimy ich podstawową
zaletę – klasę złożoności
Drzewo AVL jest drzewem BST, w którym dla każdego węzła wysokość jego poddrzew dla lewego i prawego syna różni się co najwyżej o 1. Aby osiągnąć ten warunek, drzewo AVL posiada zmodyfikowane procedury wstawiania i usuwania węzłów. Równoważenie uzyskuje się poprzez odpowiednie rotacje w lewo i w prawo węzłów drzewa na ścieżce w kierunku korzenia, jeśli został zaburzony warunek drzewa AVL (wysokości poddrzew różnią się co najwyżej o 1) po wstawieniu nowego węzła lub po usunięciu istniejącego węzła.
Każdy węzeł drzewa AVL posiada dodatkowe pole o nazwie bf (ang. balance factor – współczynnik równowagi). Wartość tego pola spełnia równanie:
bf = hL-hR gdzie: hL i hR to odpowiednio wysokości lewego i prawego poddrzewa.
Z powyższego wzoru możemy wysnuć następujące wnioski:
bf = 1 – lewe poddrzewo jest wyższe o jeden poziom od prawego poddrzewa, hL > hR bf = 0 – oba poddrzewa są równej wysokości, hL = hR bf = -1 – prawe poddrzewo jest wyższe o jeden poziom od lewego poddrzewa, hL < hR
Innych wartości w drzewie AVL współczynnik bf nie może przyjmować – ograniczenie to nosi nazwę własności drzewa AVL (ang. AVL tree property). Poniżej przedstawiamy dwa drzewa, w których węzły oprócz kluczy posiadają również pola bf zapisane po dwukropku:
![]() Drzewo spełnia własność AVL |
![]() Drzewo nie spełnia własności AVL |
W prawym drzewie węzeł o kluczu 20 posiada współczynnik równowagi równy -2, ponieważ dla tego węzła mamy:
hL = 0 hR = 2 bf = hL-hR = 0-2 = -2
Węzły drzew AVL będziemy reprezentowali następującą strukturą:
Pascaltype
PAVLnode = ^AVLnode;
AVLnode = record
up : PAVLnode;
left : PAVLnode;
right : PAVLnode;
key : typ_danych;
bf : integer;
// opcjonalnie
Data: typ_danych;
end; |
struct AVLnode
{
AVLnode * up;
AVLnode * left;
AVLnode * right;
typ_danych key;
int bf;
// opcjonalnie
typ_danych data
}; |
BasicType AVLnode up As AVLnode Ptr left As AVLnode Ptr right As AVLnode Ptr key As typ_danych bf As Integer ' opcjonalnie data As typ_danych End Type |
| Python (dodatek) |
class AVLnode:
def __init__(self):
self.up = None
self.left = None
self.right = None
self.key = 0
self.bf = 0
# opcjonalnie
self.data = 0
|
Znaczenie poszczególnych pól jest następujące:
Wyszukiwanie informacji w drzewie AVL niczym nie różni się od wyszukiwania informacji w drzewie BST, zatem możemy stosować tutaj ten sam algorytm. Dotyczy to również znajdowania poprzedników i następników węzła. Różnice pojawiają się przy wstawianiu i usuwaniu węzłów, ponieważ operacje te mogą prowadzić do zmiany wysokości poddrzew. Operacje insert oraz delete dla drzewa AVL składają się z dwóch faz – pierwsza faza jest identyczna jak w przypadku drzewa BST, druga faza ma za zadanie sprawdzić, czy pierwsza faza nie naruszyła własności AVL, a jeśli tak, to własność AVL musi zostać przywrócona.
Własność AVL przywracamy przy pomocy tzw. rotacji, czyli odpowiedniego przemieszczenia 3 węzłów. Istnieją 4 rodzaje rotacji – dwie pojedyncze oraz dwie podwójne:
Oznaczenia RR, LL, RL i LR określają sposób połączenia węzłów przed wykonaniem rotacji. Jeśli węzły łączą się prawymi krawędziami, mamy do czynienia z przypadkiem RR, jeśli tylko lewymi, to jest to przypadek LL. Mieszane połączenia są oznaczane jako LR i RL.
Rotacje wykonujemy, gdy natrafimy w drzewie AVL na węzeł o parametrze bf równym 2 lub -2. Taka wartość parametru bf świadczy o niezrównoważeniu węzła – jego poddrzewa różnią się wysokością o 2. Rotacja przywraca równowagę w węźle. Załóżmy, iż w wyniku wstawiania lub usuwania otrzymaliśmy następujący układ węzłów:

Widzimy wyraźnie, iż węzeł 1 posiada niezrównoważone poddrzewa – lewe jest puste, a prawe ma wysokość 2. Stąd jego parametr bf przyjmuje wartość -2. Dokonujemy rotacji RR węzłów 1, 2 i 3 w punkcie zaczepienia 1 i przywracamy równowagę:

Węzeł 1 stał się lewym synem węzła 2. Węzeł 2 zajął miejsce węzła 1. Rotacja zmniejszyła o 1 wysokość poddrzewa prawego i zwiększyła o 1 wysokość poddrzewa lewego. Skoro prawe było o dwa poziomy wyższe od lewego, to teraz się zrównały. To jest właśnie cel wykonywania rotacji – zrównoważenie wysokości poddrzew, gdy różnią się one od siebie wysokością o więcej niż 1. Zwróć uwagę, iż rotacja nie zmienia własności drzewa BST ani kolejności przechodzenia inorder. Rotację LL wykonujemy analogicznie – jest ona lustrzanym odbiciem rotacji RR.
Problem pojawia się przy poniższej konfiguracji węzłów (RL):

Takiego układu nie można po prostu obrócić obrotem RR, ponieważ wystąpią konflikty pomiędzy niezaznaczonymi na rysunku synami węzłów. Najpierw dokonujemy obrotu LL węzłów 2 i 3 (sprowadza on węzły do układu RR), a następnie obracamy RR węzły 1 i 2. Całość nosi nazwę obrotu podwójnego RL:

Ponieważ pojedynczy obrót nie zmienia własności BST, również obrót podwójny nie zmienia jej, gdyż składa się on z dwóch obrotów pojedynczych.
Podamy teraz dokładne algorytmy wykonywania poszczególnych rotacji węzłów, które uwzględniają posiadanie przez węzły synów. Węzeł biegunowy rotacji oznaczamy kolorem czerwonym.

W rotacji uczestniczą węzły A i B. Węzeł B zajmuje miejsce węzła A, węzeł A staje się lewym synem węzła B. Lewy syn węzła B (BL) staje się prawym synem węzła A. Aby wyznaczyć współczynniki równowagi bf obu węzłów A i B po rotacji, rozważmy poniższe przypadki:

Przypadek nr 1
A.bf =-2,
B.bf = -1
Zaczynamy od węzła B. Ponieważ B.bf = -1, to przeważa prawa gałąź BR. Wynika to bezpośrednio ze wzoru na współczynnik równowagi:
B.bf = hBL-hBR, B.bf = -1, hBL-hBR = -1, hBR = hBL+1
Oznaczmy wysokość lewego poddrzewa węzła B przez h, czyli hBL = h. Wtedy prawe poddrzewo węzła B będzie posiadało wysokość hBR = h+1, o jeden większą od lewego poddrzewa.
Teraz obliczymy wysokość poddrzewa, którego korzeniem jest węzeł B. Wzór jest bardzo prosty – do wysokości najwyższego poddrzewa dodajemy jedynkę, czyli:
hB = 1+max(hBL, hBR)
U nas najwyższe jest prawe poddrzewo, zatem:
hB = 1+hBR = 1+h+1 = h+2
Przechodzimy do węzła A. Ponieważ jego współczynnik równowagi wynosi -2, to wnioskujemy, iż przeważa o dwa poziomy prawe poddrzewo A:
A.bf = hAL-hAR, A.bf = -2, hAL-hAR = -2, hAL = hAR-2
Prawe poddrzewo węzła A jest poddrzewem, którego korzeń znajduje się w wierzchołku B. Stąd:
hAR = hB = h+2
I ostatecznie otrzymujemy:
hAL = hAR-2 = h+2-2 = h
Wyznaczyliśmy względne wysokości wszystkich synów wierzchołków A i B dla rozważanego przypadku:
hAL = h hBL = h hBR = h+1
Podkreślam, że nie interesują nas wartości h – to zależy już od konkretnego drzewa AVL. Mając wyznaczone względne wysokości poddrzew możemy wyliczyć współczynniki równowagi bf węzłów A i B po rotacji:
A.bf = hAL-hBL = h-h = 0 B.bf = hA-hBR = h+1-(h+1) = 0

Przypadek nr 2
A.bf = -2,
B.bf = 0
Wykonując podobne operację (wykonaj je sam jako ćwiczenie) w przypadku drugim dochodzimy do wniosku, iż po rotacji węzły A i B otrzymają następujące współczynniki równowagi:
A.bf = -1 B.bf = 1

Przypadek nr 3
A.bf = -2,
B.bf = 1
W przypadku nr 3 otrzymujemy po rotacji:
A.bf = -1 B.bf = 2
Wynika z tego prosty wniosek – w takim układzie współczynników równowagi węzłów rotacja RR nie rozwiązuje problemu równowagi drzewa AVL. Nie będziemy zatem jej stosować dla przypadku nr 3! Zamiast niej będą stosowane rotacje podwójne.
| Przed rotacją | ||
| A.bf | -2 | -2 |
| B.bf | -1 | 0 |
| Po rotacji RR | ||
| A.bf | 0 | -1 |
| B.bf | 0 | 1 |
Dane pomocnicze:
K01: B ← A→right ; w B umieszczamy adres ; prawego syna węzła A K02: p ← A→up ; w p umieszczamy ojca A K03: A→right ← B→left ; prawym synem A ; staje się lewy syn B K04: Jeśli A→right ≠ nil, ; jeśli prawy syn to A→right→up ← A ; istnieje, to jego ojcem ; jest teraz węzeł A K05: B→left ← A ; lewym synem B staje się A K06: B→up ← p ; ojcem B jest ojciec węzła A K07: A→up ← B ; natomiast A zmienia ojca na B K08: Jeśli p = nil, ; sprawdzamy, czy węzeł A to idź do kroku K11 ; był korzeniem K09: Jeśli p→left = A, ; jeśli nie, to to p→left ← B ; uaktualniamy jego ojca inaczej p→right ← B K10: Idź do kroku K12 K11: root ← B ; jeśli A był korzeniem, ; to uaktualniamy korzeń K12: Jeśli B→bf = -1, to A→bf ← 0 ; modyfikujemy współczynniki B→bf ← 0 ; równowagi inaczej A→bf ← -1 B→bf ← 1 K13: Zakończ

Rotacja LL jest lustrzanym odbiciem rotacji RR. W rotacji
uczestniczą węzły A i B. Węzeł
B zajmuje miejsce węzła A, węzeł
A staje się
prawym synem węzła B. Prawy syn
| Przed rotacją | ||
| A.bf | 2 | 2 |
| B.bf | 1 | 0 |
| Po rotacji RR | ||
| A.bf | 0 | 1 |
| B.bf | 0 | -1 |
K01: B ← A→left ; w B umieszczamy adres ; lewego syna węzła A K02: p ← A→up ; w p umieszczamy ojca A K03: A→left ← B→right ; lewym synem A ; staje się prawy syn B K04: Jeśli A→left ≠ nil, ; jeśli lewy syn to A→left→up ← A ; istnieje, to jego ; ojcem jest teraz A K05: B→right ← A ; prawym synem B ; staje się A K06: B→up ← p ; ojcem B jest ojciec węzła A K07: A→up ← B ; natomiast A zmienia ojca na B K08: Jeśli p = nil, ; sprawdzamy, czy węzeł A to idź do kroku K11 ; był korzeniem K09: Jeśli p→left = A, ; jeśli nie, to p→left ← B ; to uaktualniamy jego ojca inaczej p→right ← B K10: Idź do kroku K12 K11: root ← B ; jeśli A był korzeniem, ; to uaktualniamy korzeń K12: Jeśli B→bf = 1, to A→bf ← 0 ; modyfikujemy współczynniki B→bf ← 0 ; równowagi inaczej A→bf ← 1 B→bf ← -1 K13: Zakończ

Rotacja RL jest złożeniem rotacji LL i rotacji RR, dlatego nosi nazwę rotacji podwójnej. Pierwsza rotacja LL sprowadza gałąź drzewa do postaci dogodnej dla następnej rotacji RR. Zamiast jednak wykonywać kolejno rotację RR, a następnie LL, zaprojektujemy algorytm, który od razu przekształca pozycje węzłów do pozycji końcowej. Węzeł C zastępuje węzeł A. Węzeł A staje się lewym dzieckiem węzła C i przejmuje lewego syna węzła C w miejsce swojego prawego syna. Węzeł B staje się prawym synem C i przejmuje prawego syna węzła C w miejsce swojego lewego syna. Określenie współczynników bf po rotacji wykonamy podobnie jak przy rotacjach pojedynczych – przeanalizujemy wysokości poddrzew przed i po rotacji podwójnej.

Przypadek nr 1
A.bf = -2,
B.bf = 1
C.bf = -1
Współczynniki bf węzłów A, B i C po rotacji wyznaczamy w podobny sposób, jak w przypadku rotacji RR. Rozpoczynamy od najniższego węzła C:
C.bf = hCL-hCR C.bf = -1 hCR = hCL+1
Zatem lżejsze jest poddrzewo lewe. Oznaczmy:
hCL = h hCR = h+1 hC = h+2
Teraz przechodzimy do węzła B:
B.bf = hBL-hBR B.bf = 1 hBR = hBL-1 = hC-1 = (h+2)-1 = h+1 hB = h+3
Na koniec pozostaje węzeł A:
A.bf = hAL-hAR A.bf = -2 hAL = hAR-2 = hB-2 = (h+3)-2 = h+1
Wyznaczyliśmy względne wysokości poddrzew wierzchołków A, B i C:
hAL = h+1 hBR = h+1 hCL = h hCR = h+1
Możemy zatem obliczyć współczynniki równowagi bf wierzchołków A, B i C po wykonaniu rotacji RL:
A.bf = hAL-hCL = (h+1)-h = 1 B.bf = hA-hC = (h+2)-(h+2) = 0 C.bf = hCR-hBR = (h+1)-(h+1) = 0

Przypadek nr 2
A.bf = -2
B.bf = 1
C.bf = 0
W przypadku nr 2 nie trudno obliczyć wyżej podanym sposobem (zrób to jako ćwiczenie), iż po rotacji RL wierzchołki A, B i C przyjmą następujące współczynniki równowagi:
A.bf = 0 B.bf = 0 C.bf = 0

Przypadek nr 3
A.bf = -2
B.bf = 1
C.bf = 1
W przypadku nr 3 otrzymujemy:
A.bf = 0 B.bf = -1 C.bf = 0
| Przed rotacją RL | |||
| A.bf | -2 | -2 | -2 |
| B.bf | 1 | 1 | 1 |
| C.bf | -1 | 0 | 1 |
| Po rotacji RL | |||
| A.bf | 1 | 0 | 0 |
| B.bf | 0 | 0 | -1 |
| C.bf | 0 | 0 | 0 |
Z tabelki wynika, iż współczynnik równowagi C.bf jest zawsze zerowany, a pozostałe zależą od początkowej wartości C.bf.
Dane pomocnicze:
K01: B ← A→right ; ustawiamy adresy węzłów ; biorących udział w rotacji K02: C ← B→left K03: p ← A→up K04: B→left ← C→right ; lewym synem B ; staje się prawy syn C K05: Jeśli B→left ≠ nil, ; jeśli lewy syn B to B→left→up ← B ; istnieje, to B staje się ; jego ojcem K06: A→right ← C→left ; prawym synem A ; staje się lewy syn C K07: Jeśli A→right ≠ nil, ; jeśli prawy syn A to A→right→up ← A ; istnieje, to A staje się ; jego ojcem K08: C→left ← A ; lewym synem C staje się A K09: C→right ← B ; prawym synem C staje się B K10: A→up ← C ; ojcem A staje się C K11: B→up ← C ; ojcem B staje się C K12: C→up ← p ; ojcem C staje się były ojciec A K13: Jeśli p = nil, ; sprawdzamy, czy A był korzeniem to idź do kroku K16 K14: Jeśli p→left = A, ; jeśli nie, to uaktualniamy to p→left ← C ; byłego ojca A, aby był inaczej p→right ← C ; ojcem C K15: Idź do kroku K17 K16: root ← C ; w przeciwnym razie uaktualniamy korzeń K17: Jeśli C→bf = -1, to A→bf ← 1 ; uaktualniamy współczynniki inaczej A→bf ← 0 ; równowagi K18: Jeśli C→bf = 1, to B→bf ← -1 inaczej B→bf ← 0 K19: C→bf ← 0 K20: Zakończ

Podobnie jak w przypadku rotacji pojedynczych RR i LL, rotacja LR jest lustrzanym odbiciem rotacji RL. Węzeł C zastępuje węzeł A. Węzeł A staje się prawym synem węzła C i przejmuje on prawego syna CR w miejsce swojego lewego syna. Węzeł B staje się lewym synem C i przejmuje on lewego syna C w miejsce swojego prawego syna. Współczynniki równowagi zmieniają się następująco (proponuję ci sprawdzenie tych wyników):
| Przed rotacją LR | |||
| A.bf | 2 | 2 | 2 |
| B.bf | -1 | -1 | -1 |
| C.bf | -1 | 0 | 1 |
| Po rotacji LR | |||
| A.bf | 0 | 0 | -1 |
| B.bf | 1 | 0 | 0 |
| C.bf | 0 | 0 | 0 |
Współczynnik równowagi C.bf jest zawsze zerowany, a pozostałe zależą od początkowej wartości C.bf.
K01: B ← A→left ; ustawiamy adresy węzłów biorących ; udział w rotacji K02: C ← B→right K03: p ← A→up K04: B→right ← C→left ; prawym synem B staje się ; lewy syn C K05: Jeśli B→right ≠ nil, ; jeśli prawy syn B istnieje, to B→right→up ← B ; to B staje się jego ojcem K06: A→left ← C→right ; lewym synem A staje się ; prawy syn C K07: Jeśli A→left ≠ nil, ; jeśli lewy syn A istnieje, to A→left→up ← A ; to A staje się jego ojcem K08: C→right ← A ; prawym synem C staje się A K09: C→left ← B ; lewym synem C staje się B K10: A→up ← C ; ojcem A staje się C K11: B→up ← C ; ojcem B staje się C K12: C→up ← p ; ojcem C staje się były ojciec A K13: Jeśli p = nil, ; sprawdzamy, czy A był korzeniem to idź do kroku K16 K14: Jeśli p→left = A, ; jeśli nie, to uaktualniamy to p→left ← C ; byłego ojca A, aby był ojcem C inaczej p→right ← C K15: Idź do kroku K17 K16: root ← C ; w przeciwnym razie uaktualniamy korzeń K17: Jeśli C→bf = 1, to A→bf ← -1 ; uaktualniamy współczynniki równowagi inaczej A→bf ← 0 K18: Jeśli C→bf = -1, to B→bf ← 1 inaczej B→bf ← 0 K19: C→bf ← 0 K20: Zakończ
Operacja wstawiania węzła do drzewa AVL jest dwuetapowa. Pierwszy etap jest identyczny z operacją wstawiania węzła do drzewa BST. Następnie, idąc w górę w kierunku korzenia drzewa, musimy odpowiednio zmodyfikować współczynniki równowagi węzłów i jeśli natrafimy na współczynnik bf równy 2 lub -2, to za pomocą rotacji zrównoważamy drzewo AVL – rotacja powoduje przywrócenie danemu poddrzewu wysokości sprzed wykonania operacji wstawiania. Zatem po wykonaniu tej rotacji drzewo AVL jest zrównoważone i możemy zakończyć wstawianie elementu.
K01: Utwórz nowy węzeł K02: w ← adres nowego węzła K03: w→left ← nil ; inicjujemy pola węzła K04: w→right ← nil K05: w→key ← k K06: w→bf ← 0 K07: p ← root K08: Jeśli p = nil, ; sprawdzamy, czy drzewo jest puste to root ← w i zakończ K09: Jeśli k < p→key, ; porównujemy klucze to idź do kroku K13 K10: Jeśli p→right = nil, ; jeśli prawy syn nie istnieje, to p→right ← w ; to nowy węzeł staje się prawym synem i idź do kroku K16 ; i wychodzimy z pętli K11: p ← p→right ; inaczej przechodzimy do prawego syna K12: Idź do kroku K09 ; i kontynuujemy pętlę K13: Jeśli p→left = nil, ; to samo dla lewego syna to p→left ← w i idź do kroku K16 K14: p ← p→left K15: Idź do kroku K09 K16: w→up ← p ; uzupełniamy ojca węzła K17: Jeśli p→bf = 0, ; modyfikujemy współczynniki równowagi to idź do kroku K20 ; w kierunku korzenia K18: p→bf ← 0 ; patrz: UWAGA NR 1 K19: Zakończ K20: Jeśli p→left = w, ; patrz: UWAGA NR 2 to p→bf ← 1 Inaczej p→bf ← -1 K21: r ← p→up ; będziemy się przemieszczać w górę drzewa AVL ; w poszukiwaniu węzła, który stracił równowagę w wyniku ; dodania elementu n.
K22: Dopóki r ≠ nil: ; wskazania r i p to rodzić i syn wykonuj kroki K23…K26 ; na tej ścieżce K23: Jeśli r→bf ≠ 0, ; jeśli r.bf = 0, to obie gałęzie r to idź do kroku K28 ; były w równowadze przed dodaniem węzła K24: Jeśli r→left = p, ; zwiększamy lub zmniejszamy r.bf to r→bf ← 1 ; w zależności od tego, w której gałęzi węzła r inaczej r→bf ← -1 ; został dodany węzeł w. Gałąź ta jest cięższa! K25: p ← r ; przemieszczamy się w górę drzewa K26: r ← r→up K27: Zakończ K28: Jeśli r→bf = -1, ; sprawdzamy, która z gałęzi r była cięższa to idź do kroku K32 K29: Jeśli r→right = p, ; prawa gałąź ze wstawionym elementem to r→bf ← 0 i zakończ ; równoważy lewą gałąź – kończymy K30: Jeśli p→bf = -1, ; patrz UWAGA NR 3 to rot_lr(root, r) inaczej rot_ll(root, r) K31: Zakończ K32: Jeśli r→left = p, ; lewa gałąź ze wstawionym elementem to r→bf ← 0 ; równoważy prawą gałąź i zakończ K33: Jeśli p→bf = 1, ; patrz: UWAGA NR 4 to rot_rl(root, r) inaczej rot_rr(root, r) K34: Zakończ
![]() |
UWAGA NR 1: w kroku
K17 sprawdzamy współczynnik równowagi bf ojca wstawionego węzła w. Jeśli jest on różny od zera, to go zerujemy i kończymy. Wyjaśnienie jest bardzo proste. Współczynnik bf różny od zera może posiadać w drzewie AVL wartości tylko -1 lub 1. Ponieważ dołączyliśmy nowy węzeł do pustego liścia, to przed operacją w gałęzi tej nie było żadnego poddrzewa – zatem wysokość jego była równa 0. Skoro bf ojca jest różny od 0, to w drugiej gałęzi musiał być dokładnie jeden węzeł, co dawało poddrzewo o wysokości 1. Po dodaniu nowego węzła w obu gałęziach, lewej i prawej, mamy po jednym węźle, zatem współczynnik bf ich ojca musi przyjąć wartość 0. Drzewo AVL zostaje zrównoważone, kończymy operację. |
![]() |
UWAGA NR 2: w kroku K20
wiemy, że przed operacją dodawania nowego węzła węzeł p miał współczynnik bf równy 0. Ponieważ dodaliśmy nowy węzeł w do pustego liścia węzła p, zatem druga gałąź również była pusta. Sprawdzamy, która gałąź p stała się cięższa (zwiększyła się wysokość poddrzewa) po dodaniu nowego elementu w. W zależności od wyniku ustawiamy współczynnik bf ojca wstawionego węzła na 1 (nowy węzeł w jest w lewej gałęzi węzła p i ona jest teraz cięższa) lub na -1 (nowy węzeł w jest w prawej gałęzi węzła p). |
![]() |
UWAGA NR 3: w kroku K30
wiemy, że przeważało lewe poddrzewo węzła r i jest to poddrzewo, w którym wstawiliśmy nowy element, ponieważ węzeł p stanowi jego korzeń. Musimy skorygować zrównoważenie wykonując rotację. Rodzaj rotacji wybieramy w zależności od tego, które poddrzewo węzła p jest cięższe – jeśli lewe, to mamy rotację pojedynczą LL, a jeśli prawe, to musimy wykonać rotację podwójną LR. UWAGA NR 4: tutaj mamy lustrzane odbicie sytuacji opisanej powyżej. |
Usuwanie węzła z drzewa AVL jest operacją dosyć skomplikowaną, ponieważ wymaga rozważenia dużej liczby przypadków przy przywracaniu równowagi. Przy wstawianiu węzła wystarczyło na ścieżce wiodącej od tego węzła do korzenia wykryć konfigurację węzłów, których rotacja przywracała równowagę w drzewie. Po wykonaniu rotacji drzewo AVL znów było w równowadze i operacja wstawiania mogła się zakończyć. Przy usuwaniu węzła mamy nieco inną sytuację. Tutaj poddrzewo, w którym dokonaliśmy usunięcia węzła, zmniejsza swoją wysokość, co może propagować się w górę aż do samego korzenia drzewa. Jedna rotacja zatem może nie być wystarczająca do przywrócenia równowagi w drzewie – rotacje należy kolejno wykonywać dla coraz wyższych węzłów dotąd, aż zniwelujemy zmniejszenie wysokości poddrzewa. Po drodze należy rozważać wiele różnych przypadków konfiguracji węzłów.
Zasada działania algorytmu usuwającego węzeł jest następująca:
Usuwamy węzeł x z drzewa AVL.





K01: Jeśli (x→left = 0)(x→right = 0), ; czy węzeł x posiada obu synów? to idź do kroku K05 K02: y ← avl_remove(root, pred(x)) ; rekurencyjnie usuwamy poprzednik x, ; zapamiętując go w y K03: nest ← false ; zerujemy zagnieżdżenie K04: Idź do kroku K08 K05: Jeśli x→left ≠ nil, ; węzeł posiada jednego syna lub wcale to y ← x→left x→left ← nil inaczej y ← x→right x→right ← nil K06: x→bf ← 0 ; x.bf = 0, ponieważ ewentualny syn trafił do y K07: nest ← true ; ustawiamy zagnieżdżenie K08: Jeśli y = nil, ; jeśli y istnieje, to wstawiamy go za x to idź do kroku K15 K09: y→up ← x→up ; ojcem y staje się ojciec węzła x K10: y→left ← x→left ; lewy syn x staje się lewym synem y K11: Jeśli y→left ≠ nil, ; jeśli lewy syn istnieje, to y→left→up ← y ; to jego ojcem staje się y K12: y→right ← x→right ; to samo dla prawego syna K13: Jeśli y→right ≠ nil, to y→right→up ← y K14: y→bf ← x→bf K15: Jeśli x→up = nil, ; czy x posiada ojca? to idź do kroku K18 K16: Jeśli x→up→left = x, ; synem tego ojca staje się y to x→up→left ← y inaczej x→up→right ← y K17: Idź do K19 K18: root ← y ; inaczej y wstawiamy do korzenia K19: Jeśli nest = false, ; sprawdzamy zagnieżdżenie to idź do kroku K43 K20: z ← y ; ustawiamy wskaźniki, ; będziemy szli w kierunku korzenia drzewa K21: y ← x→up K22: Dopóki y ≠ nil, ; w pętli sprawdzamy różne przypadki wykonuj kroki K23…K42 K23: Jeśli y→bf ≠ 0, to idź do kroku K26 K24: Jeśli y→left = z, ; przypadek nr 1 to y→bf ← -1 K25 Idź do K43 ; przerywamy pętlę K26: Jeśli ((y→bf ≠ 1)
(y→left ≠ z))
((y→bf ≠ -1)
(y→right ≠ z)), to idź do kroku K31 K27: y→bf ← 0 ; przypadek 2 K28: z ← y ; przechodzimy na wyższy poziom drzewa K29: y ← y→up K30: Następny obieg pętli K22 K31: Jeśli y→left = z, ; t wskazuje syna y przeciwnego do z to t ← y→right inaczej t ← y→left K32: Jeśli t→bf = 0, to idź do kroku K35 K33: Jeśli y→bf = 1, ; przypadek 3A to rot_ll(root, y) Inaczej rot_rr(root, y) K34: Idź do kroku K43 ; przerywamy pętlę K35: Jeśli y→bf ≠ t→bf, to idź do kroku K40 K36: Jeśli y→bf = 1, ; przypadek 3B to rot_ll(root, y) inaczej rot_rr(root, y) K37: z ← t ; idziemy na wyższy poziom K38: y ← t→up K39: Następny obieg pętli K22 K40: Jeśli y→bf = 1, ; przypadek 3C to rot_lr(root, y) inaczej rot_rl(root, y) K41: z ← y→up ; idziemy na wyższy poziom i kontynuujemy pętlę K42: y ← z→up K43: Zakończ z wynikiem x
|
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
| Program tworzy drzewo AVL z 32
węzłów o kluczach |
Pascal// Drzewo AVL
// Data: 22.05.2013
// (C)2013 mgr Jerzy Wałaszek
//---------------------------
program AVL;
// Definicja typu węzłów
// drzewa AVL
//----------------------
type
PAVLnode = ^AVLnode;
AVLnode = record
up : PAVLnode;
left : PAVLnode;
right : PAVLnode;
key : integer;
bf : integer;
end;
// Zmienne globalne
//-----------------
// łańcuchy do znaków ramek
var
cr, cl, cp : string;
// Procedura wypisuje drzewo
//--------------------------
procedure print_bt(sp, sn : string;
v : PAVLnode);
var
s : string;
i : integer;
begin
if v <> nil then
begin
s := sp;
if sn = cr then
s[length(s)-1] := ' ';
print_bt(s+cp, cr, v^.right);
s := Copy(sp, 1, length(sp)-2);
for i := 1 to length(s) do
write(char(ord(s[i])));
for i := 1 to length(sn) do
write(char(ord(sn[i])));
writeln(v^.key, ':', v^.bf);
s := sp;
if sn = cl then
s[length(s)-1] := ' ';
print_bt(s+cp, cl, v^.left);
end;
end;
// Procedura DFS:postorder
// usuwająca drzewo
//------------------------
procedure dfs_release(v : PAVLnode);
begin
if v <> nil then
begin
// usuwamy lewe poddrzewo
dfs_release(v^.left);
// usuwamy prawe poddrzewo
dfs_release(v^.right);
// usuwamy sam węzeł
dispose(v);
end
end;
// Rotacja RR
//-----------
procedure rot_rr(var root : PAVLnode;
A : PAVLnode);
var
B, p : PAVLnode;
begin
B := A^.right;
p := A^.up;
A^.right := B^.left;
if A^.right <> nil then
A^.right^.up := A;
B^.left := A;
B^.up := p;
A^.up := B;
if p <> nil then
begin
if p^.left = A then
p^.left := B
else
p^.right := B;
end
else
root := B;
if B^.bf = -1 then
begin
A^.bf := 0;
B^.bf := 0;
end
else
begin
A^.bf := -1;
B^.bf := 1;
end
end;
// Rotacja LL
//-----------
procedure rot_ll(var root : PAVLnode;
A : PAVLnode);
var
B, p : PAVLnode;
begin
B := A^.left;
p := A^.up;
A^.left := B^.right;
if A^.left <> nil then
A^.left^.up := A;
B^.right := A;
B^.up := p;
A^.up := B;
if p <> nil then
begin
if p^.left = A then
p^.left := B
else
p^.right := B;
end
else
root := B;
if B^.bf = 1 then
begin
A^.bf := 0;
B^.bf := 0;
end
else
begin
A^.bf := 1;
B^.bf := -1;
end
end;
// Rotacja RL
//-----------
procedure rot_rl(var root : PAVLnode;
A : PAVLnode);
var
B, C, p : PAVLnode;
begin
B := A^.right;
C := B^.left;
p := A^.up;
B^.left := C^.right;
if B^.left <> nil then
B^.left^.up := B;
A^.right := C^.left;
if A^.right <> nil then
A^.right^.up := A;
C^.left := A;
C^.right := B;
A^.up := C;
B^.up := C;
C^.up := p;
if p <> nil then
begin
if p^.left = A then
p^.left := C
else
p^.right := C;
end
else
root := C;
if C^.bf = -1 then
A^.bf := 1
else
A^.bf := 0;
if C^.bf = 1 then
B^.bf := -1
else
B^.bf := 0;
C^.bf := 0;
end;
// Rotacja LR
//-----------
procedure rot_lr(var root : PAVLnode;
A : PAVLnode);
var
B, C, p : PAVLnode;
begin
B := A^.left;
C := B^.right;
p := A^.up;
B^.right := C^.left;
if B^.right <> nil then
B^.right^.up := B;
A^.left := C^.right;
if A^.left <> nil then
A^.left^.up := A;
C^.right := A;
C^.left := B;
A^.up := C;
B^.up := C;
C^.up := p;
if p <> nil then
begin
if p^.left = A then
p^.left := C
else
p^.right := C;
end
else
root := C;
if C^.bf = 1 then
A^.bf := -1
else
A^.bf := 0;
if C^.bf = -1 then
B^.bf := 1
else
B^.bf := 0;
C^.bf := 0;
end;
// Wstawia nowy węzeł do drzewa AVL
// root - referencja korzenia
// k - klucz nowego węzła
//---------------------------------
procedure avl_insert(var root : PAVLnode;
k : integer);
var
w, p, r : PAVLnode;
t : boolean;
begin
// tworzymy dynamicznie nowy węzeł
new(w);
// inicjujemy pola
w^.left := nil;
w^.right := nil;
w^.up := nil;
w^.key := k;
w^.bf := 0;
//----------------------------------------
// FAZA 1 - wstawienie węzła do drzewa AVL
//----------------------------------------
// rozpoczynamy od korzenia
p := root;
// jeśli drzewo jest puste, to węzeł w
if p = nil then
root := w // umieszczamy w korzeniu
else
begin // inaczej szukamy miejsce dla w
while true do
// porównujemy klucze
if k < p^.key then
begin
// jeśli p nie posiada
// lewego syna, to
if p^.left = nil then
begin
// lewym synem p
// staje się węzeł w
p^.left := w;
break; // wychodzimy z pętli
end;
// inaczej przechodzimy
// do lewego syna
p := p^.left;
end
else
begin
// jeśli p nie posiada
// prawego syna, to
if p^.right = nil then
begin
// prawym synem
// staje się węzeł w
p^.right := w;
break; // wychodzimy z pętli
end;
// inaczej przechodzimy
// do prawego syna
p := p^.right;
end;
w^.up := p; // ojcem w jest p
//---------------------------------
// FAZA 2 - równoważenie drzewa AVL
//---------------------------------
if p^.bf <> 0 then
p^.bf := 0 // UWAGA NR 1
else
begin
if p^.left = w then // UWAGA NR 2
p^.bf := 1
else
p^.bf := -1;
// będziemy szli w górę drzewa
// w kierunku korzenia
// r i p wskazują ojca
// i syna na tej ścieżce
r := p^.up;
t := false;
while r <> nil do
begin
if r^.bf <> 0 then
begin
// ustalamy wynik pętli
t := true;
break; // przerywamy pętlę
end;
// inaczej modyfikujemy r.bf
if r^.left = p then
r^.bf := 1
else
r^.bf := -1;
// przechodzimy w górę
// na wyższy poziom
p := r;
r := r^.up;
end;
// jeśli r.bf = +/- 1,
// to musimy
// równoważyć drzewo
if t then
begin
if r^.bf = 1 then
begin
// gałęzie się równoważą
if r^.right = p then
r^.bf := 0
else
if p^.bf = -1 then
rot_lr(root, r)
else
rot_ll(root, r);
end
else
begin // r.bf = -1
// gałęzie się równoważą
if r^.left = p then
r^.bf := 0
else
if p^.bf = 1 then
rot_rl(root, r)
else
rot_rr(root, r);
end;
end;
end;
end;
end;
// Funkcja znajduje
// poprzednik węzła p
//-------------------
function avl_pred(p : PAVLnode)
: PAVLnode;
var
r : PAVLnode;
begin
if p <> nil then
begin
if p^.left <> nil then
begin
p := p^.left;
while p^.right <> nil do
p := p^.right;
end
else
repeat
r := p;
p := p^.up;
until (p = nil) or (p^.right = r);
end;
avl_pred := p;
end;
// Funkcja szuka w drzewie AVL
// węzła o zadanym kluczu. Jeśli
// go znajdzie, zwraca jego wskazanie.
// Jeżeli nie, to zwraca wskazanie puste.
// Parametrami są:
// p - wskazanie korzenia drzewa AVL
// k - klucz poszukiwanego węzła
//---------------------------------------
function avl_find(p : PAVLnode;
k : integer)
: PAVLnode;
begin
while (p <> nil) and
(p^.key <> k) do
if k < p^.key then
p := p^.left
else
p := p^.right;
avl_find := p;
end;
// Funkcja usuwa rekurencyjnie węzeł x
// root - referencja do zmiennej
// z adresem korzenia
// x - wskazanie usuwanego węzła
//------------------------------------
function avl_remove(var root : PAVLnode;
x : PAVLnode)
: PAVLnode;
var
t, y, z : PAVLnode;
nest : boolean;
begin
if (x^.left <> nil) and
(x^.right <> nil) then
begin
y := avl_remove(root, avl_pred(x));
nest := false;
end
else
begin
if x^.left <> nil then
begin
y := x^.left;
x^.left := nil;
end
else
begin
y := x^.right;
x^.right := nil;
end;
x^.bf := 0;
nest := true;
end;
if y <> nil then
begin
y^.up := x^.up;
y^.left := x^.left;
if y^.left <> nil then
y^.left^.up := y;
y^.right := x^.right;
if y^.right <> nil then
y^.right^.up := y;
y^.bf := x^.bf;
end;
if x^.up <> nil then
begin
if x^.up^.left = x then
x^.up^.left := y
else
x^.up^.right := y;
end
else
root := y;
if nest then
begin
z := y;
y := x^.up;
while y <> nil do
begin
if y^.bf = 0 then
begin // Przypadek nr 1
if y^.left = z then
y^.bf := -1
else
y^.bf := 1;
break;
end
else
begin
if((y^.bf = 1) and (y^.left = z)) or
((y^.bf = -1) and (y^.right = z)) then
begin // Przypadek nr 2
y^.bf := 0;
z := y;
y := y^.up;
end
else
begin
if y^.left = z then
t := y^.right
else
t := y^.left;
if t^.bf = 0 then
begin // Przypadek 3A
if y^.bf = 1 then
rot_ll(root, y)
else
rot_rr(root, y);
break;
end
else
if y^.bf = t^.bf then
begin // Przypadek 3B
if y^.bf = 1 then
rot_ll(root, y)
else
rot_rr(root, y);
z := t;
y := t^.up;
end
else
begin // Przypadek 3C
if y^.bf = 1 then
rot_lr(root, y)
else
rot_rl(root, y);
z := y^.up;
y := z^.up;
end
end
end
end
end;
avl_remove := x;
end;
//**********************
//*** PROGRAM GŁÓWNY ***
//**********************
var
// tablica kluczy węzłów
Tk : array [0..31] of integer;
i, x, i1, i2 : integer;
// korzeń drzewa AVL
root : PAVLnode;
begin
// ustawiamy łańcuchy znakowe,
// ponieważ nie wszystkie
// edytory pozwalają wstawiać
// znaki konsoli do tworzenia
// ramek.
// cr = +--
// |
// cl = |
// +--
// cp = |
// |
cr := #218#196;
cl := #192#196;
cp := #179#32;
randomize;
// Tworzymy puste drzewo AVL
root := nil;
// Tablicę wypełniamy wartościami kluczy
for i := 0 to 31 do
Tk[i] := i+1;
// Mieszamy tablicę
for i := 1 to 300 do
begin
// Losujemy 2 indeksy
i1 := random(32);
i2 := random(32);
// Wymieniamy Tk[i1] <--> Tk[i2]
x := Tk[i1];
Tk[i1] := Tk[i2];
Tk[i2] := x;
end;
// Na podstawie tablicy
// tworzymy drzewo AVL
for i := 0 to 31 do
begin
write(Tk[i], ' ');
avl_insert(root, Tk [i]);
end;
writeln; writeln;
// Wyświetlamy zawartość drzewa AVL
print_bt('', '', root);
writeln; writeln;
// Ponownie mieszamy tablicę
for i := 1 to 300 do
begin
i1 := random(32);
i2 := random(32);
x := Tk[i1];
Tk[i1] := Tk[i2];
Tk[i2] := x;
end;
// Usuwamy 15 węzłów
for i := 0 to 14 do
begin
write(Tk[i], ' ');
avl_remove(root, avl_find(root, Tk[i]));
end;
writeln; writeln;
// Wyświetlamy zawartość drzewa AVL
print_bt('', '', root);
// Usuwamy drzewo AVL z pamięci
dfs_release(root);
end.
|
// Drzewo AVL
// Data: 22.05.2013
// (C)2013 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
// Definicja typu węzłów drzewa AVL
//---------------------------------
struct AVLnode
{
AVLnode * up, *left, *right;
int key, bf;
};
// Zmienne globalne
//-----------------
// łańcuchy do znaków ramek
string cr, cl, cp;
// Procedura wypisuje drzewo
//--------------------------
void print_bt(string sp,
string sn,
AVLnode * v)
{
string s;
if(v)
{
s = sp;
if(sn == cr)
s[s.length()-2] = ' ';
print_bt (s+cp, cr, v->right);
s = s.substr(0, sp.length()-2);
cout << s << sn << v->key
<< ":" << v->bf << endl;
s = sp;
if(sn == cl)
s[s.length()-2] = ' ';
print_bt(s+cp, cl, v->left);
}
}
// Procedura DFS:postorder
// usuwająca drzewo
//------------------------
void dfs_release(AVLnode * v)
{
if(v)
{
// usuwamy lewe poddrzewo
dfs_release(v->left);
// usuwamy prawe poddrzewo
dfs_release(v->right);
// usuwamy sam węzeł
delete v;
}
}
// Rotacja RR
//-----------
void rot_rr(AVLnode * & root,
AVLnode * A)
{
AVLnode * B = A->right,
* p = A->up;
A->right = B->left;
if(A->right)
A->right->up = A;
B->left = A;
B->up = p;
A->up = B;
if(p)
{
if(p->left == A)
p->left = B;
else
p->right = B;
}
else root = B;
if(B->bf == -1)
A->bf = B->bf = 0;
else
{
A->bf = -1;
B->bf = 1;
}
}
// Rotacja LL
//-----------
void rot_ll(AVLnode * & root,
AVLnode * A)
{
AVLnode * B = A->left,
* p = A->up;
A->left = B->right;
if(A->left)
A->left->up = A;
B->right = A;
B->up = p;
A->up = B;
if(p)
{
if(p->left == A)
p->left = B;
else
p->right = B;
}
else root = B;
if(B->bf == 1)
A->bf = B->bf = 0;
else
{
A->bf = 1;
B->bf = -1;
}
}
// Rotacja RL
//-----------
void rot_rl(AVLnode * & root,
AVLnode * A)
{
AVLnode * B = A->right,
* C = B->left,
* p = A->up;
B->left = C->right;
if(B->left)
B->left->up = B;
A->right = C->left;
if(A->right)
A->right->up = A;
C->left = A;
C->right = B;
A->up = B->up = C;
C->up = p;
if(p)
{
if(p->left == A)
p->left = C;
else
p->right = C;
}
else root = C;
if(C->bf == -1)
A->bf = 1;
else
A->bf = 0;
if(C->bf == 1)
B->bf = -1;
else
B->bf = 0;
C->bf = 0;
}
// Rotacja LR
//-----------
void rot_lr(AVLnode * & root,
AVLnode * A)
{
AVLnode * B = A->left,
* C = B->right,
* p = A->up;
B->right = C->left;
if(B->right)
B->right->up = B;
A->left = C->right;
if(A->left)
A->left->up = A;
C->right = A;
C->left = B;
A->up = B->up = C;
C->up = p;
if(p)
{
if(p->left == A)
p->left = C;
else
p->right = C;
}
else root = C;
if(C->bf == 1)
A->bf = -1;
else
A->bf = 0;
if(C->bf == -1)
B->bf = 1;
else
B->bf = 0;
C->bf = 0;
}
// Wstawia nowy węzeł do drzewa AVL
// root - referencja korzenia
// k - klucz nowego węzła
//---------------------------------
void avl_insert(AVLnode * & root,
int k)
{
AVLnode * w, * p, * r;
bool t;
// tworzymy dynamicznie nowy węzeł
w = new AVLnode;
w->left = w->right = w->up = NULL;
w->key = k;
w->bf = 0;
//----------------------------------------
// FAZA 1 - wstawienie węzła do drzewa AVL
//----------------------------------------
// rozpoczynamy od korzenia
p = root;
// jeśli drzewo jest puste,
// to węzeł w umieszczamy w korzeniu
if(!p) root = w;
else
{
// inaczej szukamy miejsce dla w
while(true)
// porównujemy klucze
if(k < p->key)
{
// jeśli p nie posiada
// lewego syna, to
if(!p->left)
{
// lewym synem p
// staje się węzeł w
p->left = w;
break; // wychodzimy z pętli
}
// inaczej przechodzimy
// do lewego syna
p = p->left;
}
else
{
// jeśli p nie posiada
// prawego syna, to
if(!p->right)
{
// prawym synem
// staje się węzeł w
p->right = w;
break; // wychodzimy z pętli
}
// inaczej przechodzimy
// do prawego syna
p = p->right;
}
w->up = p; // ojcem w jest p
//---------------------------------
// FAZA 2 - równoważenie drzewa AVL
//---------------------------------
if(p->bf)
p->bf = 0; // UWAGA NR 1
else
{
if(p->left == w) // UWAGA NR 2
p->bf = 1;
else
p->bf = -1;
// będziemy szli w górę drzewa
// w kierunku korzenia
// r i p wskazują ojca
// i syna na tej ścieżce
r = p->up;
t = false;
while(r)
{
if(r->bf)
{
// ustalamy wynik pętli
t = true;
break; // przerywamy pętlę
};
// inaczej modyfikujemy r.bf
if(r->left == p)
r->bf = 1;
else
r->bf = -1;
// przechodzimy w górę
// na wyższy poziom
p = r;
r = r->up;
}
// jeśli r.bf = +/- 1,
// to musimy równoważyć drzewo
if(t)
{
if(r->bf == 1)
{
// gałęzie się równoważą
if(r->right == p)
r->bf = 0;
else if(p->bf == -1)
rot_lr(root, r);
else
rot_ll(root, r);
}
else
{ // r.bf = -1
// gałęzie się równoważą
if(r->left == p)
r->bf = 0;
else if(p->bf == 1)
rot_rl(root, r);
else
RR (root, r);
}
}
}
}
}
// Funkcja znajduje
// poprzednik węzła p
//-------------------
AVLnode * avl_pred(AVLnode * p)
{
AVLnode * r;
if(p)
{
if(p->left)
{
p = p->left;
while(p->right)
p = p->right;
}
else
do
{
r = p;
p = p->up;
} while(p && p->right != r);
}
return p;
}
// Funkcja szuka w drzewie AVL
// węzła o zadanym kluczu.
// Jeśli go znajdzie, zwraca
// jego wskazanie. Jeżeli nie,
// to zwraca wskazanie puste.
// Parametrami są:
// p - wskazanie korzenia drzewa AVL
// k - klucz poszukiwanego węzła
//----------------------------------
AVLnode * avl_find(AVLnode * p,
int k)
{
while(p && p->key != k)
p = (k < p->key) ? p->left:
p->right;
return p;
}
// Funkcja usuwa rekurencyjnie
// węzeł x
// root - referencja do zmiennej
// z adresem korzenia
// x - wskazanie usuwanego węzła
//---------------------------------
AVLnode * avl_remove(AVLnode * & root,
AVLnode * x)
{
AVLnode * t, * y, * z;
bool nest;
if(x->left && x->right)
{
y = avl_remove(root, avl_pred(x));
nest = false;
}
else
{
if(x->left)
{
y = x->left;
x->left = NULL;
}
else
{
y = x->right;
x->right = NULL;
}
x->bf = 0;
nest = true;
}
if(y)
{
y->up = x->up;
y->left = x->left;
if(y->left)
y->left->up = y;
y->right = x->right;
if(y->right)
y->right->up = y;
y->bf = x->bf;
}
if(x->up)
{
if(x->up->left == x)
x->up->left = y;
else
x->up->right = y;
}
else root = y;
if(nest)
{
z = y;
y = x->up;
while(y)
{
if(!y->bf)
{ // Przypadek nr 1
if(y->left == z)
y->bf = -1;
else
y->bf = 1;
break;
}
else
{
if(((y->bf == 1) && (y->left == z))||
((y->bf == -1) && (y->right == z)))
{ // Przypadek nr 2
y->bf = 0;
z = y;
y = y->up;
}
else
{
if(y->left == z)
t = y->right;
else t = y->left;
if(!t->bf)
{ // Przypadek 3A
if(y->bf == 1)
rot_ll(root, y);
else
rot_rr(root, y);
break;
}
else if(y->bf == t->bf)
{ // Przypadek 3B
if(y->bf == 1)
rot_ll(root, y);
else
rot_rr(root, y);
z = t;
y = t->up;
}
else
{ // Przypadek 3C
if(y->bf == 1)
rot_lr(root, y);
else
rot_rl(root, y);
z = y->up;
y = z->up;
}
}
}
}
}
return x;
}
//**********************
//*** PROGRAM GŁÓWNY ***
//**********************
int main()
{
// tablica kluczy węzłów
int Tk[32];
int i, i1, i2;
AVLnode * root = NULL;
// ustawiamy łańcuchy znakowe,
// ponieważ nie wszystkie
// edytory pozwalają wstawiać
// znaki konsoli do tworzenia
// ramek.
// cr = +--
// |
// cl = |
// +--
// cp = |
// |
cr = cl = cp = " ";
cr[0] = 218; cr[1] = 196;
cl[0] = 192; cl[1] = 196;
cp[0] = 179;
srand(time(NULL));
// Tablicę wypełniamy wartościami kluczy
for(i = 0; i < 32; i++)
Tk[i] = i+1;
// Mieszamy tablicę
for(i = 0; i < 300; i++)
{
// Losujemy 2 indeksy
i1 = rand()%32;
i2 = rand()%32;
// Wymieniamy Tk[i1] <--> Tk[i2]
swap(Tk[i1], Tk[i2]);
}
// Na podstawie tablicy
// tworzymy drzewo AVL
for(i = 0; i < 32; i++)
{
cout << Tk[i] << " ";
avl_insert(root, Tk[i]);
}
cout << endl << endl;
// Wyświetlamy zawartość drzewa AVL
print_bt("", "", root);
cout << endl << endl;
// Ponownie mieszamy tablicę
for(i = 0; i < 300; i++)
{
i1 = rand()%32;
i2 = rand()%32;
swap(Tk[i1], Tk[i2]);
}
// Usuwamy 15 węzłów
for(i = 0; i < 15; i++)
{
cout << Tk[i] << " ";
avl_remove(root, avl_find(root, Tk[i]));
}
cout << endl << endl;
// Wyświetlamy zawartość drzewa AVL
print_bt("", "", root);
// Usuwamy drzewo AVL z pamięci
dfs_release(root);
return 0;}
|
Basic' Drzewo AVL
' Data: 22.05.2013
' (C)2013 mgr Jerzy Wałaszek
'---------------------------
' Definicja typu węzłów drzewa AVL
'---------------------------------
Type AVLnode
up As AVLnode Ptr
Left As AVLnode Ptr
Right As AVLnode Ptr
key As Integer
bf As Integer
End Type
' Zmienne globalne
'-----------------
' łańcuchy do ramek
Dim Shared As String * 2 cr, _
cl, _
cp
' Procedura wypisuje drzewo
'--------------------------
Sub print_bt(sp As String, _
sn As String, _
v As AVLnode Ptr)
Dim As String s
If v Then
s = sp
If sn = cr Then _
Mid(s, Len(s)-1, 1) = " "
print_bt(s+cp, cr, v->Right)
s = Mid(s, 1, Len(sp)-2)
Print Using "&&&:&";s;sn;v->key;v->bf
s = sp
If sn = cl Then _
Mid(s, Len(s)-1, 1) = " "
print_bt(s+cp, cl, v->Left)
End If
End Sub
' Procedura DFS:postorder
' usuwająca drzewo
'------------------------
Sub dfs_release(v As AVLnode Ptr)
If v Then
' usuwamy lewe poddrzewo
dfs_release(v->left)
' usuwamy prawe poddrzewo
dfs_release (v->right)
' usuwamy sam węzeł
delete v
End If
End Sub
' Rotacja RR
'-----------
Sub rot_rr(ByRef root As AVLnode Ptr, _
ByVal A As AVLnode Ptr)
Dim As AVLnode Ptr B = A->right, _
p = A->up
A->right = B->Left
If A->Right Then _
A->right->up = A
B->left = A
B->up = p
A->up = B
If p Then
if p->left = A Then
p->left = B
Else
p->right = B
End If
Else
root = B
End If
If B->bf = -1 Then
A->bf = 0
B->bf = 0
Else
A->bf = -1
B->bf = 1
End If
End Sub
' Rotacja LL
'-----------
Sub rot_ll(ByRef root As avlNode Ptr, _
ByVal A As AVLnode Ptr)
Dim As AVLnode Ptr B = A->left, _
p = A->up
A->left = B->Right
If A->left Then _
A->left->up = A
B->right = A
B->up = p
A->up = B
If p Then
If p->left = A Then
p->left = B
Else
p->right = B
End If
Else
root = B
End If
if B->bf = 1 Then
A->bf = 0
B->bf = 0
Else
A->bf = 1
B->bf = -1
End If
End Sub
' Rotacja RL
'-----------
Sub rot_rl(ByRef root As AVLnode Ptr, _
ByVal A As AVLnode Ptr)
Dim As AVLnode Ptr B = A->right, _
C = B->left, _
p = A->up
B->left = C->Right
if B->left Then _
B->left->up = B
A->right = C->Left
If A->Right Then _
A->right->up = A
C->left = A
C->right = B
A->up = C
B->up = C
C->up = p
If p Then
if p->left = A Then
p->left = C
Else
p->right = C
End If
Else
root = C
End If
If C->bf = -1 Then
A->bf = 1
Else
A->bf = 0
End If
If C->bf = 1 Then
B->bf = -1
Else
B->bf = 0
End If
C->bf = 0
End Sub
' Rotacja LR
'-----------
sub rot_lr(ByRef root As AVLnode Ptr, _
ByVal A As AVLnode Ptr)
Dim As AVLnode Ptr B = A->left, _
C = B->right, _
p = A->up
B->right = C->Left
If B->right Then _
B->right->up = B
A->left = C->Right
if A->left Then _
A->left->up = A
C->right = A
C->left = B
A->up = C
B->up = C
C->up = p
if p Then
If p->left = A Then
p->left = C
Else
p->right = C
End If
Else
root = C
End If
If C->bf = 1 Then
A->bf = -1
Else
A->bf = 0
End If
If C->bf = -1 Then
B->bf = 1
Else
B->bf = 0
End If
C->bf = 0
End Sub
' Wstawia nowy węzeł do drzewa AVL
' root - referencja korzenia
' k - klucz nowego węzła
'---------------------------------
Sub avl_insert(ByRef root As AVLnode Ptr, _
k As Integer)
Dim As AVLnode Ptr w, p, r
Dim As Integer t
' tworzymy dynamicznie nowy węzeł
w = new AVLnode
w->left = 0
w->right = 0
w->up = 0
w->key = k
w->bf = 0
'----------------------------------------
' FAZA 1 - wstawienie węzła do drzewa AVL
'----------------------------------------
' rozpoczynamy od korzenia
p = root
If p = 0 Then
' jeśli drzewo jest puste,
' to węzeł w umieszczamy w korzeniu
root = w
Else
' inaczej szukamy miejsce dla w
While 1
' porównujemy klucze
If k < p->key Then
' jeśli p nie posiada
' lewego syna, to
If p->Left = 0 Then
' lewym synem p
' staje się węzeł w
p->left = w
Exit While ' wychodzimy z pętli
End If
' inaczej przechodzimy
' do lewego syna
p = p->Left
Else
' jeśli p nie posiada
' prawego syna, to
If p->Right = 0 Then
' prawym synem
' staje się węzeł w
p->right = w
Exit While ' wychodzimy z pętli
End If
' inaczej przechodzimy
' do prawego syna
p = p->Right
End If
Wend
w->up = p ' ojcem w jest p
'---------------------------------
' FAZA 2 - równoważenie drzewa AVL
'---------------------------------
If p->bf Then
p->bf = 0 ' UWAGA NR 1
Else
' UWAGA NR 2
If p->left = w Then
p->bf = 1
Else
p->bf = -1
End If
' będziemy szli w górę drzewa
' w kierunku korzenia
' r i p wskazują ojca
' i syna na tej ścieżce
r = p->up
t = 0
While r
If r->bf Then
' ustalamy wynik pętli
t = 1
Exit While ' przerywamy pętlę
End If
' inaczej modyfikujemy r.bf
If r->left = p Then
r->bf = 1
Else
r->bf = -1
End If
' przechodzimy w górę
' na wyższy poziom
p = r
r = r->up
Wend
' jeśli r.bf = +/- 1,
' to musimy równoważyć drzewo
If t = 1 Then
If r->bf = 1 Then
If r->right = p Then
' gałęzie się równoważą
r->bf = 0
ElseIf p->bf = -1 Then
rot_lr(root, r)
Else
rot_ll(root, r)
End If
Else ' r.bf = -1
If r->left = p Then
' gałęzie się równoważą
r->bf = 0
ElseIf p->bf = 1 Then
rot_rl(root, r)
Else
rot_rr(root, r)
End If
End If
End If
End If
End If
End Sub
' Funkcja znajduje poprzednik węzła p
'------------------------------------
Function avl_pred(ByVal p As AVLnode Ptr) _
As AVLnode Ptr
Dim As AVLnode Ptr r
If p Then
If p->Left Then
p = p->Left
While p->Right
p = p->Right
Wend
Else
Do
r = p
p = p->up
Loop Until p = 0 OrElse _
p->Right = r
End If
End If
Return p
End Function
' Funkcja szuka w drzewie AVL
' węzła o zadanym kluczu.
' Jeśli go znajdzie, zwraca
' jego wskazanie. Jeżeli nie,
' to zwraca wskazanie puste.
' Parametrami są:
' p - wskazanie korzenia drzewa AVL
' k - klucz poszukiwanego węzła
'----------------------------------
Function avl_find(p As AVLnode Ptr, _
k As Integer) _
As AVLnode Ptr
while(p <> 0) AndAlso _
(p->key <> k)
If k < p->key Then
p = p->Left
Else
p = p->Right
End If
Wend
Return p
End Function
' Funkcja usuwa rekurencyjnie węzeł x
' root - referencja do zmiennej
' z adresem korzenia
' x - wskazanie usuwanego węzła
'------------------------------------
Function avl_remove(ByRef root As AVLnode Ptr, _
x As AVLnode Ptr) _
As AVLnode Ptr
Dim As AVLnode Ptr t, y, z
Dim As Integer nest
if(x->Left <> 0) And _
(x->Right <> 0) Then
y = avl_remove(root, avl_pred(x))
nest = 0
Else
If x->Left Then
y = x->left
x->left = 0
Else
y = x->right
x->right = 0
End If
x->bf = 0
nest = 1
End If
If y Then
y->up = x->up
y->left = x->left
If y->Left Then _
y->left->up = y
y->right = x->right
If y->Right Then _
y->right->up = y
y->bf = x->bf
End If
If x->up Then
If x->up->left = x Then
x->up->left = y
Else
x->up->right = y
End If
Else
root = y
End If
If nest = 1 Then
z = y
y = x->up
While y
If y->bf = 0 Then ' Przypadek nr 1
If y->left = z Then
y->bf = -1
Else
y->bf = 1
End If
Exit While
Else
' Przypadek nr 2
if((y->bf = 1) AndAlso (y->left = z)) OrElse _
((y->bf = -1) AndAlso (y->right = z)) Then
y->bf = 0
z = y: y = y->up
Else
If y->left = z Then
t = y->right
Else
t = y->Left
End If
If t->bf = 0 then ' Przypadek 3A
If y->bf = 1 Then
rot_ll(root, y)
Else
rot_rr(root, y)
End If
Exit While
ElseIf y->bf = t->bf Then ' Przypadek 3B
If y->bf = 1 Then
rot_ll(root, y)
Else
rot_rr(root, y)
End If
z = t
y = t->up
Else ' Przypadek 3C
If y->bf = 1 Then
rot_lr(root, y)
Else
rot_rl(root, y)
End If
z = y->up
y = z->up
End If
End If
End If
Wend
End If
Return x
End Function
' **********************
' *** PROGRAM GŁÓWNY ***
' **********************
' tablica kluczy węzłów
Dim As Integer Tk(31)
Dim As Integer i, i1, i2
Dim As AVLnode Ptr root = 0
' ustawiamy łańcuchy znakowe,
' ponieważ nie wszystkie edytory
' pozwalają wstawiać znaki konsoli
' do tworzenia ramek.
' cr = +--
' |
' cl = |
' +--
' cp = |
' |
cr = Chr(218)+Chr(196)
cl = Chr(192)+Chr(196)
cp = Chr(179)+" "
Randomize
' Tablicę wypełniamy wartościami kluczy
For i = 0 To 31
Tk(i) = i+1
Next
' Mieszamy tablicę
For i = 1 To 300
' Losujemy 2 indeksy
i1 = Int(Rnd()*32)
i2 = Int(Rnd()*32)
' Wymieniamy Tk[i1] <--> Tk[i2]
Swap Tk(i1), Tk(i2)
Next
' Na podstawie tablicy tworzymy drzewo AVL
For i = 0 To 31
Print Tk(i);
avl_insert(root, Tk(i))
Next
Print: Print
' Wyświetlamy zawartość drzewa AVL
print_bt("", "", root)
Print: Print
' Ponownie mieszamy tablicę
For i = 1 To 300
i1 = Int(Rnd()*32)
i2 = Int(Rnd()*32)
Swap Tk(i1), Tk(i2)
Next
' Usuwamy 15 węzłów
For i = 0 To 14
Print Tk(i);
avl_remove(root, avl_find(root, Tk(i)))
Next
Print: Print
' Wyświetlamy zawartość drzewa AVL
print_bt("", "", root)
' Usuwamy drzewo BST z pamięci
dfs_release(root)
End
|
| Python (dodatek) |
# Drzewo AVL
# Data: 29.07.2024
# (C)2024 mgr Jerzy Wałaszek
#---------------------------
import random
# klasa węzłów drzewa
class AVLnode:
def __init__(self):
self.up = None
self.left = None
self.right = None
self.key = 0
self.bf = 0
# zmienne globalne
# wskazanie korzenia drzewa
root = None
# łańcuchy do ramek
cr = "┌─"
cl = "└─"
cp = "│ "
# procedura wypisuje drzewo
def print_bt(sp, sn, v):
global cr, cl, cp
s = ""
if v:
s = sp
if sn == cr:
s = s[:-2]+' '+s[-1]
print_bt(s+cp, cr, v.right)
s = s[:-2]
print(s, sn, v.key, ":", v.bf, sep="")
s = sp
if sn == cl:
s = s[:-2]+' '+s[-1]
print_bt(s+cp, cl, v.left)
# Procedura DFS:postorder
# usuwająca drzewo
def dfs_release(v):
if v:
# usuwamy lewe poddrzewo
dfs_release(v.left)
# usuwamy prawe poddrzewo
dfs_release(v.right)
# usuwamy odwołania
v.left = None
v.right = None
# Rotacja RR
def rot_rr(a):
global root
b = a.right
p = a.up
a.right = b.left
if a.right:
a.right.up = a
b.left = a
b.up = p
a.up = b
if p:
if p.left is a:
p.left = b
else:
p.right = b
else:
root = b
if b.bf == -1:
a.bf, b.bf = 0, 0
else:
a.bf, b.bf = -1, 1
# Rotacja LL
def rot_ll(a):
global root
b = a.left
p = a.up
a.left = b.right
if a.left:
a.left.up = a
b.right = a
b.up = p
a.up = b
if p:
if p.left is a:
p.left = b
else:
p.right = b
else:
root = b
if b.bf == 1:
a.bf, b.bf = 0, 0
else:
a.bf, b.bf = 1, -1
# Rotacja RL
def rot_rl(a):
global root
b = a.right
c = b.left
p = a.up
b.left = c.right
if b.left:
b.left.up = b
a.right = c.left
if a.right:
a.right.up = a
c.left = a
c.right = b
a.up = c
b.up = c
c.up = p
if p:
if p.left is a:
p.left = c
else:
p.right = c
else:
root = c
if c.bf == -1:
a.bf = 1
else:
a.bf = 0
if c.bf == 1:
b.bf = -1
else:
b.bf = 0
c.bf = 0
# Rotacja LR
def rot_lr(a):
global root
b = a.left
c = b.right
p = a.up
b.right = c.left
if b.right:
b.right.up = b
a.left = c.right
if a.left:
a.left.up = a
c.right = a
c.left = b
a.up = c
b.up = c
c.up = p
if p:
if p.left is a:
p.left = c
else:
p.right = c
else:
root = c
if c.bf == 1:
a.bf = -1
else:
a.bf = 0
if c.bf == -1:
b.bf = 1
else:
b.bf = 0
c.bf = 0
# Wstawia nowy węzeł do drzewa AVL
# root - referencja korzenia
# k - klucz nowego węzła
def avl_insert(k):
global root
# tworzymy dynamicznie nowy węzeł
w = AVLnode()
w.key = k
# FAZA 1 - wstawienie węzła do drzewa AVL
# rozpoczynamy od korzenia
p = root
if not p:
# jeśli drzewo jest puste,
# to węzeł w umieszczamy w korzeniu
root = w
else:
# inaczej szukamy miejsce dla w
while True:
# porównujemy klucze
if k < p.key:
# jeśli p nie posiada
# lewego syna, to
if not p.left:
# lewym synem p
# staje się węzeł w
p.left = w
break # wychodzimy z pętli
# inaczej przechodzimy
# do lewego syna
p = p.left
else:
# jeśli p nie posiada
# prawego syna, to
if not p.right:
# prawym synem
# staje się węzeł w
p.right = w
break # wychodzimy z pętli
# inaczej przechodzimy
# do prawego syna
p = p.right
w.up = p # ojcem w jest p
# FAZA 2 - równoważenie drzewa AVL
if p.bf:
p.bf = 0 # UWAGA NR 1
else:
# UWAGA NR 2
if p.left is w:
p.bf = 1
else:
p.bf = -1
# będziemy szli w górę drzewa
# w kierunku korzenia
# r i p wskazują ojca
# i syna na tej ścieżce
r = p.up
t = False
while r:
if r.bf:
# ustalamy wynik pętli
t = True
break # przerywamy pętlę
# inaczej modyfikujemy r.bf
if r.left is p:
r.bf = 1
else:
r.bf = -1
# przechodzimy w górę
# na wyższy poziom
p = r
r = r.up
# jeśli r.bf = +/- 1,
# to musimy równoważyć drzewo
if t:
if r.bf == 1:
if r.right is p:
# gałęzie się równoważą
r.bf = 0
elif p.bf == -1:
rot_lr(r)
else:
rot_ll(r)
else: # r.bf = -1
if r.left is p:
# gałęzie się równoważą
r.bf = 0
elif p.bf == 1:
rot_rl(r)
else:
rot_rr(r)
# Funkcja znajduje poprzednik węzła p
def avl_pred(p):
if p:
if p.left:
p = p.left
while p.right:
p = p.right
else:
while True:
r = p
p = p.up
if not p or p.right is r:
break
return p
# Funkcja szuka w drzewie AVL
# węzła o zadanym kluczu.
# Jeśli go znajdzie, zwraca
# jego wskazanie. Jeżeli nie,
# to zwraca wskazanie puste.
# Parametrami są:
# k - klucz poszukiwanego węzła
def avl_find(k):
global root
p = root
while p and (p.key != k):
if k < p.key:
p = p.left
else:
p = p.right
return p
# Funkcja usuwa rekurencyjnie węzeł x
# x - wskazanie usuwanego węzła
def avl_remove(x):
global root
if bool(x.left) and bool(x.right):
y = avl_remove(avl_pred(x))
nest = False
else:
if x.left:
y = x.left
x.left = None
else:
y = x.right
x.right = None
x.bf = 0
nest = True
if y:
y.up = x.up
y.left = x.left
if y.left:
y.left.up = y
y.right = x.right
if y.right:
y.right.up = y
y.bf = x.bf
if x.up:
if x.up.left is x:
x.up.left = y
else:
x.up.right = y
else:
root = y
if nest:
z = y
y = x.up
while y:
if not y.bf: # Przypadek nr 1
if y.left is z:
y.bf = -1
else:
y.bf = 1
break
else:
# Przypadek nr 2
if((y.bf == 1) and (y.left is z)) or \
((y.bf == -1) and (y.right is z)):
y.bf = 0
z = y
y = y.up
else:
if y.left is z:
t = y.right
else:
t = y.left
if not t.bf: # Przypadek 3A
if y.bf == 1:
rot_ll(y)
else:
rot_rr(y)
break
elif y.bf == t.bf: # Przypadek 3B
if y.bf == 1:
rot_ll(y)
else:
rot_rr(y)
z = t
y = t.up
else: # Przypadek 3C
if y.bf == 1:
rot_lr(y)
else:
rot_rl(y)
z = y.up
y = z.up
return x
# **********************
# *** PROGRAM GŁÓWNY ***
# **********************
# Tablicę wypełniamy wartościami kluczy
tk = [i+1 for i in range(32)]
# Mieszamy tablicę
for i in range(300):
# Losujemy 2 indeksy
i1 = random.randrange(32)
i2 = random.randrange(32)
# Wymieniamy tk[i1] <--> tk[i2]
tk[i1], tk[i2] = tk[i2], tk[i1]
# Na podstawie tablicy tworzymy drzewo AVL
for i in range(32):
print(tk[i], end=" ")
avl_insert(tk[i])
print()
print()
# Wyświetlamy zawartość drzewa AVL
print_bt("", "", root)
print()
print()
# Ponownie ieszamy tablicę
for i in range(300):
# Losujemy 2 indeksy
i1 = random.randrange(32)
i2 = random.randrange(32)
tk[i1], tk[i2] = tk[i2], tk[i1]
# Usuwamy 15 węzłów
for i in range(15):
print(tk[i], end=" ")
avl_remove(avl_find(tk[i]))
print()
print()
# Wyświetlamy zawartość drzewa AVL
print_bt("", "", root)
# Usuwamy drzewo BST z pamięci
dfs_release(root)
root = None
|
| Wynik: |
25 23 17 21 4 15 3 10 29 12 14 9 7 20 18 28 11 16 6 24 5 1 2 26 22 19 30 27 8 13 31 32
┌─32:0
┌─31:-1
┌─30:-1
│ └─29:0
┌─28:0
│ │ ┌─27:0
│ │ ┌─26:-1
│ └─25:-1
│ └─24:0
┌─23:-1
│ │ ┌─22:0
│ │ ┌─21:-1
│ └─20:0
│ │ ┌─19:0
│ └─18:-1
17:0
│ ┌─16:0
│ ┌─15:1
│ │ └─14:1
│ │ └─13:0
│ ┌─12:-1
│ │ │ ┌─11:0
│ │ └─10:-1
└─9:0
│ ┌─8:0
│ ┌─7:-1
│ ┌─6:-1
│ │ └─5:0
└─4:-1
│ ┌─3:0
└─2:0
└─1:0
21 32 23 10 16 12 17 24 7 14 31 5 19 20 29
┌─30:0
┌─28:1
│ │ ┌─27:0
│ └─26:-1
┌─25:-1
│ │ ┌─22:0
│ └─18:-1
15:0
│ ┌─13:1
│ │ └─11:0
│ ┌─9:-1
│ │ └─8:0
└─6:0
│ ┌─4:1
│ │ └─3:0
└─2:-1
└─1:0
|
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2026 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.