1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 2 из 7

АВЛ-дерево: баланс по высоте

Коротко

АВЛ-дерево - первое самобалансирующееся дерево поиска. Его правило простое и строгое: в КАЖДОМ узле высоты левого и правого поддеревьев различаются не больше чем на 1. Эту разницу называют балансом узла (высота левого − высота правого, допустимы −1, 0, +1). Пока правило держится, высота дерева гарантированно Θ(log n). После вставки или удаления баланс может нарушиться на пути к корню; АВЛ чинит его ВРАЩЕНИЯМИ - разбирая ровно четыре случая перекоса (LL, RR, LR, RL). Первые два лечит одинарный поворот, вторые два - двойной. За счёт строгого баланса АВЛ даёт очень малую высоту (быстрый поиск), но чуть чаще вращает при изменениях, чем красно-чёрное дерево (следующий урок).

Если ты знаешь правило АВЛ (баланс каждого узла в пределах ±1), четыре случая перекоса LL/RR/LR/RL и какой поворот их чинит, и понимаешь, почему это держит высоту Θ(log n), листай до «Теперь сам».

Как думаешь, какое правило заставит дерево оставаться низким

Вращение умеет чинить перекос, но КОГДА его применять? Нужно правило-сторож: признак, по которому дерево понимает «я слишком перекосилось, пора поворачивать». Самый прямой признак - ВЫСОТА поддеревьев. Если у какого-то узла одно поддерево намного выше другого, дерево в этом месте вытягивается. Значит правило должно ограничивать РАЗНИЦУ высот. Насколько строго? Потребуем разницу 0 - это невыполнимо уже при двух узлах. Не ограничим вовсе - вернётся вырождение. Золотая середина - разрешить разницу не больше 1: дерево остаётся почти идеальным (высота Θ(log n)), а чинить надо лишь изредка. Это и есть правило АВЛ.

На что это похоже

Стеллаж, который нельзя нагружать однобоко: правило склада - «в любой секции левая и правая полки различаются по загрузке не более чем на один ящик». Кладовщик, поставив ящик, проверяет секции вверх по стеллажу: где разница дошла до двух - перекладывает (вращение), выравнивая. Стеллаж никогда не заваливается набок, потому что перекос ловится в зародыше - как только превысил единицу. АВЛ-дерево - такой стеллаж для ключей: после каждой вставки идём от нового листа вверх и, где баланс вышел за ±1, поворачиваем.

Механизм: баланс узла и четыре случая

В каждом узле хранят его ВЫСОТУ: высота(узел) = 1 + max(высота левого, высота правого), у пустого поддерева 0 (или сразу баланс). После вставки нового листа поднимаемся к корню, обновляя высоты; в первом узле, где баланс стал +2 или −2, чиним поворотом. Четыре случая (по тому, с какой стороны и как пришёл перекос):

LL (лево-лево):  перекос влево, и у левого ребёнка тяжёлое ЛЕВО  -> ОДНО правое вращение
RR (право-право): перекос вправо, у правого ребёнка тяжёлое ПРАВО -> ОДНО левое вращение
LR (лево-право): перекос влево, но у левого ребёнка тяжёлое ПРАВО ->
                 сперва левое вращение ребёнка, потом правое узла  (ДВОЙНОЙ)
RL (право-лево): перекос вправо, у правого ребёнка тяжёлое ЛЕВО   ->
                 сперва правое вращение ребёнка, потом левое узла  (ДВОЙНОЙ)

Простое правило распознавания: если перекос «прямой» (обе тяжёлые стороны в одну сторону, LL или RR) - хватает ОДНОГО поворота. Если «ломаный» (перекос узла в одну сторону, а ребёнка в другую, LR или RL) - нужен ДВОЙНОЙ: сначала выпрямляем ребёнка одним поворотом (превращая ломаный случай в прямой), потом чиним узел. Одна вставка требует не более ОДНОГО поворота (пусть и двойного); удаление - до O(log n) поворотов по пути вверх. Всё - O(log n).

LR: узел влево, ребёнок вправо CAB → после левого у A CBA → после правого у C BAC двойной поворот: сперва выпрямили ребёнка (LR→LL), потом повернули узел итог - сбалансированное поддерево высоты 2, порядок A < B < C цел
Здесь A, B, C - узлы, а не поддеревья. Ломаный случай LR: одно вращение у ребёнка превращает его в прямой (LL), второе - у узла - выравнивает. Двойной поворот - это два одинарных подряд.

Правило

АВЛ-дерево держит в КАЖДОМ узле разницу высот левого и правого поддеревьев в пределах ±1 (баланс −1, 0, +1). Это гарантирует высоту Θ(log n), а значит все операции Θ(log n). После вставки/удаления идут вверх к корню, обновляя высоты, и в первом разбалансированном узле (±2) чинят ВРАЩЕНИЕМ: прямые перекосы LL/RR - одинарным, ломаные LR/RL - двойным (сперва выпрямить ребёнка, потом узел). Вставка - не больше одного (двойного) поворота, удаление - до O(log n) поворотов. Строгий баланс = очень малая высота = самый быстрый поиск среди сбалансированных деревьев.

Разбор: почему баланс ±1 гарантирует высоту Θ(log n)

Докажем, что строгое правило не даёт дереву вытянуться. Спросим наоборот: каково МИНИМАЛЬНОЕ число узлов в АВЛ-дереве высоты h? Обозначим его N(h). Самое «худое» допустимое дерево высоты h имеет корень, одно поддерево высоты h−1 и другое высоты h−2 (разница ровно 1 - максимум, что позволено). Значит N(h) = 1 + N(h−1) + N(h−2) (N(1) = 1, N(2) = 2) - почти рекуррентность ФИБОНАЧЧИ: N(h) = F(h+2) − 1. Числа Фибоначчи растут ЭКСПОНЕНЦИАЛЬНО (как φ^h, где φ ≈ 1.618 - золотое сечение), и отсюда n ≥ N(h) ≥ φ^(h−1): самое худое дерево высоты h всё равно содержит экспоненциально много узлов. Значит h ≤ log_φ n + 1 ≈ 1.44·log₂ n + 1. То есть высота АВЛ-дерева не больше примерно 1.44·log₂ n - всего на 44% выше идеальной. Правило «разница ≤ 1» через Фибоначчи запирает высоту в Θ(log n): перекос просто не успевает накопиться, каждый уровень обязан быть достаточно «полным».

Разбор: почему для АВЛ хватает четырёх случаев и почему LR/RL - двойные

Кажется, перекосов должно быть много, но их ровно четыре, потому что описываются двумя двоичными выборами: с какой стороны перекошен УЗЕЛ (лево/право) и с какой стороны тяжелее у его РЕБЁНКА (лево/право). Совпали направления (лево-лево, право-право) - перекос «прямой», как наклонённая палка: одно вращение ставит её вертикально. Разошлись (лево-право, право-лево) - перекос «ломаный», как согнутое колено: одним поворотом не выпрямить, потому что тяжёлое поддерево ребёнка при повороте узла осталось бы посередине и снова перекосило бы. Поэтому сперва поворачивают РЕБЁНКА - это распрямляет колено, превращая LR в LL (или RL в RR), - и только потом поворачивают узел. Двойной поворот - не отдельная магия, а два одинарных подряд; важно лишь распознать, что случай ломаный, и повернуть в правильном порядке.

Что ломается без этого

Первая ошибка - применить ОДИНАРНЫЙ поворот к ломаному случаю (LR/RL): перекос не исчезнет, а переедет на другую сторону - дерево останется разбалансированным. Ломаные случаи требуют двойного поворота, и распознать их надо по направлению перекоса ребёнка. Вторая - забыть ОБНОВИТЬ высоты узлов после вставки и после поворотов: баланс будет считаться по старым высотам, и сторож перестанет срабатывать. Третья - чинить не в ПЕРВОМ разбалансированном узле снизу, а где попало: чинить надо в ближайшем к листу нарушенном узле, поднимаясь вверх. Четвёртая - думать, что при удалении хватит одного поворота, как при вставке: удаление может потребовать поворотов на всём пути вверх (до O(log n)), а не одного.

Зачем это в работе

АВЛ-деревья - выбор там, где ПОИСКОВ гораздо больше, чем вставок/удалений: строгий баланс даёт минимальную высоту (≤ 1.44·log₂ n + 1), а значит самый быстрый поиск среди сбалансированных деревьев. Их берут для индексов, читаемых часто и меняемых редко, и в задачах, где важна предсказуемо малая высота. Но у строгости есть цена: АВЛ поворачивает чаще (после многих удалений - до O(log n) поворотов), поэтому для нагрузок с частыми изменениями чаще берут красно-чёрное дерево (следующий урок) - оно балансирует слабее, зато вращает реже. Понимать АВЛ важно и само по себе, и как эталон «строгого» баланса, с которым сравнивают все остальные: это первый ответ на вырождение из прошлой главы - правило ±1 плюс вращения превращают Θ(n) худшего случая в гарантированные Θ(log n).

Теперь сам

В АВЛ-дерево вставляют по очереди 3, 2, 1. После какой вставки нарушится баланс, в каком узле, какой это случай и какой поворот его чинит?

Ответ. Вставили 3 - корень, баланс 0. Вставили 2: 2 < 3, левый ребёнок 3; у корня 3 левое поддерево высоты 1, правое 0, баланс +1 - ещё в норме. Вставили 1: 1 < 3 влево, 1 < 2 влево - 1 становится левым ребёнком 2. Теперь у корня 3 левое поддерево высоты 2, правое 0, баланс +2 - НАРУШЕНО. Это случай LL: узел 3 перекошен влево, и у его левого ребёнка 2 тоже тяжёлое ЛЕВО (там 1). Прямой перекос - чинится ОДНИМ правым вращением вокруг 3: поднимается 2, 3 опускается направо. Результат: корень 2, слева 1, справа 3 - сбалансировано, высота 2. Это ровно тот вырожденный ввод из прошлой главы (убывающий порядок), который обычный BST превратил бы в цепочку, - АВЛ поймал перекос сразу и выправил.

Главное

АВЛ-дерево - самобалансирующееся BST с правилом: в КАЖДОМ узле разница высот поддеревьев ≤ 1 (баланс −1/0/+1). Это гарантирует высоту ≤ 1.44·log₂ n + 1 (доказательство через числа Фибоначчи: худшее АВЛ высоты h имеет ≈ φ^h узлов), а значит все операции Θ(log n). После вставки/удаления идут вверх, обновляя высоты, и в первом узле с балансом ±2 чинят вращением: прямые перекосы LL/RR - одинарным поворотом, ломаные LR/RL - двойным (выпрямить ребёнка → затем узел). Вставка - ≤ 1 (двойного) поворота, удаление - до O(log n). Строгий баланс = минимальная высота = самый быстрый поиск, но вращает чаще, чем красно-чёрное дерево (дальше). Это первый ответ на вырождение из прошлой главы.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.