Левосторонняя куча — различия между версиями
Shersh (обсуждение | вклад) |
Shersh (обсуждение | вклад) |
||
Строка 71: | Строка 71: | ||
Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка. | Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка. | ||
+ | Покажем, почему это будет работать за <tex> O(n) </tex>. | ||
+ | |||
+ | Пусть на <tex> i </tex>-ом шаге алгоритма в нашем списке остались только кучи размера <tex> 2^i </tex>. На нулевом шаге у нас <tex> n </tex> куч из одного элемента, и на каждом следующем количество куч будет уменьшаться вдвое, а число вершин в куче будет увеличиваться вдвое. Слияние двух куч из <tex> n_i </tex> элементов выполняется за <tex> O(\log{n_i}) </tex>. Поэтому построение будет выполняться за | ||
+ | |||
+ | <tex dpi = 130> | ||
+ | \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{n \cdot \log{n_i}}{2^i} = | ||
+ | n \cdot \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{\log{2^i}}{2^i} = | ||
+ | n \cdot \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{i}{2^i} | ||
+ | </tex> | ||
+ | |||
+ | Покажем, что сумма {{---}} <tex> O(1) </tex>, тогда и алгоритм будет выполняться за <tex> O(n) </tex>. | ||
==Преимущества левосторонней кучи== | ==Преимущества левосторонней кучи== | ||
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация <tex>merge</tex> является персистентной. | Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация <tex>merge</tex> является персистентной. |
Версия 12:48, 27 мая 2013
Содержание
Определение
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.
Определение: |
Левосторонняя куча (leftist heap) — двоичное левосторонее дерево (не обязательно сбалансированное), но с соблюдением порядка кучи (heap order). |
Лемма (1): |
В двоичном дереве с вершинами существует свободная позиция на глубине не более . |
Доказательство: |
Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более | .
Левосторонняя куча накладывает на двоичное дерево дополнительное условие. Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее:
Определение: |
Условие левосторонней кучи. Пусть | — расстояние от вершины до ближайшей свободной позиции в ее поддереве. У пустых позиций . Тогда потребуем для любой вершины .
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
Поддерживаемые операции
merge
Слияние двух куч.
merge(x, y) // x, y — корни двух деревьев if x ==: return y if y == : return x if y.key < x.key: x y // Воспользуемся тем, что куча левосторонняя. Правая ветка — самая короткая и не длиннее логарифма. // Пойдем направо и сольем правое поддерево с у. x.R = merge(x.R, y) // Могло возникнуть нарушение левостороннести кучи. if dist(x.R) > dist(x.L): x.L x.R dist(x) = min(dist(x.L), dist(x.R)) + 1 // пересчитаем расстояние до ближайшей свободной позиции return x // Каждый раз идем из уже существующей вершины только в правое поддерево — не более логарифма вызовов (по лемме)
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
insert
Вставка новой вершины в дерево. Новое левостороннее дерево, состоящее из одной вершины, сливается с исходным.
extractMin
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
delete
Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью
. Уменьшаем ключ до , затем извлекаем минимальное значение.decreaseKey
Лемма (2): |
У левостороннего дерева с правой ветвью длинны количество узлов . |
Доказательство: |
Индукция по h. При — верно.При По индукции число узлов в каждом из них больше или равно левое и правое поддеревья исходного дерева левосторонние, а от их корней больше либо равен . , тогда во все дереве узлов. |
Алгоритм
- Найдем узел , вырежем поддерево с корнем в этом узле.
- Пройдем от предка вырезанной вершины, при этом пересчитывая . Если левого сына вершины меньше правого, то меняем местами поддеревья.
- Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное.
Лемма (3): |
Нужно транспонировать не более поддеревьев. |
Доказательство: |
Длина пути от вершины до корня может быть и | , но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левосторонней кучи уже выполнено. Транспонируем только если , но . На каждом шаге, если нужно транспонируем и увеличиваем , тогда увеличится до и обменов уже не надо будет делать.
Таким образом, мы восстановили левостороннесть кучи за
. Поэтому асимптотика операции — .
Построение кучи за O(n)
Храним список левосторонних куч. Пока их количество больше
, из начала списка достаем две кучи, сливаем их и кладем в конец списка.Покажем, почему это будет работать за
.Пусть на
-ом шаге алгоритма в нашем списке остались только кучи размера . На нулевом шаге у нас куч из одного элемента, и на каждом следующем количество куч будет уменьшаться вдвое, а число вершин в куче будет увеличиваться вдвое. Слияние двух куч из элементов выполняется за . Поэтому построение будет выполняться за
Покажем, что сумма —
, тогда и алгоритм будет выполняться за .Преимущества левосторонней кучи
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в
. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация является персистентной.