Drzewa AVL: Obroty, Wstawianie, Usuwanie za pomocą C++ Przykład
⚡ Inteligentne podsumowanie
Drzewa AVL to samobalansujące się drzewa wyszukiwań binarnych, w których różnica wysokości między lewym i prawym poddrzewem każdego węzła mieści się w granicach -1, 0 lub +1, gwarantując wydajność wyszukiwania na poziomie O(log n).
Czym są drzewa AVL?
Drzewa AVL Są to binarne drzewa poszukiwań, w których różnica wysokości między lewym a prawym poddrzewem każdego węzła wynosi -1, 0 lub +1. Są to samobalansujące się drzewa BST, które utrzymują logarytmiczny czas wyszukiwania, nazwane na cześć wynalazców Adelson-Velsky i Landis (AVL).
Jak działa drzewo AVL?
Aby zrozumieć, dlaczego istnieją drzewa AVL, przyjrzyjmy się temu, co jest nie tak z prostym drzewem Drzewo wyszukiwania binarnegoRozważ wstawienie tych kluczy w podanej kolejności:
Wizualizacja drzewa AVL
Drzewo rośnie liniowo, gdy klucze pojawiają się w kolejności rosnącej, degenerując wyszukiwanie do O(n). To zaprzecza celowi BST — tylko zrównoważone drzewo utrzymuje logarytmiczne wyszukiwanie. Teraz spójrz na te same klucze wstawione w innej kolejności.
Te same klucze, inna kolejność wstawiania, tworzą płytszy kształt, więc każde wyszukiwanie przebiega w czasie O(log n). Drzewa AVL wymuszają ten kształt, monitorując wysokość przy każdym wstawianiu i korygując nierównowagę bez naruszania kolejności BST.
Współczynnik równowagi w drzewach AVL
Współczynnik równowagi (BF) tracwysokość każdego węzła, dzięki czemu drzewo może samoczynnie utrzymywać równowagę w locie.
Właściwości współczynnika równowagi
Drzewo AVL współczynnika równowagi
- Współczynnik równowagi to różnica między wysokością lewego poddrzewa i wysokością prawego poddrzewa.
Balance factor(node) = height(node->left) − height(node->right)- Jedyne dozwolone wartości to −1, 0 i +1.
- Wartość −1 oznacza, że prawe poddrzewo zawiera jeden dodatkowy poziom — węzeł jest ciężki po prawej stronie.
- Wartość +1 oznacza, że lewe poddrzewo zawiera jeden dodatkowy poziom — węzeł jest położony bardziej po lewej stronie.
- Wartość 0 oznacza, że obie strony mają taką samą wysokość — węzeł jest idealnie zrównoważony.
Rotacje AVL
Rotacje są przeprowadzane za każdym razem, gdy wstawienie lub usunięcie narusza regułę współczynnika równowagi. Cztery przypadki to LL, RR, LR i RL.
W lewo – obrót w lewo
Ten obrót jest wykonywany, gdy nowy węzeł jest wstawiany po lewej stronie potomka lewego poddrzewa.
Drzewo AVL w lewo – obrót w lewo
Wykonywany jest pojedynczy obrót w prawo. Ten przypadek występuje, gdy węzeł ma BF +2, a jego lewy potomek ma BF +1.
Prawo – obrót w prawo
Ten obrót jest wykonywany, gdy nowy węzeł jest wstawiany po prawym dziecku prawego poddrzewa.
Wykonywany jest pojedynczy obrót w lewo. Ten przypadek występuje, gdy węzeł ma BF −2, a jego prawy potomek ma BF −1.
Prawo – obrót w lewo
Ten obrót jest wykonywany, gdy nowy węzeł jest wstawiany w lewym dziecku prawego poddrzewa.
Wywołuje się, gdy BF(węzeł) = −2 i BF(prawe dziecko) = +1. Obróć prawe dziecko w prawo, a następnie obróć węzeł w lewo.
Obrót w lewo – w prawo
Ten obrót jest wykonywany, gdy nowy węzeł jest wstawiany w prawym dziecku lewego poddrzewa.
Wywołuje się, gdy BF(węzeł) = +2 i BF(lewe dziecko) = −1. Obróć lewe dziecko w lewo, a następnie obróć węzeł w prawo.
Wstawienie do drzew AVL
Wstawianie jest niemal identyczne jak wstawianie zwykłego BST. Po każdym wstawieniu drzewo przechodzi w górę i ponownie się równoważy. Wstawienie wykonuje się w najgorszym przypadku w czasie O(log n).
Implementacja wstawiania drzewa AVL
Krok 1: Wstaw węzeł, używając standardowego algorytmu BST. W powyższym przykładzie wstaw 160.
Krok 2: Zaktualizuj współczynnik równowagi każdego przodka na ścieżce wprowadzania.
Krok 3: Jeśli którykolwiek z przodków narusza zakres współczynnika równowagi, wykonaj rotację dopasowującą. W tym przykładzie współczynnik równowagi węzła 350 jest naruszony, więc rotacja LL przywraca równowagę.
- If
BF(node) = +2orazBF(left-child) = +1, wykonaj obrót LL. - If
BF(node) = −2orazBF(right-child) = −1, wykonaj rotację RR. - If
BF(node) = −2orazBF(right-child) = +1, wykonaj obrót RL. - If
BF(node) = +2orazBF(left-child) = −1, wykonaj obrót LR.
Usuwanie w drzewach AVL
Usunięcie odbywa się według tej samej logiki co w przypadku zwykłego BST, a następnie następuje ponowne zrównoważenie.
Krok 1: Znajdź element w drzewie.
Krok 2: Usuń węzeł, korzystając ze standardowej procedury usuwania BST.
Krok 3: Możliwe są dwa przypadki.
Sprawa 1: Usuwanie z prawego poddrzewa.
- 1A. If
BF(node) = +2orazBF(left-child) = +1, wykonaj obrót LL. - 1B. If
BF(node) = +2orazBF(left-child) = −1, wykonaj obrót LR. - 1C. If
BF(node) = +2orazBF(left-child) = 0, wykonaj obrót LL.
Sprawa 2: Usuwanie z lewego poddrzewa.
- 2A. If
BF(node) = −2orazBF(right-child) = −1, wykonaj rotację RR. - 2B. If
BF(node) = −2orazBF(right-child) = +1, wykonaj obrót RL. - 2C. If
BF(node) = −2orazBF(right-child) = 0, wykonaj rotację RR.
C++ Przykład drzew AVL
Poniżej znajduje C++ program implementujący drzewa AVL:
#include <iostream> #include <queue> #include <unordered_map> using namespace std; struct node { struct node *left; int data; int height; struct node *right; }; class AVL { public: struct node *root; AVL() { this->root = NULL; } int calheight(struct node *p) { if (p->left && p->right) { if (p->left->height < p->right->height) return p->right->height + 1; else return p->left->height + 1; } else if (p->left && p->right == NULL) { return p->left->height + 1; } else if (p->left == NULL && p->right) { return p->right->height + 1; } return 0; } int bf(struct node *n) { if (n->left && n->right) return n->left->height - n->right->height; else if (n->left && n->right == NULL) return n->left->height; else if (n->left == NULL && n->right) return -n->right->height; return 0; } struct node *llrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; p->left = tp->right; tp->right = p; return tp; } struct node *rrrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; p->right = tp->left; tp->left = p; return tp; } struct node *rlrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; struct node *tp2 = p->right->left; p->right = tp2->left; tp->left = tp2->right; tp2->left = p; tp2->right = tp; return tp2; } struct node *lrrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; struct node *tp2 = p->left->right; p->left = tp2->right; tp->right = tp2->left; tp2->right = p; tp2->left = tp; return tp2; } struct node *insert(struct node *r, int data) { if (r == NULL) { r = new struct node; r->data = data; r->left = r->right = NULL; r->height = 1; return r; } if (data < r->data) r->left = insert(r->left, data); else r->right = insert(r->right, data); r->height = calheight(r); if (bf(r) == 2 && bf(r->left) == 1) r = llrotation(r); else if (bf(r) == -2 && bf(r->right) == -1) r = rrrotation(r); else if (bf(r) == -2 && bf(r->right) == 1) r = rlrotation(r); else if (bf(r) == 2 && bf(r->left) == -1) r = lrrotation(r); return r; } void levelorder_newline() { if (this->root == NULL) { cout << "\nEmpty tree\n"; return; } levelorder_newline(this->root); } void levelorder_newline(struct node *v) { queue<struct node *> q; struct node *cur; q.push(v); q.push(NULL); while (!q.empty()) { cur = q.front(); q.pop(); if (cur == NULL && q.size() != 0) { cout << "\n"; q.push(NULL); continue; } if (cur != NULL) { cout << " " << cur->data; if (cur->left != NULL) q.push(cur->left); if (cur->right != NULL) q.push(cur->right); } } } struct node *deleteNode(struct node *p, int data) { if (p->left == NULL && p->right == NULL) { if (p == this->root) this->root = NULL; delete p; return NULL; } struct node *q; if (p->data < data) p->right = deleteNode(p->right, data); else if (p->data > data) p->left = deleteNode(p->left, data); else { if (p->left != NULL) { q = inpre(p->left); p->data = q->data; p->left = deleteNode(p->left, q->data); } else { q = insuc(p->right); p->data = q->data; p->right = deleteNode(p->right, q->data); } } if (bf(p) == 2 && bf(p->left) == 1) p = llrotation(p); else if (bf(p) == 2 && bf(p->left) == -1) p = lrrotation(p); else if (bf(p) == 2 && bf(p->left) == 0) p = llrotation(p); else if (bf(p) == -2 && bf(p->right) == -1) p = rrrotation(p); else if (bf(p) == -2 && bf(p->right) == 1) p = rlrotation(p); else if (bf(p) == -2 && bf(p->right) == 0) p = rrrotation(p); return p; } struct node *inpre(struct node *p) { while (p->right != NULL) p = p->right; return p; } struct node *insuc(struct node *p) { while (p->left != NULL) p = p->left; return p; } ~AVL() {} }; int main() { AVL b; int c, x; do { cout << "\n1.Display levelorder on newline"; cout << "\n2.Insert"; cout << "\n3.Delete\n"; cout << "\n0.Exit\n"; cout << "\nChoice: "; cin >> c; switch (c) { case 1: b.levelorder_newline(); break; case 2: cout << "\nEnter no. "; cin >> x; b.root = b.insert(b.root, x); break; case 3: cout << "\nWhat to delete? "; cin >> x; b.root = b.deleteNode(b.root, x); break; case 0: break; } } while (c != 0); }
Przykład działania powyższego kodu:
- Skopiuj powyższy kod i zapisz go w pliku o nazwie
avl.cpp. - Skompiluj kod:
g++ avl.cpp -o run
- Uruchom kod.
./run
Zalety drzew AVL
- Wysokość drzewa AVL jest zawsze zrównoważona i nigdy nie przekracza logarytmu N.
- Przeszukiwanie jest szybsze niż w przypadku zwykłego drzewa poszukiwań binarnych, ponieważ drzewo nie może ulec degeneracji.
- Samobalansowanie odbywa się automatycznie — nie jest wymagana żadna odbudowa.
- Wydajność deterministyczna nadaje się do systemów czasu rzeczywistego i indeksów w pamięci.












