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:
 
Для балансировки АА-дерева нужны следующие две операции:
 
Для балансировки АА-дерева нужны следующие две операции:
  
1.'''<tex>\mathrm{Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра.
+
=== 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 := L.right
+
         T.left = L.right
         L.right := T
+
         L.right = T
 
         '''return''' L
 
         '''return''' L
 
     '''else'''
 
     '''else'''
 
         '''return''' T
 
         '''return''' T
 +
 +
На рисунке ниже представлен пример работы алгоритма.
  
 
[[Файл: Skew.png]]
 
[[Файл: Skew.png]]
  
2.'''<tex>\mathrm{Split(T)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер.
+
=== 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 := R.left
+
         T.right = R.left
         R.left := T
+
         R.left = T
         R.level := R.level + 1
+
         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> для каждой вершины.
+
Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <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 := insert(X, T.left)
+
         T.left = insert(X, T.left)
 
     '''else if''' X > T.value
 
     '''else if''' X > T.value
         T.right := insert(X, T.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
  
Строка 105: Строка 120:
  
 
=== Удаление вершины ===
 
=== Удаление вершины ===
Рекурсивная реализация. Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. ''predecessor'') или "преемника" (англ. ''successor''), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным.
+
Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. ''predecessor'') или "преемника" (англ. ''successor''), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.
  
Чтобы сохранять баланс дерева необходимо делать skew, split и корректировку уровня для каждой вершины.
+
Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.
  
  '''function''' decrease_level(T)
+
  '''Node''' '''function''' decreaseLevel(T : '''Node''')
     should_be = min(T.left.level, T.right.level) + 1
+
     shouldBe = min(T.left.level, T.right.level) + 1
     '''if''' should_be < T.level
+
     '''if''' shouldBe < T.level
         T.level := should_be
+
         T.level = shouldBe
         '''if''' should_be < T.right.level
+
         '''if''' shouldBe < T.right.level
             T.right.level := should_be
+
             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 := delete(X, T.right)
+
         T.right = delete(X, T.right)
 
     '''else if''' X < T.value
 
     '''else if''' X < T.value
         T.left := delete(X, 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 := successor(T)
+
             L = successor(T)
             T.right := delete(value(L), T.right)
+
             T.right = delete(value(L), T.right)
             T.value := L.value
+
             T.value = L.value
 
         '''else'''
 
         '''else'''
 
             L := predecessor(T)
 
             L := predecessor(T)
             T.left := delete(L.value, T.left))
+
             T.left = delete(L.value, T.left))
             T.value := L.value
+
             T.value = L.value
 
     <font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень"  
 
     <font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень"  
 
     у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font>
 
     у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font>
     T := decrease_level(T)
+
     T = decreaseLevel(T)
     T := skew(T)
+
     T = skew(T)
     T.right := skew(T.right)
+
     T.right = skew(T.right)
 
     '''if not''' nil(T.right)
 
     '''if not''' nil(T.right)
         right(T.right) := skew(T.right.right)
+
         right(T.right) = skew(T.right.right)
 
     '''end if'''
 
     '''end if'''
     T := split(T)
+
     T = split(T)
     T.right := split(T.right)
+
     T.right = split(T.right)
 
     '''return''' T
 
     '''return''' T
  
Строка 153: Строка 170:
  
 
== Эффективность ==
 
== Эффективность ==
Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева. В среднем более простые алгоритмы на AA-дерева выполняются быстрее, но в красно-черном дереве делается меньше поворотов, что уравновешивает асимптотику.
+
Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева.
  
 
== См. также ==
 
== См. также ==

Версия 02:35, 23 декабря 2016

АA-дерево (англ. AA-Tree) — структура данных, представляющая собой сбалансированное двоичное дерево поиска, которое является разновидностью красно-черного дерева с дополнительными ограничениями.

АA-дерево названо по первым буквам имени и фамилии изобретателя, Арне Андерссона, который впервые предложил данную модификацию красно-черного дерева в 1993 году.


Определение:
Уровень вершины (англ. Level) — вертикальная высота соответствующей вершины.


В отличие от красно-черных деревьев, к одной вершине можно присоединить вершину только того же уровня, только одну и только справа (другими словами, красные вершины могут быть добавлены только в качестве правого ребенка). На картинке ниже представлен пример АА-дерева.

Exaa.PNG

На практике в AA-дереве вместо значения цвета для балансировки дерева в вершине хранится информация о ее уровне. На картинки ниже изображен пример того же дерева, построенного только на основе информации об уровне вершин, горизонтальные ребра обозначают связь вершин одного уровня.

Exaa2.PNG

Свойства

Свойства АА-дерева:

  • Уровень каждого листа равен [math]1[/math].
  • Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
  • Уровень каждого правого ребенка равен или один меньше, чем у его родителя.
  • Уровень каждого правого внука строго меньше, чем у его прародителя.
  • Каждая вершина с уровнем больше [math]1[/math] имеет двоих детей.

Для поддержки баланса красно-черного дерева необходимо обрабатывать [math]7[/math] различных вариантов расположения вершин:

Rb3.png

В АА-дереве из-за строгих ограничений необходимо обрабатывать только два вида возможных расположений вершин:

Rb2.png

Для представления АА-дерева в памяти будем использовать следующую структуру:

struct Node:
  V value                  // значение вершины
  Node left                // указатель на левого потомка
  Node right               // указатель на правого потомка
  Node parent              // указатель на предка

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

Определение:
Горизонтальное ребро (англ. Horizontal edges) — ребро, соединяющее вершины с одинаковым уровнем.


В AA-дереве разрешены правые ребра, не идущие подряд, и запрещены все левые горизонтальные ребра. Эти более жесткие ограничения , аналогичные ограничениям на красно-черных деревьях, приводят к более простой реализации балансировки AA-дерева.


Для балансировки АА-дерева нужны следующие две операции:

Skew

[math]\mathrm{Skew(T)}[/math] — устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.

Node function skew(T : Node)
   if T == [math]\varnothing[/math]
       return [math]\varnothing[/math]
   else if T.left == [math]\varnothing[/math]
       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

На рисунке ниже представлен пример работы алгоритма.

Skew.png

Split

[math]\mathrm{Split(T)}[/math] — устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.

Node function split(T : Node)
   if T == [math]\varnothing[/math]
       return [math]\varnothing[/math]
   else if T.right == [math]\varnothing[/math] or  T.right.right == [math]\varnothing[/math]
       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

На рисунке ниже представлен пример работы алгоритма.

Split rb.png

Операции

Вставка элемента

Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя [math]\mathrm{skew}[/math] и [math]\mathrm{split}[/math]. Ниже представлена рекурсивная реализация алгоритма.

Node function insert(X : V, T : Node)
   // X - вставляемое значение, Т - корень дерева, в который вставляется вершина
   if T == [math]\varnothing[/math]
       Create a new leaf node with X.
       return node(X, 1, [math]\varnothing[/math], [math]\varnothing[/math])
   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

Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями):

Ex insert.png

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

Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. predecessor) или "преемника" (англ. successor), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем [math]1[/math], имеющих двух детей, предшественник или преемник будет на уровне [math]1[/math], что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.

Будем использовать дополнительную функцию [math]\mathrm{decreaseLevel}[/math], она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.

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

Чтобы сохранять баланс дерева необходимо делать [math]\mathrm{skew}[/math], [math]\mathrm{split}[/math] и [math]\mathrm{decreaseLevel}[/math] для каждой вершины.

Node function delete(X : V, T : Node)
   // X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален
   if T == [math]\varnothing[/math]
       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 [math]\varnothing[/math]
       else if T.left == [math]\varnothing[/math]
           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

Пример удаления вершины (на рис. уровни разделены горизонтальными линиями):

Exdelete.png

Эффективность

Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева.

См. также


Источники информации