Menu
Coddy logo textTech

AVL-дерево

Последнее обновление

AVL-дерево - это самобалансирующееся двоичное дерево поиска. Оно работает как обычное BST, но после каждой вставки проверяет коэффициент баланса каждого предка - разницу высот между его левым и правым поддеревьями - и если какой-либо узел становится несбалансированным (разница больше 1), выполняет повороты для восстановления баланса. Нажмите воспроизведение выше, чтобы увидеть, как вставляются значения и дерево поворачивается обратно в форму.

Поскольку оно никогда не позволяет дереву перекоситься больше чем слегка, AVL-дерево гарантирует высоту O(log n), поэтому поиск, вставка и удаление всегда O(log n) - даже для отсортированного ввода, который разрушил бы обычное BST. Ценой этого являются дополнительные повороты и учёт высоты при каждой вставке.

Временная и пространственная сложность

ОперацияСложностьПримечания
ПоискO(log n)Высота всегда ~1.44 log n
ВставкаO(log n)Плюс O(1) поворотов
УдалениеO(log n)Плюс O(log n) поворотов
ПамятьO(n)Одно поле высоты на узел

Четыре случая поворота

СлучайДисбалансИсправление
Лево-ЛевоПеревес слева у левого потомкаОдин правый поворот
Право-ПравоПеревес справа у правого потомкаОдин левый поворот
Лево-ПравоПеревес справа у левого потомкаЛевый, затем правый поворот
Право-ЛевоПеревес слева у правого потомкаПравый, затем левый поворот

Разобранный пример

Вставка [10, 20, 30, 40, 50, 25] по одному значению:

ШагСтруктураДействие
Вставить 1010Первый узел становится корнем; сбалансировано
Вставить 2010(_, 20)Идёт вправо от 10; всё ещё сбалансировано
Вставить 3020(10, 30)Право-Право в 10, поэтому один левый поворот поднимает 20 в корень
Вставить 4020(10, 30(_, 40))Идёт вправо от 30; все коэффициенты баланса остаются в пределах ±1
Вставить 5020(10, 40(30, 50))Право-Право в 30, поэтому левый поворот в 30 поднимает 40
Вставить 2530(20(10, 25), 40(_, 50))Право-Лево в 20: повернуть вправо поддерево 40, затем повернуть влево 20

Когда использовать AVL-дерево

Используйте, когдаИзбегайте, когда
Поисков намного больше, чем вставок, и вам нужна максимально компактная высотаВставки и удаления преобладают - дополнительные повороты и перебалансировка обходятся дороже, чем у красно-чёрного дерева
Вам нужен гарантированный худший случай O(log n), даже для враждебного или отсортированного вводаДостаточно обычной хеш-таблицы - вам не нужен упорядоченный обход или запросы по диапазону
Вам нужны упорядоченные операции: обход в порядке возрастания, предшественник/преемник, запросы по диапазонуНабор данных крошечный - обычное BST или отсортированный массив проще и достаточно быстры
Данные могут поступать уже отсортированными, что превратило бы несбалансированное BST в связный списокВы не можете позволить себе дополнительную память поля высоты на узел в очень ограниченном объёме

Код AVL Tree

Чистая, готовая к запуску реализация AVL Tree на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.

Код AVL Tree на Python

Python
1class Node:2    def __init__(self, key):3        self.key = key4        self.left = None5        self.right = None6        self.height = 17
8
9def height(node):10    return node.height if node else 011
12
13def update(node):14    node.height = 1 + max(height(node.left), height(node.right))15
16
17def rotate_right(y):18    x = y.left19    y.left = x.right20    x.right = y21    update(y)22    update(x)23    return x24
25
26def rotate_left(x):27    y = x.right28    x.right = y.left29    y.left = x30    update(x)31    update(y)32    return y33
34
35def insert(node, key):36    if node is None:37        return Node(key)38    if key < node.key:39        node.left = insert(node.left, key)40    else:41        node.right = insert(node.right, key)42    update(node)43    balance = height(node.left) - height(node.right)44    # Four imbalance cases: LL, RR, LR, RL45    if balance > 1 and key < node.left.key:46        return rotate_right(node)47    if balance < -1 and key > node.right.key:48        return rotate_left(node)49    if balance > 1:50        node.left = rotate_left(node.left)51        return rotate_right(node)52    if balance < -1:53        node.right = rotate_right(node.right)54        return rotate_left(node)55    return node56
57
58def inorder(node):59    if node is None:60        return []61    return inorder(node.left) + [node.key] + inorder(node.right)62
63
64root = None65for key in [10, 20, 30, 40, 50, 25]:66    root = insert(root, key)67
68print("Inorder:", inorder(root))69print("Root:", root.key, "| tree height:", root.height)
Запустите этот код в плейграунде Python

Часто задаваемые вопросы об AVL-дереве

Что такое коэффициент баланса в AVL-дереве?
Коэффициент баланса узла - это высота его левого поддерева минус высота его правого поддерева. AVL-дерево удерживает коэффициент баланса каждого узла на уровне -1, 0 или +1; если вставка выводит его за пределы этого диапазона, поворот восстанавливает баланс.
В чём разница между AVL-деревом и красно-чёрным деревом?
Оба являются самобалансирующимися BST с операциями O(log n). AVL-деревья сбалансированы строже, поэтому поиск немного быстрее, но при вставке/удалении они могут выполнять больше поворотов. Красно-чёрные деревья сбалансированы более свободно, отдавая предпочтение меньшему числу поворотов - вот почему многие стандартные библиотеки используют их для отображений и множеств.
Зачем использовать AVL-дерево вместо обычного двоичного дерева поиска?
Обычное BST может деградировать до операций O(n), если значения поступают в отсортированном порядке, образуя перекошенную цепочку. AVL-дерево поворачивается, чтобы оставаться сбалансированным, гарантируя высоту и операции O(log n) независимо от порядка вставки.
Лучше ли AVL-дерево, чем красно-чёрное дерево, для индекса базы данных?
Это зависит от нагрузки. AVL-деревья сбалансированы жёстче, поэтому они выигрывают, когда преобладают чтения и вам нужны кратчайшие возможные пути поиска. Но большинство баз данных и библиотек языков выбирают красно-чёрные деревья (или B-деревья), потому что они перебалансируются с меньшим числом поворотов при нагрузках с частой записью, что важнее, когда вставки и удаления происходят часто.
Сколько поворотов требуется для одной вставки в AVL-дерево?
Для перебалансировки дерева после вставки одного значения требуется не более одного поворота - либо одинарного, либо двойного (двухшагового). Это потому, что вставка увеличивает высоту поддерева не более чем на единицу, поэтому достаточно одного исправления у самого нижнего несбалансированного предка. Удаление другое: оно может потребовать до O(log n) поворотов, по мере того как перебалансировка распространяется к корню.
Нужно ли обновлять высоты узлов после каждого поворота?
Да - распространённая ошибка - правильно повернуть указатели, но забыть пересчитать высоту двух задействованных узлов. После каждого поворота нужно сначала обновить высоту понижаемого узла, а затем повышаемого, потому что высота повышаемого узла зависит от его новых потомков. Пропуск этого оставляет устаревшие коэффициенты баланса, которые нарушают будущие решения о перебалансировке.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ