Дерева 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 вліво – обертання вліво
Виконується одне обертання праворуч. Цей випадок спрацьовує, коли вузол має 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
Нижче 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.
- Пошук швидший, ніж звичайне бінарне дерево пошуку, оскільки дерево не може вироджуватися.
- Самобалансування відбувається автоматично — жодного етапу перебудови не потрібно.
- Детермінована продуктивність підходить для систем реального часу та індексів у пам'яті.












