AA-дерево — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 2: Строка 2:
  
 
АA-дерево названо по первым буквам имени и фамилии изобретателя, Арне Андерссона, который впервые предложил данную модификацию [[Красно-черное дерево|красно-черного дерева]] в 1993 году.
 
АA-дерево названо по первым буквам имени и фамилии изобретателя, Арне Андерссона, который впервые предложил данную модификацию [[Красно-черное дерево|красно-черного дерева]] в 1993 году.
 +
 +
== Свойства ==
  
 
{{Определение
 
{{Определение
Строка 16: Строка 18:
 
[[Файл: Exaa2.PNG]]
 
[[Файл: Exaa2.PNG]]
  
== Свойства ==
 
 
Свойства АА-дерева:
 
Свойства АА-дерева:
 
*Уровень каждого листа равен <tex>1</tex>.
 
*Уровень каждого листа равен <tex>1</tex>.
 
*Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
 
*Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
*Уровень каждого правого ребенка равен или один меньше, чем у его родителя.
+
*Уровень каждого правого ребенка равен или на один меньше, чем у его родителя.
 
*Уровень каждого правого внука строго меньше, чем у его прародителя.
 
*Уровень каждого правого внука строго меньше, чем у его прародителя.
 
*Каждая вершина с уровнем больше <tex>1</tex> имеет двоих детей.
 
*Каждая вершина с уровнем больше <tex>1</tex> имеет двоих детей.
Строка 35: Строка 36:
 
  '''struct''' Node:
 
  '''struct''' Node:
 
   '''V''' value                  <font color="green">// значение вершины</font>
 
   '''V''' value                  <font color="green">// значение вершины</font>
 +
  '''Node''' level              <font color="green">// указатель на предка</font>
 
   '''Node''' left                <font color="green">// указатель на левого потомка</font>
 
   '''Node''' left                <font color="green">// указатель на левого потомка</font>
 
   '''Node''' right              <font color="green">// указатель на правого потомка</font>
 
   '''Node''' right              <font color="green">// указатель на правого потомка</font>
   '''Node''' parent              <font color="green">// указатель на предка</font>
+
   <font color=green>// Конструктор вершины (создаем экземпляр структуры с полученными значениями)</font>
 +
  '''Node''' (newValue : '''V''', curLevel : '''V''', leftNode : '''Node''', rightNode : '''Node''')
 +
      value = newValue
 +
      level = curLevel
 +
      left = leftNode
 +
      right = rightNode
  
 
== Балансировка ==
 
== Балансировка ==
Строка 53: Строка 60:
 
=== Skew ===
 
=== Skew ===
  
'''<tex>\mathrm{Skew(T)}</tex>''' {{---}} устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.
+
'''<tex>\mathrm{Skew(t)}</tex>''' {{---}} устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.
  
  '''Node''' '''function''' skew(T : '''Node''')
+
  '''Node''' skew(t : '''Node''')
     '''if''' T == <tex>\varnothing</tex>
+
     '''if''' t == <tex>\varnothing</tex>
 
         '''return''' <tex>\varnothing</tex>
 
         '''return''' <tex>\varnothing</tex>
     '''else''' '''if''' T.left == <tex>\varnothing</tex>
+
     '''else''' '''if''' t.left == <tex>\varnothing</tex>
         '''return''' T
+
         '''return''' t
     '''else''' '''if''' T.left.level == T.level '''then'''
+
     '''else''' '''if''' t.left.level == t.level
 
         <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
  
 
На рисунке ниже представлен пример работы алгоритма.
 
На рисунке ниже представлен пример работы алгоритма.
Строка 75: Строка 82:
 
=== Split ===
 
=== Split ===
  
'''<tex>\mathrm{Split(T)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.  
+
'''<tex>\mathrm{Split(t)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.  
  
  '''Node''' '''function''' split(T : '''Node''')
+
  '''Node''' split(t : '''Node''')
     '''if''' T == <tex>\varnothing</tex>
+
     '''if''' t == <tex>\varnothing</tex>
 
         '''return''' <tex>\varnothing</tex>
 
         '''return''' <tex>\varnothing</tex>
     '''else''' '''if''' T.right == <tex>\varnothing</tex> '''or'''  T.right.right == <tex>\varnothing</tex>
+
     '''else''' '''if''' t.right == <tex>\varnothing</tex> '''or'''  t.right.right == <tex>\varnothing</tex>
         '''return''' T
+
         '''return''' t
     '''else''' '''if''' T.level == T.right.right.level
+
     '''else''' '''if''' t.level == t.right.right.level
 
         <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
  
 
На рисунке ниже представлен пример работы алгоритма.
 
На рисунке ниже представлен пример работы алгоритма.
Строка 100: Строка 107:
 
Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex>. Ниже представлена рекурсивная реализация алгоритма.
 
Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex>. Ниже представлена рекурсивная реализация алгоритма.
  
  '''Node''' '''function''' insert(X : '''V''', T : '''Node''')
+
  '''Node''' insert(x : '''V''', t : '''Node''')
     <font color=green>// X - вставляемое значение, Т - корень дерева, в который вставляется вершина</font>
+
     <font color=green>// x {{---}} вставляемое значение, t {{---}} корень дерева, в который вставляется вершина</font>
     '''if''' T == <tex>\varnothing</tex>
+
     '''if''' t == <tex>\varnothing</tex>
        Create a new leaf node with X.
+
         '''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 == t.value не определен. Т.е. вставка не будет иметь никакого эффекта,
     <font color=green>// Случай X == value(T) не определен. Т.е. вставка не будет иметь никакого эффекта,
 
 
     возможны различные варианты обработки, в зависимости от решаемой задачи</font>
 
     возможны различные варианты обработки, в зависимости от решаемой задачи</font>
     T = skew(T)
+
     t = skew(t)
     T = split(T)
+
     t = split(t)
     '''return''' T
+
     '''return''' t
  
 
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями):
 
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями):
Строка 120: Строка 126:
  
 
=== Удаление вершины ===
 
=== Удаление вершины ===
Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего "предшественника" (англ. ''predecessor'') или "преемника" (англ. ''successor''), в зависимости от реализации. "Предшественник" находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, "преемник" может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.
+
Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего «предшественника» или «преемника», в зависимости от реализации. «Предшественник» находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, «преемник» может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.
  
 
Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.
 
Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.
  
  '''Node''' '''function''' decreaseLevel(T : '''Node''')
+
  '''Node''' decreaseLevel(t : '''Node''')
     shouldBe = min(T.left.level, T.right.level) + 1
+
     shouldBe = min(t.left.level, t.right.level) + 1
     '''if''' shouldBe < T.level
+
     '''if''' shouldBe < t.level
         T.level = shouldBe
+
         t.level = shouldBe
         '''if''' shouldBe < T.right.level
+
         '''if''' shouldBe < t.right.level
             T.right.level = shouldBe
+
             t.right.level = shouldBe
     '''return''' T
+
     '''return''' t
  
 
Чтобы сохранять баланс дерева необходимо делать <tex>\mathrm{skew}</tex>, <tex>\mathrm{split}</tex> и <tex>\mathrm{decreaseLevel}</tex> для каждой вершины.
 
Чтобы сохранять баланс дерева необходимо делать <tex>\mathrm{skew}</tex>, <tex>\mathrm{split}</tex> и <tex>\mathrm{decreaseLevel}</tex> для каждой вершины.
  
  '''Node''' '''function''' delete(X : '''V''', T : '''Node''')
+
  '''Node''' delete(x : '''V''', t : '''Node''')
     <font color=green>// X - удаляемый элемент, Т - корень дерева, из которого он должен быть удален</font>
+
     <font color=green>// x {{---}} удаляемый элемент, t {{---}} корень дерева, из которого он должен быть удален</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)
 
             '''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 = decreaseLevel(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''' t.right == <tex>\varnothing</tex>
         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
  
 
Пример удаления вершины (на рис. уровни разделены горизонтальными линиями):
 
Пример удаления вершины (на рис. уровни разделены горизонтальными линиями):
Строка 178: Строка 184:
 
* [[Декартово дерево]]
 
* [[Декартово дерево]]
 
* [[Красно-черное дерево]]
 
* [[Красно-черное дерево]]
 
  
 
== Источники информации ==
 
== Источники информации ==
* [http://user.it.uu.se/~arnea/ps/simp.pdf Balanced searched trees made simple]
+
* [http://user.it.uu.se/~arnea/ps/simp.pdf Uppsala University {{---}} Balanced searched trees made simple]  
* [https://en.wikipedia.org/wiki/AA_tree AA - tree]
+
* [https://en.wikipedia.org/wiki/AA_tree Википедия {{---}} AA - tree]
* [https://habrahabr.ru/post/110212/ AA-Tree или простое бинарное дерево]
+
* [https://habrahabr.ru/post/110212/ Хабрахабр {{---}} AA-Tree или простое бинарное дерево]
 
 
 
 
  
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Структуры данных]]
 
[[Категория:Структуры данных]]
 
[[Категория:Деревья поиска]]
 
[[Категория:Деревья поиска]]

Версия 23:49, 24 декабря 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 level               // указатель на предка
  Node left                // указатель на левого потомка
  Node right               // указатель на правого потомка
  // Конструктор вершины (создаем экземпляр структуры с полученными значениями)
  Node (newValue : V, curLevel : V, leftNode : Node, rightNode : Node)
     value = newValue
     level = curLevel
     left = leftNode
     right = rightNode

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

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


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


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

Skew

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

Node 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
       // Меняем указатель горизонтального левого ребра
       l = t.left
       t.left = l.right
       l.right = t
       return l
   else
       return t

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

Skew.png

Split

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

Node 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 insert(x : V, t : Node)
   // x — вставляемое значение, t — корень дерева, в который вставляется вершина
   if t == [math]\varnothing[/math]
       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 == t.value не определен. Т.е. вставка не будет иметь никакого эффекта,
   возможны различные варианты обработки, в зависимости от решаемой задачи
   t = skew(t)
   t = split(t)
   return t

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

Ex insert.png

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

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

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

Node 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 delete(x : V, t : Node)
   // x — удаляемый элемент, t — корень дерева, из которого он должен быть удален
   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)
           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 t.right == [math]\varnothing[/math]
       right(t.right) = skew(t.right.right)
   end if
   t = split(t)
   t.right = split(t.right)
   return t

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

Exdelete.png

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

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

См. также

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