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

Что такое деревья AVL?
AVL Деревья Это бинарные деревья поиска, в которых разница высот между левым и правым поддеревом каждого узла равна -1, 0 или +1. Они представляют собой самобалансирующиеся бинарные деревья поиска, поддерживающие логарифмическое время поиска, названные в честь изобретателей Адельсона-Вельски и Ландиса (AVL).
Как работает дерево AVL?
Чтобы понять, зачем существуют AVL-деревья, давайте посмотрим, что не так с обычным деревом. Двоичное дерево поискаРассмотрим следующие клавиши, вставленные в указанном порядке:
Визуализация дерева AVL
Дерево растет линейно, когда ключи поступают в порядке возрастания, что приводит к вырождению поиска до O(n). Это противоречит цели бинарного дерева поиска — только сбалансированное дерево поддерживает логарифмический поиск. Теперь рассмотрим те же ключи, вставленные в другом порядке.
Одинаковые ключи, но разный порядок вставки приводят к более пологой форме, поэтому каждый поиск выполняется за O(log n). AVL-деревья обеспечивают эту форму, отслеживая высоту при каждой вставке и исправляя дисбаланс без нарушения порядка BST.
Коэффициент баланса в деревьях AVL
Коэффициент баланса (BF) tracзадает высоту каждого узла, чтобы дерево могло автоматически балансироваться в режиме реального времени.
Свойства фактора баланса
Коэффициент баланса AVL-дерева
- Коэффициент баланса — это разница между высотой левого поддерева и высотой правого поддерева.
Balance factor(node) = height(node->left) − height(node->right)- Допустимы только значения −1, 0 и +1.
- Значение −1 означает, что правое поддерево содержит один дополнительный уровень — узел является право-тяжелым.
- Значение +1 означает, что левое поддерево содержит на один уровень больше — узел имеет преобладание левых узлов.
- Значение 0 означает, что обе стороны имеют одинаковую высоту — узел идеально сбалансирован.
Ротации АВЛ
Вращение выполняется всякий раз, когда вставка или удаление нарушает правило балансового коэффициента. Четыре случая: LL, RR, LR и RL.
Влево – вращение влево
Этот поворот выполняется, когда новый узел вставляется в левый дочерний элемент левого поддерева.
Дерево AVL слева – вращение влево
Выполняется однократное вращение вправо. Этот случай срабатывает, когда у узла значение BF + 2, а у его левого дочернего узла — BF + 1.
Вправо – правое вращение
Этот поворот выполняется, когда новый узел вставляется в правый дочерний элемент правого поддерева.
Выполняется однократное вращение влево. Этот случай срабатывает, когда у узла значение BF −2, а у его правого дочернего узла — BF −1.
Право-левое вращение
Этот поворот выполняется, когда новый узел вставляется в левый дочерний элемент правого поддерева.
Срабатывает, когда BF(узел) = −2 и BF(правый дочерний узел) = +1. Поворачивает правый дочерний узел вправо, затем поворачивает узел влево.
Вращение влево-вправо
Этот поворот выполняется, когда новый узел вставляется в правый дочерний элемент левого поддерева.
Срабатывает, когда BF(узел) = +2 и BF(левый дочерний узел) = −1. Поворачивает левый дочерний узел влево, затем узел вправо.
Вставка в деревья AVL
Процесс вставки практически идентичен вставке в обычное дерево поиска. После каждой вставки дерево поднимается вверх и выполняет перебалансировку. Вставка выполняется за время 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, выполнить вращение влево-вправо.
Удаление в деревьях 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, выполнить вращение влево-вправо. - 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.
- Поиск выполняется быстрее, чем поиск по простому бинарному дереву поиска, потому что дерево не может вырождаться.
- Самобалансировка происходит автоматически — перенастройка не требуется.
- Детерминированная производительность подходит для систем реального времени и индексов, хранящихся в оперативной памяти.











