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> | ||
− | + | <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( | + | '''<tex>\mathrm{Skew(t)}</tex>''' {{---}} устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь. |
− | '''Node | + | '''Node''' skew(t : '''Node''') |
− | '''if''' | + | '''if''' t == <tex>\varnothing</tex> |
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
− | '''else''' '''if''' | + | '''else''' '''if''' t.left == <tex>\varnothing</tex> |
− | '''return''' | + | '''return''' t |
− | '''else''' '''if''' | + | '''else''' '''if''' t.left.level == t.level |
<font color=green>// Меняем указатель горизонтального левого ребра</font> | <font color=green>// Меняем указатель горизонтального левого ребра</font> | ||
− | + | l = t.left | |
− | + | t.left = l.right | |
− | + | l.right = t | |
− | '''return''' | + | '''return''' l |
'''else''' | '''else''' | ||
− | '''return''' | + | '''return''' t |
На рисунке ниже представлен пример работы алгоритма. | На рисунке ниже представлен пример работы алгоритма. | ||
Строка 75: | Строка 82: | ||
=== Split === | === Split === | ||
− | '''<tex>\mathrm{Split( | + | '''<tex>\mathrm{Split(t)}</tex>''' {{---}} устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем. |
− | '''Node | + | '''Node''' split(t : '''Node''') |
− | '''if''' | + | '''if''' t == <tex>\varnothing</tex> |
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
− | '''else''' '''if''' | + | '''else''' '''if''' t.right == <tex>\varnothing</tex> '''or''' t.right.right == <tex>\varnothing</tex> |
− | '''return''' | + | '''return''' t |
− | '''else''' '''if''' | + | '''else''' '''if''' t.level == t.right.right.level |
<font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> | <font color=green> // Существует два правых горизонтальных ребра. Берем центральную вершину, "поднимаем" ее и возвращаем указатель на нее</font> | ||
− | + | r = t.right | |
− | + | t.right = r.left | |
− | + | r.left = t | |
− | + | r.level = r.level + 1 | |
− | '''return''' | + | '''return''' r |
'''else''' | '''else''' | ||
− | '''return''' | + | '''return''' t |
На рисунке ниже представлен пример работы алгоритма. | На рисунке ниже представлен пример работы алгоритма. | ||
Строка 100: | Строка 107: | ||
Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex>. Ниже представлена рекурсивная реализация алгоритма. | Вставка нового элемента происходит как в обычном дереве поиска, только на пути вверх необходимо делать ребалансировку, используя <tex>\mathrm{skew}</tex> и <tex>\mathrm{split}</tex>. Ниже представлена рекурсивная реализация алгоритма. | ||
− | '''Node | + | '''Node''' insert(x : '''V''', t : '''Node''') |
− | <font color=green>// | + | <font color=green>// x {{---}} вставляемое значение, t {{---}} корень дерева, в который вставляется вершина</font> |
− | '''if''' | + | '''if''' t == <tex>\varnothing</tex> |
− | + | '''return''' Node(x, 1, <tex>\varnothing</tex>, <tex>\varnothing</tex>) | |
− | '''return''' | + | '''else if''' x < t.value |
− | '''else if''' | + | t.left = insert(x, t.left) |
− | + | '''else if''' x > t.value | |
− | '''else if''' | + | t.right = insert(x, t.right) |
− | + | <font color=green>// Случай x == t.value не определен. Т.е. вставка не будет иметь никакого эффекта, | |
− | <font color=green>// Случай | ||
возможны различные варианты обработки, в зависимости от решаемой задачи</font> | возможны различные варианты обработки, в зависимости от решаемой задачи</font> | ||
− | + | t = skew(t) | |
− | + | t = split(t) | |
− | '''return''' | + | '''return''' t |
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями): | Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями): | ||
Строка 120: | Строка 126: | ||
=== Удаление вершины === | === Удаление вершины === | ||
− | Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего | + | Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего «предшественника» или «преемника», в зависимости от реализации. «Предшественник» находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, «преемник» может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем <tex>1</tex>, имеющих двух детей, предшественник или преемник будет на уровне <tex>1</tex>, что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма. |
Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин. | Будем использовать дополнительную функцию <tex>\mathrm{decreaseLevel}</tex>, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин. | ||
− | '''Node | + | '''Node''' decreaseLevel(t : '''Node''') |
− | shouldBe = min( | + | shouldBe = min(t.left.level, t.right.level) + 1 |
− | '''if''' shouldBe < | + | '''if''' shouldBe < t.level |
− | + | t.level = shouldBe | |
− | '''if''' shouldBe < | + | '''if''' shouldBe < t.right.level |
− | + | t.right.level = shouldBe | |
− | '''return''' | + | '''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 | + | '''Node''' delete(x : '''V''', t : '''Node''') |
− | <font color=green>// | + | <font color=green>// x {{---}} удаляемый элемент, t {{---}} корень дерева, из которого он должен быть удален</font> |
− | '''if''' | + | '''if''' t == <tex>\varnothing</tex> |
− | '''return''' | + | '''return''' t |
− | '''else if''' | + | '''else if''' x > t.value |
− | + | t.right = delete(x, t.right) | |
− | '''else if''' | + | '''else if''' x < t.value |
− | + | t.left = delete(x, t.left) | |
'''else''' | '''else''' | ||
− | '''if''' leaf( | + | '''if''' leaf(t) |
'''return''' <tex>\varnothing</tex> | '''return''' <tex>\varnothing</tex> | ||
− | '''else''' '''if''' | + | '''else''' '''if''' t.left == <tex>\varnothing</tex> |
− | + | l = successor(t) | |
− | + | t.right = delete(value(l), t.right) | |
− | + | t.value = l.value | |
'''else''' | '''else''' | ||
− | + | l := predecessor(t) | |
− | + | t.left = delete(l.value, t.left)) | |
− | + | t.value = l.value | |
<font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" | <font color=green>// Сбалансируем дерево. Если необходимо, уменьшим поля "уровень" | ||
у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | у вершин на данном уровне, и затем skew и split все вершины на новом уровне</font> | ||
− | + | t = decreaseLevel(t) | |
− | + | t = skew(t) | |
− | + | t.right = skew(t.right) | |
− | '''if not''' | + | '''if not''' t.right == <tex>\varnothing</tex> |
− | right( | + | right(t.right) = skew(t.right.right) |
'''end if''' | '''end if''' | ||
− | + | t = split(t) | |
− | + | t.right = split(t.right) | |
− | '''return''' | + | '''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) — вертикальная высота соответствующей вершины. |
В отличие от красно-черных деревьев, к одной вершине можно присоединить вершину только того же уровня, только одну и только справа (другими словами, красные вершины могут быть добавлены только в качестве правого ребенка). На картинке ниже представлен пример АА-дерева.
На практике в AA-дереве вместо значения цвета для балансировки дерева в вершине хранится информация о ее уровне. На картинки ниже изображен пример того же дерева, построенного только на основе информации об уровне вершин, горизонтальные ребра обозначают связь вершин одного уровня.
Свойства АА-дерева:
- Уровень каждого листа равен .
- Уровень каждого левого ребенка ровно на один меньше, чем у его родителя.
- Уровень каждого правого ребенка равен или на один меньше, чем у его родителя.
- Уровень каждого правого внука строго меньше, чем у его прародителя.
- Каждая вершина с уровнем больше имеет двоих детей.
Для поддержки баланса красно-черного дерева необходимо обрабатывать
различных вариантов расположения вершин:В АА-дереве из-за строгих ограничений необходимо обрабатывать только два вида возможных расположений вершин:
Для представления АА-дерева в памяти будем использовать следующую структуру:
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
— устранение левого горизонтального ребра. Делаем правое вращение, чтобы заменить поддерево, содержащее левую горизонтальную связь, на поддерево, содержащее разрешенную правую горизонтальную связь.
Node skew(t : Node) if t ==return else if t.left == return t else if t.left.level == t.level // Меняем указатель горизонтального левого ребра l = t.left t.left = l.right l.right = t return l else return t
На рисунке ниже представлен пример работы алгоритма.
Split
— устранение двух последовательных правых горизонтальных ребер. Делаем левое вращение и увеличиваем уровень, чтобы заменить поддерево, содержащее две или более последовательных правильных горизонтальных связи, на вершину, содержащую два поддерева с меньшим уровнем.
Node 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 insert(x : V, t : Node) // x — вставляемое значение, t — корень дерева, в который вставляется вершина if t ==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 == t.value не определен. Т.е. вставка не будет иметь никакого эффекта, возможны различные варианты обработки, в зависимости от решаемой задачи t = skew(t) t = split(t) return t
Пример вставки нового элемента (на рис. уровни разделены горизонтальными линиями):
Удаление вершины
Как и в большинстве сбалансированных бинарных деревьев, удаление внутренней вершины можно заменить на удаление листа, если заменить внутреннюю вершину на ее ближайшего «предшественника» или «преемника», в зависимости от реализации. «Предшественник» находиться в начале последнего левого ребра, после которого идут все правые ребра. По аналогии, «преемник» может быть найден после одного правого ребра и последовательности левых ребер, пока не будет найден указатель на NULL. В силу свойства всех узлов уровня более чем
, имеющих двух детей, предшественник или преемник будет на уровне , что делает их удаление тривиальным. Ниже представлена рекурсивная реализация алгоритма.Будем использовать дополнительную функцию
, она будет обновлять уровень вершины, которую передают в функцию, в зависимости от значения уровня дочерних вершин.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
Чтобы сохранять баланс дерева необходимо делать
, и для каждой вершины.Node delete(x : V, t : Node) // x — удаляемый элемент, t — корень дерева, из которого он должен быть удален 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) 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 t.right == right(t.right) = skew(t.right.right) end if t = split(t) t.right = split(t.right) return t
Пример удаления вершины (на рис. уровни разделены горизонтальными линиями):
Эффективность
Скорость работы AA-дерева эквивалентна скорости работы красно-черного дерева.