63
правки
Изменения
Нет описания правки
{{Определение
|definition=
'''Двоичная куча''' или '''пирамида''' (англ. ''Binary heap'') — такое двоичное [[Дерево, эквивалентные определения|подвешенное дерево]], для которого выполнены следующие три условия:
* Значение в любой вершине не меньше, (если куча для максимума), чем значения её потомков.
[[Файл:Heap.png|thumb|325px|Пример кучи для максимума]]
Удобнее всего двоичную кучу хранить в виде массива <tex>Aa[0..n-1]</tex>, у которого нулевой элемент, <tex>Aa[0]</tex> — элемент в корне, а потомками элемента <tex>Aa[i]</tex> являются <tex>Aa[2i+1]</tex> и <tex>Aa[2i+2]</tex>. Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть <tex>O(\log{Nn})</tex>, где <tex>Nn</tex> — количество узлов дерева.
Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за <tex>O(\log{Nn})</tex>. Они являются частным случаем приоритетных очередей.
==Базовые процедуры==
Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией <tex> \mathrm {siftDown} </tex>.
Работа процедуры: если <tex>i</tex>-й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами <tex>i</tex>-й элемент с наименьшим из его сыновей, после чего выполняем <tex> \mathrm {siftDown} </tex> для этого сына.
Процедура выполняется за время <tex>O(\log{Nn})</tex>.
====siftDown====
<code>
'''function''' siftDown(i : '''int'''):
'''while''' 2 * i + 1 <tex><</tex> Aa.heapSize <font color = "green">// heapSize {{---}} количество элементов в куче</font>
left = 2 * i + 1 <font color = "green">// left {{---}} левый сын</font>
right = 2 * i + 2 <font color = "green">// right {{---}} правый сын</font>
j = left
'''if''' right <tex><</tex> Aa.heapSize '''and''' Aa[right] <tex><</tex> A[left]
j = right
'''if''' Aa[i] <tex>\leqslant</tex> Aa[j]
'''break'''
swap(Aa[i], Aa[j])
i = j
</code>
Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем <tex> \mathrm {siftUp} </tex>
для этого отца. Иными словами, слишком маленький элемент всплывает наверх.
Процедура выполняется за время <tex>O(\log{Nn})</tex>.
<code>
'''function''' siftUp(i : '''int'''):
'''while''' Aa[i] <tex><</tex> Aa[(i - 1) / 2] <font color = "green">// i <tex>==</tex> 0 {{---}} мы в корне</font> swap(Aa[i], Aa[(i - 1) / 2])
i = (i - 1) / 2
</code>
===Извлечение минимального элемента===
Выполняет извлечение минимального элемента из кучи за время <tex>O(\log{Nn})</tex>.
Извлечение выполняется в четыре этапа:
# Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата.
<code>
'''int''' extractMin():
'''int''' min = Aa[0] Aa[0] = Aa[A.heapSize - 1] Aa.heapSize = Aa.heapSize - 1
siftDown(0)
'''return''' min
===Добавление нового элемента===
Выполняет добавление элемента в кучу за время <tex>O(\log{Nn})</tex>.
Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры <math> \mathrm {siftUp} </math>.
<code>
'''function''' insert(key : '''int'''):
</code>
==Построение кучи за O(Nn) ==
{{Определение | definition =
'''<tex>D</tex>-куча''' {{---}} это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно <tex>Dd</tex> потомков.
}}
Дан массив <tex>Aa[0.. N n - 1].</tex> Требуется построить <tex>Dd</tex>-кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива {{---}} сделать нулевой элемент массива корнем, а дальше по очереди добавить все его элементы в конец кучи и запускать от каждого добавленного элемента <math>\mathrm {siftUp}</math>. Временная оценка такого алгоритма <tex> O(Nn\log{Nn})</tex>. Однако можно построить кучу еще быстрее — за <tex> O(Nn) </tex>.
Представим, что в массиве хранится дерево (<tex>Aa[0] - </tex> корень, а потомками элемента <tex>Aa[i]</tex> являются <tex>Aa[2i+1]...Aa[2i+Dd]</tex>). Сделаем <tex> \mathrm {siftDown} </tex> для вершин, имеющих хотя бы одного потомка: от <tex dpi=140>\genfrac {}{}{}{}{Nn}{Dd}</tex> до <tex>0</tex>,{{---}} так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены.
{{Лемма
|statement= На выходе получим искомую кучу.
}}
{{Лемма
|statement= Время работы этого алгоритма <tex> O(Nn) </tex>.|proof= Число вершин на высоте <tex>h</tex> в куче из <tex>Nn</tex> элементов не превосходит <tex dpi = "160"> \left \lceil \frac{Nn}{Dd^h} \right \rceil </tex>. Высота кучи не превосходит <tex> \log_{Dd}N n </tex>. Обозначим за <tex> H </tex> высоту дерева, тогда время построения не превосходит
<tex dpi = "160"> \sum_{h = 1}^H \limits\frac{Nn}{Dd^h} \cdot D d </tex> <tex dpi = "150"> \cdot h </tex> <tex dpi = "160"> = N n \cdot D d \cdot {\sum_{h = 1}^H \limits}\frac{h}{Dd^h}. </tex>
Докажем вспомогательную лемму о сумме ряда.
{{Лемма
|statement= <tex dpi = "160"> {\sum_{h = 1}^\infty \limits}\frac{h}{Dd^h} = \frac{Dd}{(D d - 1)^2} . </tex>
|proof=
Обозначим за <tex>Ss</tex> сумму ряда. Заметим, что<tex dpi = "160"> \frac{n}{Dd^n} = \frac{1}{Dd} \cdot \frac{n - 1}{D d ^{n - 1}} + \frac{1}{Dd^n}. </tex>
<tex dpi = "160">{\sum_{n = 1}^\infty \limits}\frac{1}{d^n}</tex> {{---}} это сумма бесконечной убывающей геометрической прогрессии, и она равна <tex dpi = "160">
\frac{\frac{1}{Dd}}{1 - \frac{1}{Dd}} = \frac{1}{D d - 1}. </tex>
Получаем <tex>Ss</tex> <tex dpi = "160" >=\frac{1}{Dd}</tex> <tex>\cdot S s +</tex> <tex dpi = "160" > \frac{1}{D d - 1}. </tex> Откуда <tex>Ss</tex> <tex dpi = "160"> = \frac{Dd}{(D d - 1)^2}. </tex>
}}
Подставляя в нашу формулу результат леммы, получаем <tex >Nn</tex> <tex dpi = "160">\cdot (\frac {Dd}{D d - 1})^2 </tex> <tex> \leqslant 4 \cdot N n </tex> <tex>=O(Nn).</tex>
}}
==Слияние двух куч==
Даны две кучи <tex>a</tex> и <tex>b</tex>, требуется объединить эти две кучи.
Добавим все элементы кучи <tex>b</tex> в конец массива <tex>a</tex>, после чего вызовем функцию построения кучи. Процедура выполняется за время <tex>O(a.heapSize + b.heapSize)</tex>.
<code>
'''function''' merge(a, b : '''heap'''):
'''for''' i = 0 '''to''' b.heapSize - 1
a.heapSize = a.heapSize + 1
a[a.heapSize - 1] = b[i]
heapify(a)
</code>
== См. также ==