Изменения

Перейти к: навигация, поиск

Левосторонняя куча

402 байта добавлено, 10:33, 27 мая 2013
Нет описания правки
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.
{{Определение
|definition='''Левосторонняя куча (leftist heap)''' {{---}} двоичное левосторонее [[Дерево, эквивалентные определения|дерево]] (не обязательно сбалансированное), но с соблюдением [[Двоичная куча#Определение|порядка кучи]] (heap order).}}
{{Лемма
|id=lemma1
Левосторонняя куча накладывает на двоичное дерево дополнительное условие.
Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее:
{{Определение
|definition=Условие левосторонней кучи. Пусть <tex>dist(u)</tex> {{---}} расстояние от вершины <tex>u</tex> до ближайшей свободной позиции в ее поддереве. У пустых позиций <tex>dist = 0</tex>. Тогда потребуем для любой вершины <tex>u : dist(u.L)\ge dict(u.R)</tex>.}}
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за <tex>O(1)</tex> поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
===merge===
Слияние двух куч.
<code> '''merge'''(x,y) //x,y {{---}} корни двух деревьев '''if''' x == NULL <tex> \varnothing </tex>: '''return''' y '''if''' y == NULL <tex> \varnothing </tex>: '''return''' x '''if''' y.key < x.key :
x <tex>\leftrightarrow</tex> y
//Воспользуемся тем, что куча левосторонняя. Правая ветка {{---}} самая короткая и не длиннее логарифма. //Пойдем направо и сольем правое поддерево с у. x.R <tex>\leftarrow</tex> = '''merge'''(x.R, y) //Могло возникнуть нарушение левостороннести кучи.
'''if''' dist(x.R) > dist(x.L):
x.L <tex>\leftrightarrow</tex> x.R
'''update''' dist(x) //dist(x) = min(dist(x.L), dist(x.R)) + 1// пересчитаем расстояние до ближайшей свободной позиции '''return''' x; //Каждый раз идет в идем из уже существующей вершине вершины только в правое поддерево {{---}} не более логарифма вызовов (по лемме).</code>
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
===delete===
Аналогично удаляется любой элемент {{---}} на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя {{---}} на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью <tex>decrease key</tex>. Уменьшаем ключ до <tex>-\infty</tex>, затем извлекаем минимальное значение. ===decKeydecreaseKey===
{{Лемма
|id=lemma2
|proof=Индукция по h.
При <tex>h = 1</tex> {{---}} верно.
При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>.
По индукции число узлов в каждом из них больше или равно <tex>2^{h - 1} - 1</tex>, тогда во все дереве <tex>n \ge (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{h} - 1</tex> узлов.}}
====Алгоритм====
1. * Найдем узел <tex>x</tex>, вырежем поддерево с корнем в этом узле. 2. * Пройдем от предка вырезанной вершины, при этом пересчитывая <tex>dist</tex>. Если <tex>dist</tex> левого сына вершины меньше <tex>dist</tex> правого, то меняем местами поддеревья.* Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное.
{{Лемма
|id=lemma3
|about=3
|statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев.
|proof=Длина пути от вершины до корня может быть и <tex>O(n)</tex>, но нам не нужно подниматься до корня {{---}} достаточно подняться до вершины, у которой свойство левостороннести левосторонней кучи уже выполнено. Транспонируем только если <tex>dist(x.L) < dist(x.R)</tex>, но <tex>dist(x.R) \le \log{n}</tex>. На каждом шаге, если нужно транспонируем и увеличиваем <tex>dist++</tex>, тогда <tex>dist</tex> увеличится до <tex>\log{n}</tex> и обменов уже не надо будет делать.}}Таким образом, мы восстановили левостороннесть кучи за <tex>O(\log{n})</tex>.Поэтому асимптотика операции <tex> decreaseKey </tex> {{---}} <tex>O(\log{n})</tex>. 
3. Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. <tex>O(\log{n})</tex>
==Построение кучи за O(n)==
Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка.
==Преимущества левосторонней кучи==
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация <tex>merge</tex> является персистентной.
 
[[Категория: Дискретная математика и алгоритмы]]
[[Категория: Приоритетные очереди]]

Навигация