AVL дървета: ротации, вмъкване, изтриване с C++ Пример
⚡ Умно обобщение
AVL дърветата са самобалансиращи се двоични дървета за търсене, където разликата във височината между лявото и дясното поддърво на всеки възел остава в рамките на -1, 0 или +1, което гарантира O(log n) производителност при търсене.

Какво представляват AVL дърветата?
AVL дървета са двоични дървета за търсене, в които разликата във височината между лявото и дясното поддърво на всеки възел е -1, 0 или +1. Те са самобалансиращи се BST, които поддържат логаритмично време за търсене, кръстени на изобретателите Аделсън-Велски и Ландис (AVL).
Как работи AVL Tree?
За да разберете защо съществуват AVL дървета, вижте какво се обърква с обикновена Двоично дърво за търсенеДа разгледаме тези ключове, поставени в дадения ред:
AVL дървовидна визуализация
Дървото расте линейно, когато ключовете пристигат във възходящ ред, дегенерирайки търсенето до O(n). Това обезсмисля целта на BST - само балансирано дърво поддържа търсенето логаритмично. Сега разгледайте същите ключове, вмъкнати в различен ред.
Същите ключове, различен ред на вмъкване, водят до по-плитка форма, така че всяко търсене се изпълнява за O(log n). AVL дърветата налагат тази форма, като наблюдават височината при всяко вмъкване и коригират дисбаланса, без да нарушават BST подредбата.
Фактор на баланс в AVL дървета
Факторът на баланс (BF) tracks височината на всеки възел, така че дървото да може да се самобалансира в движение.
Свойства на фактора на баланса
Коефициент на баланс AVL дърво
- Коефициентът на баланс е разликата между височината на лявото поддърво и височината на дясното поддърво.
Balance factor(node) = height(node->left) − height(node->right)- Единствените разрешени стойности са −1, 0 и +1.
- Стойност -1 означава, че дясното поддърво съдържа едно допълнително ниво - възелът е с претеглено дясно ниво.
- Стойност +1 означава, че лявото поддърво съдържа едно допълнително ниво - възелът е с претеглено ляво разположение.
- Стойност 0 означава, че двете страни имат еднаква височина - възелът е перфектно балансиран.
AVL ротации
Ротациите се изпълняват винаги, когато вмъкване или изтриване наруши правилото за балансиран фактор. Четирите случая са LL, RR, LR и RL.
Ляво – ляво завъртане
Това завъртане се извършва, когато се вмъкне нов възел в лявото дете на лявото поддърво.
AVL Tree Left – Ляво завъртане
Извършва се еднократно завъртане надясно. Този случай се задейства, когато възелът има BF +2, а лявото му дете има BF +1.
Надясно – завъртане надясно
Това завъртане се извършва, когато се вмъкне нов възел в дясното дете на дясното поддърво.
Извършва се еднократно завъртане наляво. Този случай се задейства, когато възелът има BF −2, а десният му наследник има BF −1.
Завъртане надясно – наляво
Това завъртане се извършва, когато се вмъкне нов възел в лявото дете на дясното поддърво.
Задейства се, когато BF(възел) = −2 и BF(десен-дете) = +1. Завъртете надясно десния дете, след това завъртете наляво възела.
Завъртане наляво – надясно
Това завъртане се извършва, когато се вмъкне нов възел в дясното дете на лявото поддърво.
Задейства се, когато BF(възел) = +2 и BF(ляво-дете) = −1. Завъртете лявото дете, след това завъртете надясно възела.
Вмъкване в AVL дървета
Вмъкването е почти идентично с обикновеното вмъкване с BST. След всяко вмъкване, дървото се изкачва нагоре и балансира отново. Вмъкването се изпълнява за време от O(log n) в най-лошия случай.
Реализация на вмъкване на AVL дърво
Стъпка 1: Вмъкнете възела, използвайки стандартния BST алгоритъм. В горния пример, вмъкнете 160.
Стъпка 2: Актуализирайте коефициента на баланс на всеки предшественик по пътя на вмъкване.
Стъпка 3: Ако някой предшественик наруши диапазона на фактора на баланс, извършете съответстващата ротация. В примера, факторът на баланс на възел 350 е нарушен, така че LL ротацията възстановява баланса.
- If
BF(node) = +2намляваBF(left-child) = +1, извършете LL въртене. - If
BF(node) = −2намляваBF(right-child) = −1, извършете RR въртене. - If
BF(node) = −2намляваBF(right-child) = +1, извършете RL въртене. - If
BF(node) = +2намляваBF(left-child) = −1, извършете LR въртене.
Изтриване в AVL дървета
Изтриването следва същата логика като обикновен BST и след това се ребалансира.
Стъпка 1: Намерете елемента в дървото.
Стъпка 2: Изтрийте възела, използвайки стандартното BST изтриване.
Стъпка 3: Възможни са два случая.
Дело 1: Изтриване от дясното поддърво.
- 1A. If
BF(node) = +2намляваBF(left-child) = +1, извършете LL въртене. - 1B. If
BF(node) = +2намляваBF(left-child) = −1, извършете LR въртене. - 1C. If
BF(node) = +2намляваBF(left-child) = 0, извършете LL въртене.
Дело 2: Изтриване от лявото поддърво.
- 2A. If
BF(node) = −2намляваBF(right-child) = −1, извършете RR въртене. - 2B. If
BF(node) = −2намляваBF(right-child) = +1, извършете RL въртене. - 2C. If
BF(node) = −2намляваBF(right-child) = 0, извършете RR въртене.
C++ Пример за AVL дървета
По-долу е показан a C++ програма, имплементираща 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); }
Пример за изпълнение на горния код:
- Копирайте горния код и го запазете във файл с име
avl.cpp. - Компилирайте кода:
g++ avl.cpp -o run
- Изпълнете кода.
./run
Предимства на AVL дървета
- Височината на AVL дървото винаги е балансирана и никога не надвишава log N.
- Търсенето е по-бързо от обикновеното двоично дърво за търсене, защото дървото не може да се изроди.
- Самобалансирането е автоматично — не е необходима стъпка за възстановяване.
- Детерминистичната производителност е подходяща за системи в реално време и индекси в паметта.











