AA-дерево — различия между версиями
Строка 31: | Строка 31: | ||
[[Файл: Rb2.png]] | [[Файл: 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> | ||
== Балансировка == | == Балансировка == | ||
Строка 44: | Строка 51: | ||
Для балансировки АА-дерева нужны следующие две операции: | Для балансировки АА-дерева нужны следующие две операции: | ||
− | + | === Skew === | |
− | '''function''' skew(T) | + | '''<tex>\mathrm{Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь. |
+ | |||
+ | '''Node''' '''function''' skew(T : '''Node''') | ||
'''if''' T == <tex>\varnothing</tex> | '''if''' T == <tex>\varnothing</tex> | ||
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
Строка 52: | Строка 61: | ||
'''return''' T | '''return''' T | ||
'''else''' '''if''' T.left.level == T.level '''then''' | '''else''' '''if''' T.left.level == T.level '''then''' | ||
− | <font color=green> // Меняем указатель горизонтального левого ребра</font> | + | <font color=green>// Меняем указатель горизонтального левого ребра</font> |
L = T.left | L = T.left | ||
− | T.left | + | T.left = L.right |
− | L.right | + | L.right = T |
'''return''' L | '''return''' L | ||
'''else''' | '''else''' | ||
'''return''' T | '''return''' T | ||
+ | |||
+ | На рисунке ниже представлен пример работы алгоритма. | ||
[[Файл: Skew.png]] | [[Файл: Skew.png]] | ||
− | + | === Split === | |
+ | |||
+ | '''<tex>\mathrm{Split(T)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем. | ||
− | '''function''' split(T) | + | '''Node''' '''function''' split(T : '''Node''') |
'''if''' T == <tex>\varnothing</tex> | '''if''' T == <tex>\varnothing</tex> | ||
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
Строка 72: | Строка 85: | ||
<font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> | <font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> | ||
R = T.right | R = T.right | ||
− | T.right | + | T.right = R.left |
− | R.left | + | R.left = T |
− | R.level | + | R.level = R.level + 1 |
'''return''' R | '''return''' R | ||
'''else''' | '''else''' | ||
'''return''' T | '''return''' T | ||
+ | |||
+ | На рисунке ниже представлен пример работы алгоритма. | ||
[[Файл: Split_rb.png]] | [[Файл: Split_rb.png]] | ||
Строка 83: | Строка 98: | ||
== Операции == | == Операции == | ||
=== Вставка элемента === | === Вставка элемента === | ||
− | + | Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex>. Ниже представлена рекурсивная реализация алгоритма. | |
− | '''function''' insert(X, T) | + | '''Node''' '''function''' insert(X : '''V''', T : '''Node''') |
<font color=green>// X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font> | <font color=green>// X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font> | ||
'''if''' T == <tex>\varnothing</tex> | '''if''' T == <tex>\varnothing</tex> | ||
Строка 91: | Строка 106: | ||
'''return''' node(X, 1, <tex>\varnothing</tex>, <tex>\varnothing</tex>) | '''return''' node(X, 1, <tex>\varnothing</tex>, <tex>\varnothing</tex>) | ||
'''else if''' X < T.value | '''else if''' X < T.value | ||
− | T.left | + | T.left = insert(X, T.left) |
'''else if''' X > T.value | '''else if''' X > T.value | ||
− | T.right | + | T.right = insert(X, T.right) |
<font color=green>// Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта, | <font color=green>// Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта, | ||
возможны различные варианты обработки, в зависимости от решаемой задачи</font> | возможны различные варианты обработки, в зависимости от решаемой задачи</font> | ||
− | T | + | T = skew(T) |
− | T | + | T = split(T) |
'''return''' T | '''return''' T | ||
Строка 105: | Строка 120: | ||
=== Удаление вершины === | === Удаление вершины === | ||
− | + | Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. ''predecessor'') или "преемника" (англ. ''successor''), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма. | |
− | + | Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин. | |
− | '''function''' | + | '''Node''' '''function''' decreaseLevel(T : '''Node''') |
− | + | shouldBe = min(T.left.level, T.right.level) + 1 | |
− | '''if''' | + | '''if''' shouldBe < T.level |
− | T.level | + | T.level = shouldBe |
− | '''if''' | + | '''if''' shouldBe < T.right.level |
− | T.right.level | + | T.right.level = shouldBe |
'''return''' T | '''return''' T | ||
− | '''function''' delete(X, 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> | <font color=green>// X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален</font> | ||
'''if''' T == <tex>\varnothing</tex> | '''if''' T == <tex>\varnothing</tex> | ||
'''return''' T | '''return''' T | ||
'''else if''' X > T.value | '''else if''' X > T.value | ||
− | T.right | + | T.right = delete(X, T.right) |
'''else if''' X < T.value | '''else if''' X < T.value | ||
− | T.left | + | T.left = delete(X, T.left) |
'''else''' | '''else''' | ||
'''if''' leaf(T) '''then''' | '''if''' leaf(T) '''then''' | ||
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
'''else''' '''if''' T.left == <tex>\varnothing</tex> | '''else''' '''if''' T.left == <tex>\varnothing</tex> | ||
− | L | + | L = successor(T) |
− | T.right | + | T.right = delete(value(L), T.right) |
− | T.value | + | T.value = L.value |
'''else''' | '''else''' | ||
L := predecessor(T) | L := predecessor(T) | ||
− | T.left | + | T.left = delete(L.value, T.left)) |
− | T.value | + | T.value = L.value |
<font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" | <font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" | ||
у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | ||
− | T | + | T = decreaseLevel(T) |
− | T | + | T = skew(T) |
− | T.right | + | T.right = skew(T.right) |
'''if not''' nil(T.right) | '''if not''' nil(T.right) | ||
− | right(T.right) | + | right(T.right) = skew(T.right.right) |
'''end if''' | '''end if''' | ||
− | T | + | T = split(T) |
− | T.right | + | T.right = split(T.right) |
'''return''' T | '''return''' T | ||
Строка 153: | Строка 170: | ||
== Эффективность == | == Эффективность == | ||
− | Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева | + | Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева. |
== См. также == | == См. также == |
Версия 02:35, 23 декабря 2016
АA-дерево (англ. AA-Tree) — структура данных, представляющая собой сбалансированное двоичное дерево поиска, которое является разновидностью красно-черного дерева с дополнительными ограничениями.
АA-дерево названо по первым буквам имени и фамилии изобретателя, Арне Андерссона, который впервые предложил данную модификацию красно-черного дерева в 1993 году.
Определение: |
Уровень вершины (англ. Level) — вертикальная высота соответствующей вершины. |
В отличие от красно-черных деревьев, к одной вершине можно присоединить вершину только того же уровня, только одну и только справа (другими словами, красные вершины могут быть добавлены только в качестве правого ребенка). На картинке ниже представлен пример АА-дерева.
На практике в AA-дереве вместо значения цвета для балансировки дерева в вершине хранится информация о ее уровне. На картинки ниже изображен пример того же дерева, построенного только на основе информации об уровне вершин, горизонтальные ребра обозначают связь вершин одного уровня.
Содержание
Свойства
Свойства АА-дерева:
- Уровень каждого листа равен .
- Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
- Уровень каждого правого ребенка равен или один меньше, чем у его родителя.
- Уровень каждого правого внука строго меньше, чем у его прародителя.
- Каждая вершина с уровнем больше имеет двоих детей.
Для поддержки баланса красно-черного дерева необходимо обрабатывать
различных вариантов расположения вершин:В АА-дереве из-за строгих ограничений необходимо обрабатывать только два вида возможных расположений вершин:
Для представления АА-дерева в памяти будем использовать следующую структуру:
struct Node: V value // значение вершины Node left // указатель на левого потомка Node right // указатель на правого потомка Node parent // указатель на предка
Балансировка
Определение: |
Горизонтальное ребро (англ. Horizontal edges) — ребро, соединяющее вершины с одинаковым уровнем. |
В AA-дереве разрешены правые ребра, не идущие подряд, и запрещены все левые горизонтальные ребра.
Эти более жесткие ограничения , аналогичные ограничениям на красно-черных деревьях, приводят к более простой реализации балансировки AA-дерева.
Для балансировки АА-дерева нужны следующие две операции:
Skew
— устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.
Node function skew(T : Node) if T ==return else if T.left == return T else if T.left.level == T.level then // Меняем указатель горизонтального левого ребра L = T.left T.left = L.right L.right = T return L else return T
На рисунке ниже представлен пример работы алгоритма.
Split
— устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.
Node function split(T : Node) if T ==return else if T.right == or T.right.right == return T else if T.level == T.right.right.level // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее R = T.right T.right = R.left R.left = T R.level = R.level + 1 return R else return T
На рисунке ниже представлен пример работы алгоритма.
Операции
Вставка элемента
Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя
и . Ниже представлена рекурсивная реализация алгоритма.Node function insert(X : V, T : Node) // X - вставляемое значение, Т - корень дерева, в который вставляется вершина if T ==Create a new leaf node with X. return node(X, 1, , ) else if X < T.value T.left = insert(X, T.left) else if X > T.value T.right = insert(X, T.right) // Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта, возможны различные варианты обработки, в зависимости от решаемой задачи T = skew(T) T = split(T) return T
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями):
Удаление вершины
Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. predecessor) или "преемника" (англ. successor), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем
, имеющих двух детей, предшественник или преемник будет на уровне , что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.Будем использовать дополнительную функцию
, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.Node function decreaseLevel(T : Node) shouldBe = min(T.left.level, T.right.level) + 1 if shouldBe < T.level T.level = shouldBe if shouldBe < T.right.level T.right.level = shouldBe return T
Чтобы сохранять баланс дерева необходимо делать
, и для каждой вершины.Node function delete(X : V, T : Node) // X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален if T ==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 else if T.left == 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 // Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" у вершин на данном уровне, и затем skew и split все вершины на новом уровне T = decreaseLevel(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-дерева эквивалентна скорости работы красно-черного дерева.
См. также