Редактирование: АВЛ-дерево

Перейти к: навигация, поиск

Внимание! Вы не авторизовались на сайте. Ваш IP-адрес будет публично видимым, если вы будете вносить любые правки. Если вы войдёте или создадите учётную запись, правки вместо этого будут связаны с вашим именем пользователя, а также у вас появятся другие преимущества.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 1: Строка 1:
'''АВЛ-дерево''' (англ. ''AVL-Tree'') {{---}} сбалансированное [[Дерево поиска, наивная реализация|двоичное дерево поиска]], в котором поддерживается следующее свойство: для каждой его вершины высота её двух поддеревьев различается не более чем на 1.
+
'''АВЛ-дерево''' {{---}} сбалансированное [[Дерево поиска, наивная реализация|двоичное дерево поиска]], в котором поддерживается следующее свойство: для каждой его вершины высота её двух поддеревьев различается не более чем на 1.
  
 
АВЛ-деревья названы по первым буквам фамилий их изобретателей, Г. М. Адельсона-Вельского и Е. М. Ландиса, которые впервые предложили использовать АВЛ-деревья в 1962 году.
 
АВЛ-деревья названы по первым буквам фамилий их изобретателей, Г. М. Адельсона-Вельского и Е. М. Ландиса, которые впервые предложили использовать АВЛ-деревья в 1962 году.
Строка 13: Строка 13:
 
|statement= Пусть <tex>m_h</tex> {{---}} минимальное число вершин в AVL-дереве высоты <tex>h</tex>, тогда <tex>m_h = F_{h+2} - 1</tex>, где            <tex>F_h - h</tex>-ое число Фибоначчи.   
 
|statement= Пусть <tex>m_h</tex> {{---}} минимальное число вершин в AVL-дереве высоты <tex>h</tex>, тогда <tex>m_h = F_{h+2} - 1</tex>, где            <tex>F_h - h</tex>-ое число Фибоначчи.   
 
|proof=
 
|proof=
Если <tex>m_h</tex> {{---}} минимальное число вершин в AVL-дереве высоты <tex>h</tex>. Тогда как легко видеть, <tex>m_{h+2} = m_{h+1} + m_h + 1</tex>. Равенство <tex>m_h = F_{h+2} - 1</tex> докажем по индукции.  
+
Если <tex>m_h</tex> {{---}} минимальное число вершин в AVL-дереве высоты <tex>h</tex>. Тогда, как легко видеть, <tex>m_{h+2} = m_{h+1} + m_h + 1</tex>. Равенство <tex>m_h = F_{h+2} - 1</tex> докажем по индукции.  
  
 
База индукции <tex>m_1 = F_3 - 1</tex> {{---}} верно, <tex>m_1 = 1, F_3 = 2</tex>.  
 
База индукции <tex>m_1 = F_3 - 1</tex> {{---}} верно, <tex>m_1 = 1, F_3 = 2</tex>.  
  
Допустим <tex>m_h = F_{h+2} - 1</tex> {{---}} верно.  
+
Допустим <tex>m_h = F_{h+2} - 1</tex> {{---}} верно. Тогда <tex>m_{h+1} = F_{h+3} - 1</tex>.Так как <tex>m_h = m_{h-1} + m_{h-2} + 1, m_{h+1} = m_h + m_{h-1} + 1</tex>, то:
  
Тогда <tex>m_{h+1} = m_h + m_{h-1} + 1 = F_{h+2} - 1 + F_{h+1} - 1 + 1 = F_{h+3} - 1</tex>.  
+
<tex>m_{h+1} - m_h = m_h - m_{h-2}</tex>,
 +
 
 +
<tex>m_{h+1} - m_h = F_{h+3} - F_{h+2}</tex>,
 +
 
 +
<tex>m_h - m_{h-2} = F_{h+3} - F_{h+2}</tex>,
 +
 
 +
<tex>F_{h+2} - F_h = F_{h+1}</tex>,
 +
 
 +
<tex>F_{h+1} = F_{h+1}</tex>.
  
 
Таким образом, равенство <tex>m_h = F_{h+2} - 1</tex> {{---}} доказано.
 
Таким образом, равенство <tex>m_h = F_{h+2} - 1</tex> {{---}} доказано.
Строка 25: Строка 33:
  
  
<tex>F_h = \Omega(\varphi^h)</tex>, <tex>\varphi = \dfrac{ \sqrt{5}+1}{2}</tex>. То есть
+
<tex>F_h = \Omega(\varphi^h)</tex>, <tex>\varphi = \frac{ \sqrt{5}+1}{2}</tex>. То есть
  
 
<tex>n \geqslant \varphi^{h}</tex>
 
<tex>n \geqslant \varphi^{h}</tex>
Строка 33: Строка 41:
 
<tex>\log_{\varphi}n \geqslant h</tex>
 
<tex>\log_{\varphi}n \geqslant h</tex>
  
Таким образом,  получаем, что высота AVL-дерева из n вершин {{---}} <tex>O(\log{n})</tex>.
+
Таким образом,  получаем, что высота AVL-дерева из n вершин {{---}} <tex>O(\log{n})</tex>..
 
}}
 
}}
  
 
== Балансировка ==
 
== Балансировка ==
'''Балансировкой вершины''' называется операция, которая в случае разницы высот левого и правого поддеревьев <tex>|h(L) - h(R)| = 2</tex>, изменяет связи предок-потомок в поддереве данной вершины так, чтобы восстановилось свойство дерева <tex>|h(L) - h(R)| \leqslant 1</tex>, иначе ничего не меняет.
+
'''Балансировкой вершины''' называется операция, которая в случае разницы высот левого и правого поддеревьев <tex>|h(L) - h(R)| = 2</tex>, изменяет связи предок-потомок в поддереве данной вершины так, чтобы восстановилось свойство дерева <tex>|h(L) - h(R)| \le 1</tex>, иначе ничего не меняет.
 
Для балансировки будем хранить для каждой вершины разницу между высотой её левого и правого поддерева <tex>diff[i] = h(L) - h(R)</tex>
 
Для балансировки будем хранить для каждой вершины разницу между высотой её левого и правого поддерева <tex>diff[i] = h(L) - h(R)</tex>
  
Строка 49: Строка 57:
 
  |-
 
  |-
 
  |'''Малое левое вращение'''
 
  |'''Малое левое вращение'''
  |  [[Файл:avl_u1.jpg|2000x200px]]   
+
  |  [[Файл:AVL RR.GIF|2000x150px]]   
  |
+
  |<tex>h(b) - h(L) = 2</tex> и <tex>h(C) \le h(R)</tex>.
 
<tex>diff[a] = -2</tex> и <tex>diff[b] = -1</tex>
 
<tex>diff[a] = -2</tex> и <tex>diff[b] = -1</tex>
  
Строка 63: Строка 71:
  
 
<tex>diff[a] = -1</tex> и <tex>diff[b] = 1</tex>
 
<tex>diff[a] = -1</tex> и <tex>diff[b] = 1</tex>
 +
 
  |-
 
  |-
 
  |'''Большое левое вращение'''
 
  |'''Большое левое вращение'''
  | [[Файл:avl_u2.jpg|2000x200px]]
+
  | [[Файл:AVL RL.GIF|2000x150px]]
  |
+
  |<tex>h(b) - h(L) = 2</tex> и <tex>h(c) > h(R)</tex>.
 
<tex>diff[a] = -2</tex> , <tex>diff[b] = 1</tex> и <tex>diff[c] = 1</tex>
 
<tex>diff[a] = -2</tex> , <tex>diff[b] = 1</tex> и <tex>diff[c] = 1</tex>
  
Строка 86: Строка 95:
  
 
<tex>diff[a] = 0</tex>, <tex>diff[b] = 0</tex> и <tex>diff[c] = 0</tex>
 
<tex>diff[a] = 0</tex>, <tex>diff[b] = 0</tex> и <tex>diff[c] = 0</tex>
|}
 
Малый левый поворот:
 
  '''function''' rotateLeft(Node a):
 
    Node b = a.right
 
    a.right = b.left
 
    b.left = a
 
    корректировка высоты a
 
    корректировка высоты b
 
  
Большой левый поворот пишется проще:
+
|-
  '''function''' bigRotateLeft(Node a):
+
|'''Малое правое вращение'''
    rotateRight(a.right)
+
| [[Файл:AVL LL.GIF|2000x150px]] 
    rotateLeft(a)
+
|<tex>h(b) - h(R) = 2</tex> и <tex>h(C) \le h(L)</tex>.
 
+
<tex>diff[a] = 2</tex> и <tex>diff[b] = 1</tex>.
Малое правое и большое правое вращение определяются симметрично малому левому и большому левому вращению.
 
В каждом случае операция приводит к нужному результату, а полная высота уменьшается не более чем на <tex>1</tex> и не может увеличиться.
 
  
Все вращения, очевидно, требуют <tex>O(1)</tex> операций.
+
или
  
== Операции ==
+
<tex>diff[a] = 2</tex> и <tex>diff[b] = 0</tex>.  
=== Добавление вершины ===
+
|
Пусть нам надо добавить ключ <tex>t</tex>. Будем спускаться по дереву, как при поиске ключа <tex>t</tex>. Если мы стоим в вершине <tex>a</tex> и нам надо идти в поддерево, которого нет, то делаем ключ <tex>t</tex> листом, а вершину <tex>a</tex> его корнем. Дальше поднимаемся вверх по пути поиска и пересчитываем баланс у вершин. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то <tex>diff[i]</tex> увеличивается на единицу, если из правого, то уменьшается на единицу. Если пришли в вершину и её баланс стал равным нулю, то это значит высота поддерева не изменилась и подъём останавливается. Если пришли в вершину и её баланс стал равным <tex>1</tex> или <tex>-1</tex>, то это значит высота поддерева изменилась и подъём продолжается. Если пришли в вершину и её баланс стал равным <tex>2</tex> или <tex>-2</tex>, то делаем одно из четырёх вращений и, если после вращения баланс стал равным нулю, то останавливаемся, иначе продолжаем подъём.
 
  
Так как в процессе добавления вершины мы рассматриваем не более, чем <tex> O(h) </tex> вершин дерева, и для каждой запускаем балансировку не более одного раза, то суммарное количество операций при включении новой вершины в дерево составляет <tex> O(\log{n}) </tex> операций.
 
  
=== Удаление вершины ===
+
<tex>diff[a] = 0</tex> и <tex>diff[b] = 0</tex>
Для простоты опишем рекурсивный алгоритм удаления.
 
Если вершина {{---}} лист, то [[Дерево поиска, наивная реализация#Удаление|удалим]] её, иначе найдём самую близкую по значению вершину <tex>a</tex>, переместим её на место удаляемой вершины и [[Дерево поиска, наивная реализация#Удаление|удалим]] вершину <tex>a</tex>. От удалённой вершины будем подниматься вверх к корню и пересчитывать баланс у вершин. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то <tex>diff[i]</tex> уменьшается на единицу, если из правого, то увеличивается на единицу. Если пришли в вершину и её баланс стал равным <tex>1</tex> или <tex>-1</tex>, то это значит, что высота этого поддерева не изменилась и подъём можно остановить. Если баланс вершины стал равным нулю, то высота поддерева уменьшилась и подъём нужно продолжить. Если баланс стал равным <tex>2</tex> или <tex>-2</tex>, следует выполнить одно из четырёх вращений и, если после вращений баланс вершины стал равным нулю, то подъём продолжается, иначе останавливается.
 
 
 
В результате указанных действий на удаление вершины и балансировку суммарно тратится, как и ранее, <tex> O(h) </tex> операций. Таким образом, требуемое количество действий {{---}} <tex> O(\log{n}) </tex>.
 
 
 
=== Поиск вершины, минимум/максимум в дереве, etc. ===
 
Остальные операции не меняют структуры дерева, поэтому выполняются так же, как и в [[Дерево поиска, наивная реализация|наивной реализации]] дерева поиска.
 
===Слияние двух AVL-деревьев===
 
  
Дано два дерева <tex>T_1</tex> и <tex>T_2</tex>, все ключи в <tex>T_1</tex> меньше ключей в <tex>T_2</tex>, <tex>h(T_1) \leqslant h(T_2)</tex>.
 
  
В дереве <tex>T_1</tex> удаляем самую правую вершину, назовём её <tex>b</tex>. Высота дерева <tex>T_1</tex> может уменьшиться на единицу. В дереве <tex>T_2</tex> идём от корня всегда в левое поддерево и, когда высота этого поддерева <tex>P</tex> будет равна высоте дерева <tex>T_1</tex>, делаем новое дерево <tex>S</tex>, корнем <tex>S</tex> будет вершина <tex>b</tex>, левым поддеревом будет дерево <tex>T_1</tex>, а правым дерево <tex>P</tex>. Теперь в дереве <tex>T_2</tex> у вершины, в которой мы остановились при спуске, левым поддеревом делаем дерево <tex>S</tex> и запускаем балансировку. Таким образом, дерево <tex>T_2</tex> будет результатом слияния двух АВЛ-деревьев.
+
<tex>diff[a] = 1</tex> и <tex>diff[b] = -1</tex>
  
Дерево <tex>T_1</tex> и <tex>T_2</tex> до слияния
+
|-
 +
|'''Большое правое вращение'''
 +
| [[Файл:AVL LR.GIF|2000x150px]]
 +
|<tex>h(b) - h(R) = 2</tex> и <tex>h(c) > h(L)</tex>.
 +
<tex>diff[a] = 2</tex>, <tex>diff[b] = -1</tex> и <tex>diff[c] = 1</tex>
  
 +
или
  
[[File:avl_u3.jpg|340px]]
+
<tex>diff[a] = 2</tex>, <tex>diff[b] = -1</tex> и <tex>diff[c] = -1</tex>
  
Дерево <tex>T_2</tex> после слияния
+
или
 
 
 
 
[[File:avl_u4.jpg|300px]]
 
 
 
===Алгоритм разделения AVL-дерева на два===
 
====Алгоритм первый====
 
Пусть у нас есть дерево <tex>T</tex>. Мы должны разбить его на два дерева <tex>T_{1}</tex> и <tex>T_{2}</tex> такие, что <tex>T_{1} \leqslant x</tex> и <tex>x < T_{2}</tex>.
 
 
 
Предположим, что корень нашего дерева <tex>\leqslant x</tex>, в таком случае все левое поддерево вместе с корнем после разделения отойдет в дерево <tex>T_{1}</tex>. Тогда рекурсивно спускаемся в правое поддерево и там проверяем это условие (так как часть правого поддерева тоже может содержать ключи <tex>\leqslant x</tex>). Если же корень оказался <tex>> x</tex>, то мы спускаемся той же рекурсией, но только в левое поддерево и ищем там.
 
 
 
Пусть мы пришли в поддерево <tex>S</tex>, корень которого <tex>\leqslant x</tex>. В таком случае этот корень со своим левым поддеревом должен отойти в дерево <tex>T_{1}</tex>. Поэтому мы делаем следующее: запоминаем ссылку на правое поддерево <tex>S</tex>, удаляем корень, запоминая его значение (не меняя конфигурацию дерева, то есть просто делаем ссылки на него NULL'ами). Таким образом, мы отделяем сбалансированное АВЛ-дерево (бывшее левое поддерево <tex>S</tex>). Делаем новую вершину со значением бывшего корня правым листом самой правой вершины <tex>S</tex> и запускаем балансировку. Обозначим полученное дерево за <tex>T'</tex>. Теперь нам нужно объединить его с уже построенным ранее <tex>T_{1}</tex> (оно может быть пустым, если мы первый раз нашли такое дерево <tex>S</tex>). Для этого мы ищем в дереве <tex>T_{1}</tex> самое правое поддерево <tex>P</tex> высоты, равной высоте <tex>T'</tex> (спускаясь от корня всегда в правые поддеревья). Делаем новое дерево <tex>K</tex>, сливая <tex>P</tex> и <tex>T'</tex> (очевидно, все ключи в <tex>T_{1}</tex> меньше ключей в <tex>T'</tex>, поэтому мы можем это сделать). Теперь в дереве <tex>T_{1}</tex> у отца вершины, в которой мы остановились при поиске дерева <tex>P</tex>, правым поддеревом делаем дерево <tex>K</tex> и запускаем балансировку. После нужно спуститься в правое поддерево бывшего дерева <tex>S</tex> (по ссылке, которую мы ранее запомнили) и обработать его.
 
 
 
Если мы пришли в поддерево <tex>Q</tex>, корень которого <tex>> x</tex>, совершаем аналогичные действия: делаем NULL'ами ссылки на корень <tex>Q</tex>, запоминая ссылку на его левое поддерево. Делаем новую вершину со значением бывшего корня левым листом самой левой вершины <tex>Q</tex> и запускаем балансировку. Объединяем полученное АВЛ-дерево с уже построенным ранее <tex>T_{2}</tex> аналогичным первому случаю способом, только теперь мы ищем самое левое поддерево <tex>T_{2}</tex>.
 
 
 
Рассмотрим пример (рис. 1). Цветом выделены поддеревья, которые после разделения должны отойти в дерево <tex>T_{1}</tex>. <tex>x = 76</tex>.
 
 
 
{| cellpadding="2"
 
| || [[Файл:AVL.jpg|thumb|left|525px|Рис. 1. Разделение АВЛ-дерева на два.]]
 
|}
 
 
 
Корень дерева <tex>\leqslant x</tex>, поэтому он со всем выделенным поддеревом должен отойти в дерево <tex>T_{1}</tex>. По описанному выше алгоритму отделяем это поддерево с корнем и делаем из них сбалансированное АВЛ-дерево <tex>T'</tex> (рис. 2). Так как это первая ситуация, в которой корень рассматриваемого поддерева был <tex>\leqslant x</tex>, <tex>T'</tex> становится <tex>T_{1}</tex>. Далее по сохраненной ссылке спускаемся в правое поддерево. Его корень <tex>> x</tex>. Следовательно, строим из него и его правого поддерева <tex>T_{2}</tex> и спускаемся в левое поддерево. Снова корень <tex>\leqslant x</tex>. Строим новое <tex>T'</tex> и объединяем его с уже существующим <tex>T_{1}</tex> (рис. 3).
 
 
 
{| cellpadding="2"
 
| || [[Файл:АВВЛ2.jpg|thumb|left|525px|Рис. 2. Создание T'.]]
 
|}
 
{| cellpadding="2"
 
| || [[Файл:AVL3.jpg|thumb|left|1050px|Рис. 3. Объединение T' и T1.]]
 
|}
 
 
 
Далее действуем по алгоритму и в итоге получаем (рис. 4):
 
 
 
{| cellpadding="2"
 
| || [[Файл:End.jpg|thumb|left|525px|Рис. 4. АВЛ-деревья после разделения.]]
 
|}
 
 
 
Данный алгоритм имеет сложность <tex>O(\log^{2} n)</tex>.
 
 
 
====Алгоритм второй====
 
Рассмотрим решение, которое имеет сложность <tex>O(\log{n})</tex>.
 
 
 
Вернемся к примеру (рис. 1). Теперь рекурсивно спустимся вниз и оттуда будем строить деревья <tex>T_{1}</tex> и <tex>T_{2}</tex>, передавая наверх корректные АВЛ-деревья. То есть для рис. 1 первым в дерево <tex>T_{1}</tex> придет вершина <tex>75</tex> с левым поддеревом (выделено светло-зеленым цветом), так как это корректное АВЛ-дерево, оно же и вернется из рекурсии. Далее мы попадем в вершину со значением <tex>70</tex> и должны слить ее и ее левое поддерево (выделено светло-синим) с тем, что нам пришло. И сделать это нужно так, чтобы передать наверх корректное АВЛ-дерево. Будем действовать по такому алгоритму, пока не дойдем до вершины.
 
 
 
Пусть мы пришли в поддерево <tex>S</tex> с корнем <tex>\leqslant x</tex>. Тогда сольем его с уже построенным на тот момент <tex>T_{1}</tex> (<tex>T_{1}</tex> пришло снизу, а значит по условию рекурсии это корректное АВЛ-дерево, <tex>S \leqslant T_{1}</tex> и <tex>h(T_{1}) \leqslant h(S)</tex>). Но так как обычная процедура слияния сливает два АВЛ-дерева, а <tex>S</tex> не является корректным АВЛ-деревом, мы немного ее изменим. Пусть мы в дереве <tex>S</tex> нашли самое правое поддерево <tex>K</tex>, высота которого равна высоте <tex>T_{1}</tex>. Тогда сделаем новое дерево <tex>T'</tex>, корнем которого будет вершина <tex>S</tex> (без нее это дерево является сбалансированным), правым поддеревом {{---}} <tex>T_{1}</tex>, левым {{---}} <tex>K</tex>. И подвесим <tex>T'</tex> на то место, где мы остановились при поиске <tex>K</tex>. Запустим балансировку. В случае, когда корень поддерева, в которое мы пришли, <tex>> x</tex>, все аналогично.
 
 
 
Разберем пример на рис. 1. Пусть мы рекурсивно спустились до узла <tex>77</tex>. Ключ больше <tex>x</tex>, поэтому эта вершина станет деревом <tex>T_{2}</tex> и передастся наверх. Теперь мы поднялись в узел <tex>75</tex>. Он со своим левым поддеревом станет деревом <tex>T_{1}</tex> и мы снова поднимемся наверх в узел <tex>70</tex>. Он со своим левым поддеревом снова должен отойти в дерево <tex>T_{1}</tex>, и так как теперь дерево <tex>T_{1}</tex> уже не пустое, то их надо слить. После слияния по описанному выше алгоритму получим (рис. 5)
 
 
 
{| cellpadding="2"
 
| || [[Файл:Ex.jpg|thumb|left|525px|Рис. 5.]]
 
|}
 
 
 
После мы поднимемся в вершину с ключом <tex>80</tex>. Она с правым поддеревом отойдет в дерево <tex>T_{2}</tex> (рис. 6).
 
 
 
{| cellpadding="2"
 
| || [[Файл:Ex2am.jpg|thumb|left|525px|Рис. 6.]]
 
|}
 
 
 
И на последней итерации мы поднимемся в корень дерева с ключом <tex>50</tex>, он с левым поддеревом отойдет в дерево <tex>T_{1}</tex>, после чего алгоритм завершится.
 
 
 
Пусть поддеревьев с ключами <tex>\leqslant x</tex> оказалось больше, чем поддеревьев с ключами <tex>> x</tex>. Докажем для них логарифмическую асимптотику. Дерево на последнем уровне имеет высоту <tex>H_{k}</tex> (она может быть не равна <tex>1</tex>, если мы придём в <tex>x</tex>). Его мы передаем наверх и вставляем в поддерево высотой <tex>H_{k-1}</tex>. <tex>H_{k} \leqslant H_{k-1}</tex>, так как разница высот поддеревьев у любой вершины не больше <tex>1</tex>, и мы при переходе от <tex>H_{k}</tex> к <tex>H_{k-1}</tex> поднимаемся как минимум на одну вершину вверх. Слияние этих поддеревьев мы выполним за <tex>H_{k-1} - H_{k}</tex>, получим в итоге дерево высоты не большей, чем <tex>H_{k-1}</tex>. Его мы передадим наверх, поэтому в следующий раз слияние будет выполнено за <tex>H_{k-2} - H_{k - 1}</tex> и так далее. Таким образом мы получим <tex>(H - H_{1}) + (H_{1} - H_{2}) + (H_{2} - H_{3}) + \cdots +  (H_{k - 1} - H_{k}) = H - H_{k} = O(\log{n})</tex>.
 
 
 
Итоговая асимптотика алгоритма {{---}} <tex>O(\log{n})</tex>.
 
 
 
== АВЛ-дерево с O(1) бит в каждом узле ==
 
 
 
=== Идея ===
 
В обычной реализации АВЛ-дерева в каждом узле мы хранили высоту этого узла. Так как высоты левых и правых поддеревьев в АВЛ-дереве отличаются максимум на <tex>1</tex>, то мы будем хранить не всю высоту дерева, а некоторое число, которое будет показывать, какое поддерево больше, или равны ли они, назовём ''фактор баланса''. Таким образом в каждом узле будет храниться <tex>1</tex> {{---}} если высота правого поддерева выше левого, <tex>0</tex> {{---}} если высоты равны, и <tex>-1</tex> {{---}} если правое поддерево выше левого.
 
 
 
=== Операции ===
 
 
 
'''Операция добавления''' <br>
 
Пусть нам надо добавить ключ <tex>t</tex>. Будем спускаться по дереву, как при поиске ключа <tex>t</tex>. Если мы стоим в вершине <tex>a</tex> и нам надо идти в поддерево, которого нет, то делаем ключ <tex>t</tex> листом, а вершину <tex>a</tex> его корнем. Пересчитываем баланс данного узла <tex>a</tex>. Если он оказался <tex>0</tex>, то высота поддерева с корнем в этой вершине не изменилась и пересчет балансов останавливается. Дальше начинаем подниматься вверх по дереву, исправляя балансы попутных узлов. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то баланс увеличивается на единицу, если из правого, то уменьшается на единицу. Если мы пришли в вершину и её баланс стал равным <tex>1</tex> или <tex>-1</tex>, то это значит, что высота поддерева изменилась, и подъём продолжается. Если баланс вершины <tex>a</tex>, в которую мы собираемся идти из ее левого поддерева, равен <tex>1</tex>, то делается поворот для этой вершины <tex>a</tex>. Аналогично делаем поворот, если баланс вершины <tex>a</tex>, в которую мы идем из ее правого поддерева, равен <tex>-1</tex>. Если в результате изменения узла, фактор баланса стал равен нулю, то останавливаемся, иначе продолжаем подъём.
 
 
 
'''Операция удаления''' <br>
 
Если вершина {{---}} лист, то просто удалим её, иначе найдём ближайшую по значению вершину <tex>a</tex>, поменяем ее местами с удаляемой вершиной и удалим. От удалённой вершины будем подниматься вверх к корню и пересчитывать фактор баланса вершин. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то фактор баланса уменьшается на единицу, если из правого, то увеличивается на единицу. Если мы пришли в вершину и её баланс стал равным <tex>1</tex> или <tex>-1</tex>, то это значит, что высота поддерева не изменилась, и подъём можно остановить. Если баланс вершины стал равным нулю, то высота поддерева уменьшилась и подъём нужно продолжить. Если баланс вершины <tex>a</tex>, в которую мы собираемся идти из ее левого поддерева, равен <tex>-1</tex>, то делается поворот для этой вершины <tex>a</tex>. Аналогично делаем поворот, если баланс вершины <tex>a</tex>, в которую мы идем из ее правого поддерева, равен <tex>1</tex>. Если в результате изменения узла, фактор баланса стал равен нулю, то подъём продолжается, иначе останавливается.
 
 
 
=== Балансировка ===
 
Опишем операции балансировки, а именно малый левый поворот, большой левый поворот и случаи их возникновения. Балансировка нам нужна для операций добавления и удаления узла. Для исправления факторов баланса, достаточно знать факторы баланса двух(в случае большого поворота {{---}} трех) вершин перед поворотом, и исправить значения этих же вершин после поворота. Обозначим фактор баланса вершины <tex>i</tex> как <tex>balance[i]</tex>. Операции поворота делаются на том шаге, когда мы находимся в правом сыне вершины <tex>a</tex>, если мы производим операцию добавления, и в левом сыне, если мы производим операцию удаления. Вычисления производим заранее, чтобы не допустить значения <tex>2</tex> или <tex>-2</tex> в вершине <tex>a</tex>. На каждой иллюстрации изображен один случай высот поддеревьев. Нетрудно убедиться, что в остальных случаях всё тоже будет корректно.
 
  
{| border="1" cellpadding="5" cellspacing="0"
+
<tex>diff[a] = 2</tex>, <tex>diff[b] = -1</tex> и <tex>diff[c] = 0</tex>.
!Тип вращения
 
!Иллюстрация
 
!Факторы балансов до вращения
 
!Факторы балансов после вращения
 
|-
 
|'''Малое левое вращение'''
 
|  [[Файл:Avl_u1.jpg|2000x200px]] 
 
 
  |
 
  |
'''1 вариант:''' <tex>balance[a] = -1</tex> и <tex>balance[b] = -1</tex>
 
  
  
'''2 вариант:''' <tex>balance[a] = -1</tex> и <tex>balance[b] = 0</tex>
+
<tex>diff[a] = -1</tex>, <tex>diff[b] = 0</tex> и <tex>diff[c] = 0</tex>
|
 
  
'''1 вариант:''' <tex>balance[a] = 0</tex> и <tex>balance[b] = 0</tex>
 
  
 +
<tex>diff[a] = 0</tex>, <tex>diff[b] = 1</tex> и <tex>diff[c] = 0</tex>
  
'''2 вариант:''' <tex>balance[a] = -1</tex> и <tex>balance[b] = 1</tex>
 
  
|-
+
<tex>diff[a] = 0</tex>, <tex>diff[b] = 0</tex> и <tex>diff[c] = 0</tex>
|'''Большое левое вращение'''
+
|}
| [[Файл:Avl_u2.jpg|2000x200px]]
+
В каждом случае операция приводит к нужному результату, а полная высота уменьшается не более чем на 1 и не может увеличиться.
|
 
'''1 вариант:'''  <tex>balance[a] = -1</tex> , <tex>balance[b] = 1</tex> и <tex>balance[c] = 1</tex>
 
  
 +
Все операции вращения, очевидно, требуют <tex>O(1)</tex> операций.
  
'''2 вариант:''' <tex>balance[a] = -1</tex>, <tex>balance[b] = 1</tex> и <tex>balance[c] = -1</tex>
+
== Операции ==
 +
=== Добавление вершины ===
 +
Пусть нам надо добавить ключ <tex>t</tex>. Будем спускаться по дереву, как при поиске ключа <tex>t</tex>. Если мы стоим в вершине <tex>a</tex> и нам надо идти в поддерево, которого нет, то делаем ключ <tex>t</tex> листом, а вершину <tex>a</tex> его корнем. Дальше поднимаемся вверх по пути поиска и пересчитываем баланс у вершин. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то <tex>diff[i]</tex> увеличивается на единицу, если из правого, то уменьшается на единицу. Подъём останавливается, когда приходим в вершину, баланс которой равен нулю, то есть <tex>diff[i] = 0</tex>. Если в результате пересчёта баланса, баланс вершины стал равен 2 или -2, то запускаем процедуру балансировки, которая делает одно из четырёх вращений.
  
 +
Так как в процессе добавления вершины мы рассматриваем не более, чем <tex> O(h) </tex> вершин дерева, и для каждой запускаем балансировку не более одного раза, то суммарное количество операций при включении новой вершины в дерево составляет <tex> O(\log{n}) </tex> операций.
  
'''3 вариант:''' <tex>balance[a] = -1</tex>, <tex>balance[b] = 1</tex> и <tex>balance[c] = 0</tex>
+
=== Удаление вершины ===
|
+
Для простоты опишем рекурсивный алгоритм удаления.
 +
Если вершина - лист, то [[Дерево поиска, наивная реализация#Удаление|удалим]] её, иначе найдём самую близкую по значению вершину <tex>a</tex>, переместим её на место удаляемой вершины и [[Дерево поиска, наивная реализация#Удаление|удалим]] вершину <tex>a</tex>. От удалённой вершины будем подниматься вверх к корню и пересчитывать баланс у вершин. Если мы поднялись в вершину <tex>i</tex> из левого поддерева, то <tex>diff[i]</tex> уменьшается на единицу, если из правого, то увеличивается на единицу. Подъём останавливается, когда приходим в вершину, баланс которой равен нулю, то есть <tex>diff[i] = 0</tex>. Если в результате пересчёта баланса, баланс вершины стал равен 2 или -2, то запускаем процедуру балансировки, которая делает одно из четырёх вращений.
  
'''1 вариант:''' <tex>balance[a] = 0</tex>, <tex>balance[b] = -1</tex> и <tex>balance[c] = 0</tex>
+
В результате указанных действий на удаление вершины и балансировку суммарно тратится, как и ранее, <tex> O(h) </tex> операций. Таким образом, требуемое количество действий {{---}} <tex> O(\log{n}) </tex>.
  
 +
=== Поиск вершины, минимум/максимум в дереве, etc. ===
 +
Остальные операции не меняют структуры дерева, поэтому выполняются так же, как и в [[Дерево поиска, наивная реализация|наивной реализации]] дерева поиска.
 +
===Слияние двух AVL-деревьев===
 +
Дано два дерева <tex>T_1</tex> и <tex>T_2</tex>, все ключи в <tex>T_1</tex> меньше ключей в <tex>T_2</tex>, <tex>h(T_1) \le h(T_2)</tex>.
  
'''2 вариант:''' <tex>balance[a] = 1</tex>, <tex>balance[b] = 0</tex> и <tex>balance[c] = 0</tex>
 
  
 +
[[Файл: Avltree1.jpg]]
  
'''3 вариант:''' <tex>balance[a] = 0</tex>, <tex>balance[b] = 0</tex> и <tex>balance[c] = 0</tex>
+
В дереве <tex>T_1</tex> удаляем самую правую вершину, назовём её <tex>b</tex>. Высота дерева <tex>T_1</tex> может уменьшиться на единицу. В дереве <tex>T_2</tex> идём от корня всегда в левое поддерево и, когда высота этого поддерева <tex>P</tex> будет равна высоте дерева <tex>T_1</tex>, делаем новое дерево <tex>S</tex>, корнем <tex>S</tex> будет вершина <tex>b</tex>, левым поддеревом будет дерево <tex>T_1</tex>, а правым дерево <tex>P</tex>. Теперь в дереве <tex>T_2</tex> у вершины, в которой мы остановились при спуске, левым поддеревом делаем дерево <tex>S</tex> и запускаем балансировку. Таким образом, дерево <tex>T_2</tex> будет результатом слияния двух АВЛ-деревьев.
|}
 
 
 
=== Примеры ===
 
Ниже приведены примеры добавления и удаления вершины с подписанными изменениями факторов баланса каждой вершины.
 
{| cellpadding="2"
 
| || [[Файл:Avl_add.png|thumb|left|1050px|'''Добавление''']]
 
|}
 
{| cellpadding="2"
 
| || [[Файл:Avl_delete.png|thumb|left|1050px|'''Удаление''']]
 
|}
 
  
== См. также ==
+
[[Файл: Avltree2.jpg]]
* [[Splay-дерево]]
 
* [[Красно-черное дерево]]
 
* [[2-3 дерево]]
 
  
== Источники информации ==
+
== Литература ==
* [http://habrahabr.ru/post/150732/ Habrahabr {{---}} АВЛ-деревья]
 
 
* [http://ru.wikipedia.org/wiki/АВЛ-дерево Википедия {{---}} АВЛ-дерево]
 
* [http://ru.wikipedia.org/wiki/АВЛ-дерево Википедия {{---}} АВЛ-дерево]
  
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Деревья поиска]]
 
[[Категория:Деревья поиска]]
[[Категория:Структуры данных]]
 

Пожалуйста, учтите, что любой ваш вклад в проект «Викиконспекты» может быть отредактирован или удалён другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Викиконспекты:Авторские права). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!

Чтобы изменить эту страницу, пожалуйста, ответьте на приведённый ниже вопрос (подробнее):

Отменить | Справка по редактированию (в новом окне)

Шаблоны, используемые на этой странице: