AA-дерево — различия между версиями
(→Удаление вершины) |
|||
Строка 44: | Строка 44: | ||
Для балансировки АА-дерева нужны следующие две операции: | Для балансировки АА-дерева нужны следующие две операции: | ||
− | 1.'''Skew(T)''' {{---}} устранение левого горизонтального ребра. | + | 1.'''<tex>\mathrm{Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра. |
'''function''' skew(T) | '''function''' skew(T) | ||
− | '''if''' T == | + | '''if''' T == <tex>\varnothing</tex> |
− | '''return''' | + | '''return''' <tex>\varnothing</tex> |
− | '''else''' '''if''' left | + | '''else''' '''if''' T.left == <tex>\varnothing</tex> |
'''return''' T | '''return''' T | ||
− | '''else''' '''if''' level | + | '''else''' '''if''' T.left.level == T.level '''then''' |
− | <font color=green> //Меняем указатель горизонтального левого ребра</font> | + | <font color=green> // Меняем указатель горизонтального левого ребра</font> |
− | L = left | + | L = T.left |
− | left | + | T.left := L.right |
− | right | + | L.right := T |
'''return''' L | '''return''' L | ||
'''else''' | '''else''' | ||
'''return''' T | '''return''' T | ||
− | |||
− | |||
[[Файл: Skew.png]] | [[Файл: Skew.png]] | ||
− | 2.'''Split(T)''' {{---}} устранение двух последовательных правых горизонтальных ребер. | + | 2.'''<tex>\mathrm{Split(T)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. |
'''function''' split(T) | '''function''' split(T) | ||
− | '''if''' | + | '''if''' T == <tex>\varnothing</tex> |
− | '''return''' | + | '''return''' <tex>\varnothing</tex> |
− | '''else''' '''if''' | + | '''else''' '''if''' T.right == <tex>\varnothing</tex> '''or''' T.right.right == <tex>\varnothing</tex> |
'''return''' T | '''return''' T | ||
− | '''else''' '''if''' level | + | '''else''' '''if''' T.level == T.right.right.level |
− | <font color=green> //Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> | + | <font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> |
− | R = right | + | R = T.right |
− | right | + | T.right := R.left |
− | left | + | R.left := T |
− | level | + | R.level := R.level + 1 |
'''return''' R | '''return''' R | ||
'''else''' | '''else''' | ||
'''return''' T | '''return''' T | ||
− | |||
− | |||
[[Файл: Split_rb.png]] | [[Файл: Split_rb.png]] | ||
Строка 87: | Строка 83: | ||
== Операции == | == Операции == | ||
=== Вставка элемента === | === Вставка элемента === | ||
− | Рекурсивная реализация. Спускаемся от корня вниз по дереву, сравнивая ключи; вставляем новую вершину; выходя из рекурсии и выполняем балансировку: skew и split для каждой вершины. | + | Рекурсивная реализация. Спускаемся от корня вниз по дереву, сравнивая ключи; вставляем новую вершину; выходя из рекурсии и выполняем балансировку: <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex> для каждой вершины. |
'''function''' insert(X, T) | '''function''' insert(X, T) | ||
− | <font color=green>//X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font> | + | <font color=green>// X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font> |
− | + | '''if''' T == <tex>\varnothing</tex> | |
− | '''if''' | ||
Create a new leaf node with X. | Create a new leaf node with X. | ||
− | '''return''' node(X, 1, | + | '''return''' node(X, 1, <tex>\varnothing</tex>, <tex>\varnothing</tex>) |
− | '''else if''' X < value | + | '''else if''' X < T.value |
− | left | + | T.left := insert(X, T.left) |
− | '''else if''' X > value | + | '''else if''' X > T.value |
− | right | + | T.right := insert(X, T.right) |
− | + | <font color=green>// Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта, | |
− | <font color=green>//Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта, возможны различные варианты обработки, в зависимости от решаемой задачи</font> | + | возможны различные варианты обработки, в зависимости от решаемой задачи</font> |
− | |||
T := skew(T) | T := skew(T) | ||
T := split(T) | T := split(T) | ||
− | |||
'''return''' T | '''return''' T | ||
− | |||
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями): | Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями): | ||
Строка 116: | Строка 108: | ||
Чтобы сохранять баланс дерева необходимо делать skew, split и корректировку уровня для каждой вершины. | Чтобы сохранять баланс дерева необходимо делать skew, split и корректировку уровня для каждой вершины. | ||
+ | |||
+ | '''function''' decrease_level(T) | ||
+ | should_be = min(T.left.level, T.right.level) + 1 | ||
+ | '''if''' should_be < T.level | ||
+ | T.level := should_be | ||
+ | '''if''' should_be < T.right.level | ||
+ | T.right.level := should_be | ||
+ | '''return''' T | ||
'''function''' delete(X, T) | '''function''' delete(X, T) | ||
− | <font color=green>//X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален</font> | + | <font color=green>// X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален</font> |
− | + | '''if''' T == <tex>\varnothing</tex> | |
− | '''if''' | ||
'''return''' T | '''return''' T | ||
− | '''else if''' X > value | + | '''else if''' X > T.value |
− | right | + | T.right := delete(X, T.right) |
− | '''else if''' X < value | + | '''else if''' X < T.value |
− | left | + | T.left := delete(X, T.left) |
'''else''' | '''else''' | ||
'''if''' leaf(T) '''then''' | '''if''' leaf(T) '''then''' | ||
− | '''return''' | + | '''return''' <tex>\varnothing</tex> |
− | '''else''' '''if''' | + | '''else''' '''if''' T.left == <tex>\varnothing</tex> |
L := successor(T) | L := successor(T) | ||
− | right | + | T.right := delete(value(L), T.right) |
− | value | + | T.value := L.value |
'''else''' | '''else''' | ||
L := predecessor(T) | L := predecessor(T) | ||
− | left | + | T.left := delete(L.value, T.left)) |
− | value | + | T.value := L.value |
− | + | <font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" | |
− | + | у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | |
− | |||
− | <font color=green>//Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | ||
T := decrease_level(T) | T := decrease_level(T) | ||
T := skew(T) | T := skew(T) | ||
− | right | + | T.right := skew(T.right) |
− | '''if not''' nil(right | + | '''if not''' nil(T.right) |
− | right(right | + | right(T.right) := skew(T.right.right) |
'''end if''' | '''end if''' | ||
T := split(T) | T := split(T) | ||
− | right | + | T.right := split(T.right) |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
'''return''' T | '''return''' T | ||
− | |||
Пример удаления вершины (на рис. уровни разделены горизонтальными линиями): | Пример удаления вершины (на рис. уровни разделены горизонтальными линиями): |
Версия 01:08, 23 декабря 2016
АA-дерево (англ. AA-Tree) — структура данных, представляющая собой сбалансированное двоичное дерево поиска, которое является разновидностью красно-черного дерева с дополнительными ограничениями.
АA-дерево названо по первым буквам имени и фамилии изобретателя, Арне Андерссона, который впервые предложил данную модификацию красно-черного дерева в 1993 году.
Определение: |
Уровень вершины (англ. Level) — вертикальная высота соответствующей вершины. |
В отличие от красно-черных деревьев, к одной вершине можно присоединить вершину только того же уровня, только одну и только справа (другими словами, красные вершины могут быть добавлены только в качестве правого ребенка). На картинке ниже представлен пример АА-дерева.
На практике в AA-дереве вместо значения цвета для балансировки дерева в вершине хранится информация о ее уровне. На картинки ниже изображен пример того же дерева, построенного только на основе информации об уровне вершин, горизонтальные ребра обозначают связь вершин одного уровня.
Содержание
Свойства
Свойства АА-дерева:
- Уровень каждого листа равен .
- Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
- Уровень каждого правого ребенка равен или один меньше, чем у его родителя.
- Уровень каждого правого внука строго меньше, чем у его прародителя.
- Каждая вершина с уровнем больше имеет двоих детей.
Для поддержки баланса красно-черного дерева необходимо обрабатывать
различных вариантов расположения вершин:В АА-дереве из-за строгих ограничений необходимо обрабатывать только два вида возможных расположений вершин:
Балансировка
Определение: |
Горизонтальное ребро (англ. Horizontal edges) — ребро, соединяющее вершины с одинаковым уровнем. |
В AA-дереве разрешены правые ребра, не идущие подряд, и запрещены все левые горизонтальные ребра.
Эти более жесткие ограничения , аналогичные ограничениям на красно-черных деревьях, приводят к более простой реализации балансировки AA-дерева.
Для балансировки АА-дерева нужны следующие две операции:
1.
— устранение левого горизонтального ребра.function skew(T) 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
2.
— устранение двух последовательных правых горизонтальных ребер.function split(T) 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
Операции
Вставка элемента
Рекурсивная реализация. Спускаемся от корня вниз по дереву, сравнивая ключи; вставляем новую вершину; выходя из рекурсии и выполняем балансировку:
и для каждой вершины.function insert(X, T) // 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. В силу свойства всех узлов уровня более чем
, имеющих двух детей, предшественник или преемник будет на уровне , что делает их удаление тривиальным.Чтобы сохранять баланс дерева необходимо делать skew, split и корректировку уровня для каждой вершины.
function decrease_level(T) should_be = min(T.left.level, T.right.level) + 1 if should_be < T.level T.level := should_be if should_be < T.right.level T.right.level := should_be return T
function delete(X, T) // 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 := decrease_level(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-дерева выполняются быстрее, но в красно-черном дереве делается меньше поворотов, что уравновешивает асимптотику.
См. также