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-дерева выполняются быстрее, но в красно-черном дереве делается меньше поворотов, что уравновешивает асимптотику.
См. также






