Левосторонняя куча — различия между версиями
Shersh (обсуждение | вклад) м  | 
				Shersh (обсуждение | вклад)   (мелкий рефакторинг)  | 
				||
| Строка 15: | Строка 15: | ||
{{Определение  | {{Определение  | ||
| − | |definition='''Условие левосторонней кучи'''. Пусть <tex>dist(u)</tex> {{---}} расстояние от вершины <tex>u</tex> до ближайшей свободной позиции в ее поддереве. У пустых позиций <tex>dist = 0</tex>. Тогда потребуем для любой вершины <tex>u : dist(u.L)\  | + | |definition='''Условие левосторонней кучи'''. Пусть <tex>dist(u)</tex> {{---}} расстояние от вершины <tex>u</tex> до ближайшей свободной позиции в ее поддереве. У пустых позиций <tex>dist = 0</tex>. Тогда потребуем для любой вершины <tex>u : dist(u.L)\geqslant dict(u.R)</tex>.}}  | 
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за <tex>O(1)</tex> поменять местами левого и правого ребенка, что не повлияет на порядок кучи.  | Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за <tex>O(1)</tex> поменять местами левого и правого ребенка, что не повлияет на порядок кучи.  | ||
| Строка 23: | Строка 23: | ||
Слияние двух куч.    | Слияние двух куч.    | ||
| − |    '''  | + |    '''function''' merge(x, y): // x, y {{---}} корни двух деревьев  | 
     '''if''' x == <tex> \varnothing </tex>: '''return''' y  |      '''if''' x == <tex> \varnothing </tex>: '''return''' y  | ||
     '''if''' y == <tex> \varnothing </tex>: '''return''' x  |      '''if''' y == <tex> \varnothing </tex>: '''return''' x  | ||
| Строка 50: | Строка 50: | ||
|id=lemma2  | |id=lemma2  | ||
|about=2  | |about=2  | ||
| − | |statement=У левостороннего дерева с правой ветвью длинны <tex>h</tex> количество узлов <tex>n \  | + | |statement=У левостороннего дерева с правой ветвью длинны <tex>h</tex> количество узлов <tex>n \geqslant 2^{h} - 1</tex>.  | 
|proof=Индукция по h.  | |proof=Индукция по h.  | ||
| Строка 57: | Строка 57: | ||
При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>.  | При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>.  | ||
| − | По индукции число узлов в каждом из них больше или равно <tex>2^{h - 1} - 1</tex>, тогда во все дереве <tex>n \  | + | По индукции число узлов в каждом из них больше или равно <tex>2^{h - 1} - 1</tex>, тогда во все дереве <tex>n \geqslant (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{h} - 1</tex> узлов.}}  | 
====Алгоритм====  | ====Алгоритм====  | ||
* Найдем узел <tex>x</tex>, вырежем поддерево с корнем в этом узле.  | * Найдем узел <tex>x</tex>, вырежем поддерево с корнем в этом узле.  | ||
| Строка 66: | Строка 66: | ||
|about=3  | |about=3  | ||
|statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев.    | |statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев.    | ||
| − | |proof=Длина пути от вершины до корня может быть и <tex>O(n)</tex>, но нам не нужно подниматься до корня {{---}} достаточно подняться до вершины, у которой свойство левосторонней кучи уже выполнено. Транспонируем только если <tex>dist(x.L) < dist(x.R)</tex>, но <tex>dist(x.R) \  | + | |proof=Длина пути от вершины до корня может быть и <tex>O(n)</tex>, но нам не нужно подниматься до корня {{---}} достаточно подняться до вершины, у которой свойство левосторонней кучи уже выполнено. Транспонируем только если <tex>dist(x.L) < dist(x.R)</tex>, но <tex>dist(x.R) \leqslant \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>.  | Таким образом, мы восстановили левостороннесть кучи за <tex>O(\log{n})</tex>. Поэтому асимптотика операции <tex> decreaseKey </tex> {{---}} <tex>O(\log{n})</tex>.  | ||
| Строка 87: | Строка 87: | ||
<tex dpi = 130>    | <tex dpi = 130>    | ||
\sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{i}{2^i} < \sum\limits_{i = 1}^{\infty } \genfrac{}{}{}{0}{i}{2^i} \\  | \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{i}{2^i} < \sum\limits_{i = 1}^{\infty } \genfrac{}{}{}{0}{i}{2^i} \\  | ||
| − | f(x) = \sum\limits_{i = 1}^{\infty } \Bigl. i \cdot x^i \Bigr|_{x = \frac{1}{2}}  | + | f(x) = \sum\limits_{i = 1}^{\infty } \Bigl. i \cdot x^i \Bigr|_{x = \frac{1}{2}} \\   | 
| − | ~\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } i \cdot x^{i - 1}  | + | ~\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } i \cdot x^{i - 1} = \sum\limits_{i = 1}^{\infty } (x^i)' = (\sum\limits_{i = 1}^{\infty } x^i)' \\  | 
| − | ~\int\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } x^i =~\genfrac{}{}{}{0}{1}{1 - x} - 1  | + | ~\int\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } x^i =~\genfrac{}{}{}{0}{1}{1 - x} - 1 \\  | 
| − | ~\genfrac{}{}{}{0}{f(x)}{x} = (\genfrac{}{}{}{0}{1}{1 - x} - 1)' = \genfrac{}{}{}{0}{1}{(1 - x)^2}  | + | ~\genfrac{}{}{}{0}{f(x)}{x} = (\genfrac{}{}{}{0}{1}{1 - x} - 1)' = \genfrac{}{}{}{0}{1}{(1 - x)^2} \\  | 
~f(x) = \genfrac{}{}{}{0}{x}{(1 - x)^2}  | ~f(x) = \genfrac{}{}{}{0}{x}{(1 - x)^2}  | ||
</tex>  | </tex>  | ||
Версия 23:09, 4 мая 2014
Содержание
Условие кучи
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.
| Определение: | 
| Левосторонняя куча (leftist heap) — двоичное левосторонее дерево (не обязательно сбалансированное), но с соблюдением порядка кучи (heap order). | 
Свободной позицией назовем место в дереве, куда может быть вставлена новая вершина. Само дерево будет являться свободной позицией, если оно не содержит вершин. Если же у какой-то внутренней вершины нет сына, то на его месте — свободная позиция.
| Лемма (1): | 
В двоичном дереве с  вершинами существует свободная позиция на глубине не более .  | 
| Доказательство: | 
| Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более . | 
Левосторонняя куча накладывает на двоичное дерево дополнительное условие. Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее:
| Определение: | 
| Условие левосторонней кучи. Пусть — расстояние от вершины до ближайшей свободной позиции в ее поддереве. У пустых позиций . Тогда потребуем для любой вершины . | 
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за  поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
Поддерживаемые операции
merge
Слияние двух куч.
function merge(x, y): // x, y — корни двух деревьев if x == : return y if y == : return x if y.key < x.key: swap(x, y) // Воспользуемся тем, что куча левосторонняя. Правая ветка — самая короткая и не длиннее логарифма. // Пойдем направо и сольем правое поддерево с у. x.R = merge(x.R, y) // Могло возникнуть нарушение левостороннести кучи. if dist(x.R) > dist(x.L): swap(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)
Храним список левосторонних куч. Пока их количество больше , из начала списка достаем две кучи, сливаем их и кладем в конец списка.
Покажем, почему это будет работать за .
Пусть на -ом шаге алгоритма в нашем списке остались только кучи размера . На нулевом шаге у нас куч из одного элемента, и на каждом следующем количество куч будет уменьшаться вдвое, а число вершин в куче будет увеличиваться вдвое. Слияние двух куч из элементов выполняется за . Поэтому построение будет выполняться за
Покажем, что сумма — , тогда и алгоритм будет выполняться за . Найдём сумму ряда, заменив его на эквивалентный функциональный:
После подстановки получаем, что сумма равна . Следовательно, построение кучи таким образом произойдёт за .
Преимущества левосторонней кучи
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в . Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реализация является персистентной.
Ссылки
1. Лекция "Приоритетные очереди" А. С. Станкевича в Computer Science Center
