Изменения

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

AA-дерево

1755 байт добавлено, 02:35, 23 декабря 2016
Нет описания правки
[[Файл: Rb2.png]]
 
Для представления АА-дерева в памяти будем использовать следующую структуру:
'''struct''' Node:
'''V''' value <font color="green">// значение вершины</font>
'''Node''' left <font color="green">// указатель на левого потомка</font>
'''Node''' right <font color="green">// указатель на правого потомка</font>
'''Node''' parent <font color="green">// указатель на предка</font>
== Балансировка ==
Для балансировки АА-дерева нужны следующие две операции:
1.'''<tex>\mathrm{=== Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра.===
'''<tex>\mathrm{Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.  '''Node''' '''function''' skew(T: '''Node''')
'''if''' T == <tex>\varnothing</tex>
'''return''' <tex>\varnothing</tex>
'''return''' T
'''else''' '''if''' T.left.level == T.level '''then'''
<font color=green> // Меняем указатель горизонтального левого ребра</font>
L = T.left
T.left := L.right L.right := T
'''return''' L
'''else'''
'''return''' T
 
На рисунке ниже представлен пример работы алгоритма.
[[Файл: Skew.png]]
2.=== Split === '''<tex>\mathrm{Split(T)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.
'''Node''' '''function''' split(T: '''Node''')
'''if''' T == <tex>\varnothing</tex>
'''return''' <tex>\varnothing</tex>
<font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font>
R = T.right
T.right := R.left R.left := T R.level := R.level + 1
'''return''' R
'''else'''
'''return''' T
 
На рисунке ниже представлен пример работы алгоритма.
[[Файл: Split_rb.png]]
== Операции ==
=== Вставка элемента ===
Рекурсивная реализация. Спускаемся от корня вниз по деревуВставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, сравнивая ключи; вставляем новую вершину; выходя из рекурсии и выполняем балансировку: используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex> для каждой вершины. Ниже представлена рекурсивная реализация алгоритма.
'''Node''' '''function''' insert(X: '''V''', T: '''Node''')
<font color=green>// X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font>
'''if''' T == <tex>\varnothing</tex>
'''return''' node(X, 1, <tex>\varnothing</tex>, <tex>\varnothing</tex>)
'''else if''' X < T.value
T.left := insert(X, T.left)
'''else if''' X > T.value
T.right := insert(X, T.right)
<font color=green>// Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта,
возможны различные варианты обработки, в зависимости от решаемой задачи</font>
T := skew(T) T := split(T)
'''return''' T
=== Удаление вершины ===
Рекурсивная реализация. Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. ''predecessor'') или "преемника" (англ. ''successor''), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.
Чтобы сохранять баланс дерева необходимо делать skewБудем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, split и корректировку она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня для каждой вершиныдочерних вершин.
'''Node''' '''function''' decrease_leveldecreaseLevel(T: '''Node''') should_be shouldBe = min(T.left.level, T.right.level) + 1 '''if''' should_be shouldBe < T.level T.level := should_beshouldBe '''if''' should_be shouldBe < T.right.level T.right.level := should_beshouldBe
'''return''' T
Чтобы сохранять баланс дерева необходимо делать <tex>\mathrm{skew}</tex>, <tex>\mathrm{split}</tex> и <tex>\mathrm{decreaseLevel}</tex> для каждой вершины.  '''Node''' '''function''' delete(X: '''V''', T: '''Node''')
<font color=green>// X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален</font>
'''if''' T == <tex>\varnothing</tex>
'''return''' T
'''else if''' X > T.value
T.right := delete(X, T.right)
'''else if''' X < T.value
T.left := delete(X, T.left)
'''else'''
'''if''' leaf(T) '''then'''
'''return''' <tex>\varnothing</tex>
'''else''' '''if''' T.left == <tex>\varnothing</tex>
L := successor(T) T.right := delete(value(L), T.right) T.value := L.value
'''else'''
L := predecessor(T)
T.left := delete(L.value, T.left)) T.value := L.value
<font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень"
у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font>
T := decrease_leveldecreaseLevel(T) T := skew(T) T.right := skew(T.right)
'''if not''' nil(T.right)
right(T.right) := skew(T.right.right)
'''end if'''
T := split(T) T.right := split(T.right)
'''return''' T
== Эффективность ==
Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева. В среднем более простые алгоритмы на AA-дерева выполняются быстрее, но в красно-черном дереве делается меньше поворотов, что уравновешивает асимптотику.
== См. также ==
302
правки

Навигация