АВЛ-дерево — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м
м
Строка 5: Строка 5:
 
АВЛ-деревья названы по первым буквам фамилий их изобретателей, Г. М. Адельсона-Вельского и Е. М. Ландиса, которые впервые предложили использовать АВЛ-деревья в 1962 году.
 
АВЛ-деревья названы по первым буквам фамилий их изобретателей, Г. М. Адельсона-Вельского и Е. М. Ландиса, которые впервые предложили использовать АВЛ-деревья в 1962 году.
  
 +
== Высота дерева ==
 +
Высоту поддерева с корнем <tex>x</tex> будем обозначать как <tex>h(x)</tex>, высоту поддерева <tex>T</tex> {{---}} как <tex>h(T)</tex>
 +
 +
Можно показать, что в AVL-дереве высоты <tex>h</tex> не менее, чем <tex>F_h = \Omega(\varphi^h)</tex> вершин, где <tex>F_h - h</tex>-ое число Фиббоначи, <tex>\varphi = \frac{ \sqrt{5}+1}{2}</tex>. Делая замену <tex>h = \log_\varphi{n}</tex>, получаем, что высота AVL-дерева из n вершин - <tex>O(\log{n})</tex>.
 
== Балансировка ==
 
== Балансировка ==
Высоту поддерева с корнем <tex>x</tex> будем обозначать как <tex>h(x)</tex>, высоту поддерева <tex>T</tex> {{---}} как <tex>h(T)</tex>
 
 
{{Определение|definition=
 
{{Определение|definition=
 
'''Балансировкой вершины''' называется операция, которая в случае разницы высот левого и правого поддеревьев <tex>|h(L) - h(R)| = 2</tex>, изменяет связи предок-потомок в поддереве данной вершины так, чтобы восстановилось свойство дерева <tex>|h(L) - h(R)| \le 1</tex>, иначе ничего не меняет.
 
'''Балансировкой вершины''' называется операция, которая в случае разницы высот левого и правого поддеревьев <tex>|h(L) - h(R)| = 2</tex>, изменяет связи предок-потомок в поддереве данной вершины так, чтобы восстановилось свойство дерева <tex>|h(L) - h(R)| \le 1</tex>, иначе ничего не меняет.
Строка 33: Строка 36:
 
  |<tex>h(b) - h(R) = 2</tex> и <tex>h(c) > h(L)</tex>.
 
  |<tex>h(b) - h(R) = 2</tex> и <tex>h(c) > h(L)</tex>.
 
  |}
 
  |}
В каждом случае достаточно просто показать, что операция приводит к нужному результату и что полная высота уменьшается не более чем на 1 и не может увеличиться.
+
В каждом случае можно показать, что операция приводит к нужному результату и что полная высота уменьшается не более чем на 1 и не может увеличиться.
 +
 
 +
Все операции балансировки, очевидно, требуют <tex>O(1)</tex> операций.
  
== Алгоритм добавления вершины ==
+
== Добавление вершины ==
Показатель сбалансированности в дальнейшем будем интерпретировать как разность между высотой левого и правого поддерева, а алгоритм будет основаться на типе TAVLTree, описанном выше. Непосредственно при вставке (листу) присваивается нулевой баланс. Процесс включения вершины состоит из трех частей:
+
Процесс включения вершины состоит из двух частей:
 
# Прохода по пути поиска, пока не убедимся, что ключа в дереве нет.
 
# Прохода по пути поиска, пока не убедимся, что ключа в дереве нет.
# Включения новой вершины в дерево и определения результирующих показателей балансировки.
+
# "Отступления" назад по пути поиска, вплоть до корня и пересчета высот в каждой вершине на обратном пути. При необходимости осуществляется балансировка.
# "Отступления" назад по пути поиска и проверки в каждой вершине показателя сбалансированности. Если необходимо - балансировка.
 
Расширим список параметров обычной процедуры вставки параметром-переменной flag, означающим, что высота дерева увеличилась.
 
Предположим, что процесс из левой ветви возвращается к родителю (рекурсия идет назад), тогда возможны три случая:
 
{ h<sub>l</sub> - высота левого поддерева, h<sub>r</sub> - высота правого поддерева }
 
Включение вершины в левое поддерево приведет к
 
# h<sub>l</sub> < h<sub>r</sub>: выравняется h<sub>l</sub> = h<sub>r</sub>. Ничего делать не нужно.
 
# h<sub>l</sub> = h<sub>r</sub>: теперь левое поддерево будет больше на единицу, но балансировка пока не требуется.
 
# h<sub>l</sub> > h<sub>r</sub>: теперь h<sub>l</sub> - h<sub>r</sub> = 2, - требуется балансировка.
 
В третьей ситуации требуется определить балансировку левого поддерева. Если левое поддерево этой вершины выше правого, то требуется двойной правый поворот, иначе хватит и малого.
 
Аналогичные (симметричные) рассуждения можно привести и для включение в правое поддерево.
 
  
== Алгоритм удаления вершины ==
+
Так как в процессе добавления вершины мы рассматриваем не более, чем <tex> O(h) </tex> вершин дерева, и для каждой запускаем балансировку не более одного раза, то суммарное количество операций при включении новой вершины в дерево составляет <tex> O(\log{n}) </tex> операций.
 +
 
 +
== Удаление вершины ==
 
Для простоты опишем рекурсивный алгоритм удаления.
 
Для простоты опишем рекурсивный алгоритм удаления.
 
Если вершина - лист, то удалим её и вызовем балансировку всех её предков в порядке от родителя к корню.
 
Если вершина - лист, то удалим её и вызовем балансировку всех её предков в порядке от родителя к корню.
Иначе найдём самую близкую по значению вершину в поддереве наибольшей высоты (правом или левом) и переместим её на место удаляемой вершины, при этом вызвав процедуру её удаления.
+
Иначе найдём самую близкую по значению вершину и переместим её на место удаляемой вершины, при этом вызвав процедуру её удаления.
 
 
Докажем, что данный алгоритм сохраняет балансировку. Для этого докажем по индукции по высоте дерева, что после удаления некоторой вершины из дерева и последующей балансировки высота дерева уменьшается не более, чем на 1. База индукции: Для листа очевидно верно. Шаг индукции: Либо условие балансированности в корне (после удаления корень может изменится) не нарушилось, тогда высота данного дерева не изменилась, либо уменьшилось строго меньшее из поддеревьев => высота до балансировки не изменилась => после уменьшится не более чем на 1.
 
  
Очевидно, в результате указанных действий процедура удаления вызывается не более 3 раз, так как у вершины, удаляемой по 2-му вызову, нет одного из поддеревьев. Но поиск ближайшего каждый раз требует O(N) операций, отсюда видна очевидная оптимизация: поиск ближайшей вершины производится по краю поддерева. Отсюда количество действий O(log(N)).
+
В результате указанных действий процедура удаления вызывается не более 3 раз, так как у вершины, удаляемой по 2-му вызову, нет хотя бы одного из поддеревьев. На балансировку суммарно тратится, как и ранее, <tex> O(h) </tex> операций. Таким образом, требуемое количество действий {{---}} <tex> O(\log{n}) </tex>.

Версия 08:09, 21 марта 2011

Определение:
АВЛ-дерево — двоичное дерево поиска, в котором поддерживается следующее свойство: для каждой его вершины высота её двух поддеревьев различается не более чем на 1.


АВЛ-деревья названы по первым буквам фамилий их изобретателей, Г. М. Адельсона-Вельского и Е. М. Ландиса, которые впервые предложили использовать АВЛ-деревья в 1962 году.

Высота дерева

Высоту поддерева с корнем [math]x[/math] будем обозначать как [math]h(x)[/math], высоту поддерева [math]T[/math] — как [math]h(T)[/math]

Можно показать, что в AVL-дереве высоты [math]h[/math] не менее, чем [math]F_h = \Omega(\varphi^h)[/math] вершин, где [math]F_h - h[/math]-ое число Фиббоначи, [math]\varphi = \frac{ \sqrt{5}+1}{2}[/math]. Делая замену [math]h = \log_\varphi{n}[/math], получаем, что высота AVL-дерева из n вершин - [math]O(\log{n})[/math].

Балансировка

Определение:
Балансировкой вершины называется операция, которая в случае разницы высот левого и правого поддеревьев [math]|h(L) - h(R)| = 2[/math], изменяет связи предок-потомок в поддереве данной вершины так, чтобы восстановилось свойство дерева [math]|h(L) - h(R)| \le 1[/math], иначе ничего не меняет.

Указанный результат получается вращениями поддерева данной вершины. Используются 4 типа вращений:

Тип вращения Иллюстрация Когда используется
Малое левое вращение AVL RR.GIF [math]h(b) - h(L) = 2[/math] и [math]h(C) \le h(R)[/math].
Большое левое вращение AVL RL.GIF [math]h(b) - h(L) = 2[/math] и [math]h(c) \gt h(R)[/math].
Малое правое вращение AVL LL.GIF [math]h(b) - h(R) = 2[/math] и [math]h(C) \le h(L)[/math].
Большое правое вращение AVL LR.GIF [math]h(b) - h(R) = 2[/math] и [math]h(c) \gt h(L)[/math].

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

Все операции балансировки, очевидно, требуют [math]O(1)[/math] операций.

Добавление вершины

Процесс включения вершины состоит из двух частей:

  1. Прохода по пути поиска, пока не убедимся, что ключа в дереве нет.
  2. "Отступления" назад по пути поиска, вплоть до корня и пересчета высот в каждой вершине на обратном пути. При необходимости осуществляется балансировка.

Так как в процессе добавления вершины мы рассматриваем не более, чем [math] O(h) [/math] вершин дерева, и для каждой запускаем балансировку не более одного раза, то суммарное количество операций при включении новой вершины в дерево составляет [math] O(\log{n}) [/math] операций.

Удаление вершины

Для простоты опишем рекурсивный алгоритм удаления. Если вершина - лист, то удалим её и вызовем балансировку всех её предков в порядке от родителя к корню. Иначе найдём самую близкую по значению вершину и переместим её на место удаляемой вершины, при этом вызвав процедуру её удаления.

В результате указанных действий процедура удаления вызывается не более 3 раз, так как у вершины, удаляемой по 2-му вызову, нет хотя бы одного из поддеревьев. На балансировку суммарно тратится, как и ранее, [math] O(h) [/math] операций. Таким образом, требуемое количество действий — [math] O(\log{n}) [/math].