Двоичная куча — различия между версиями
Shchuko (обсуждение | вклад) (→Восстановление свойств кучи) |
м (rollbackEdits.php mass rollback) |
||
| (не показано 7 промежуточных версий 7 участников) | |||
| Строка 4: | Строка 4: | ||
'''Двоичная куча''' или '''пирамида''' (англ. ''Binary heap'') — такое двоичное [[Дерево, эквивалентные определения|подвешенное дерево]], для которого выполнены следующие три условия: | '''Двоичная куча''' или '''пирамида''' (англ. ''Binary heap'') — такое двоичное [[Дерево, эквивалентные определения|подвешенное дерево]], для которого выполнены следующие три условия: | ||
| − | * Значение в любой вершине не | + | * Значение в любой вершине не больше (если куча для минимума), чем значения её потомков. |
* На <tex>i</tex>-ом слое <tex>2^i</tex> вершин, кроме последнего. Слои нумеруются с нуля. | * На <tex>i</tex>-ом слое <tex>2^i</tex> вершин, кроме последнего. Слои нумеруются с нуля. | ||
* Последний слой заполнен слева направо (как показано на рисунке) | * Последний слой заполнен слева направо (как показано на рисунке) | ||
| Строка 65: | Строка 65: | ||
# Сохранённый элемент возвращается. | # Сохранённый элемент возвращается. | ||
| − | |||
'''int''' extractMin(): | '''int''' extractMin(): | ||
'''int''' min = a[0] | '''int''' min = a[0] | ||
| Строка 72: | Строка 71: | ||
siftDown(0) | siftDown(0) | ||
'''return''' min | '''return''' min | ||
| − | |||
===Добавление нового элемента=== | ===Добавление нового элемента=== | ||
| Строка 96: | Строка 94: | ||
{{Лемма | {{Лемма | ||
|statement= На выходе получим искомую кучу. | |statement= На выходе получим искомую кучу. | ||
| − | |proof= | + | |proof= До вызова <tex> \mathrm {siftDown} </tex> для вершины, ее поддеревья являются кучами. После выполнения <tex> \mathrm {siftDown} </tex> эта вершина с ее поддеревьями будут также являться кучей. Значит, после выполнения всех <tex> \mathrm {siftDown} </tex> получится куча. |
}} | }} | ||
{{Лемма | {{Лемма | ||
| Строка 123: | Строка 121: | ||
Псевдокод алгоритма: | Псевдокод алгоритма: | ||
<code style="display:inline-block"> | <code style="display:inline-block"> | ||
| − | '''function''' | + | '''function''' buldHeap(): |
'''for''' i = a.heapSize / 2 '''downto''' 0 | '''for''' i = a.heapSize / 2 '''downto''' 0 | ||
siftDown(i) | siftDown(i) | ||
| Строка 134: | Строка 132: | ||
<code style="display:inline-block"> | <code style="display:inline-block"> | ||
'''function''' merge(a, b : '''Heap'''): | '''function''' merge(a, b : '''Heap'''): | ||
| − | '''while''' b.heapSize | + | '''while''' b.heapSize > 0 |
a.insert(b.extractMin()) | a.insert(b.extractMin()) | ||
| − | </code> | + | </code> |
| + | |||
====Реализация с помощью построения кучи==== | ====Реализация с помощью построения кучи==== | ||
Добавим все элементы кучи <tex>b</tex> в конец массива <tex>a</tex>, после чего вызовем функцию построения кучи. Процедура выполняется за время <tex>O(n + m)</tex>. | Добавим все элементы кучи <tex>b</tex> в конец массива <tex>a</tex>, после чего вызовем функцию построения кучи. Процедура выполняется за время <tex>O(n + m)</tex>. | ||
| Строка 148: | Строка 147: | ||
</code> | </code> | ||
| − | ===Поиск k-ого элемента=== | + | ===Поиск k-ого элемента (очень коряво расписано с неверными индексами)=== |
Требуется найти <tex>k</tex>-ый по величине элемент в куче. | Требуется найти <tex>k</tex>-ый по величине элемент в куче. | ||
Текущая версия на 19:30, 4 сентября 2022
Определение
| Определение: |
Двоичная куча или пирамида (англ. Binary heap) — такое двоичное подвешенное дерево, для которого выполнены следующие три условия:
|
Удобнее всего двоичную кучу хранить в виде массива , у которого нулевой элемент, — элемент в корне, а потомками элемента являются и . Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть , где — количество узлов дерева.
Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за . Они являются частным случаем приоритетных очередей.
Базовые процедуры
Восстановление свойств кучи
Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры (просеивание вниз) и (просеивание вверх).
siftDown
Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией .
Работа процедуры: если -й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами -й элемент с наименьшим из его сыновей, после чего выполняем для этого сына. Процедура выполняется за время .
function siftDown(i : int):
while 2 * i + 1 < a.heapSize // heapSize — количество элементов в куче
left = 2 * i + 1 // left — левый сын
right = 2 * i + 2 // right — правый сын
j = left
if right < a.heapSize and a[right] < a[left]
j = right
if a[i] <= a[j]
break
swap(a[i], a[j])
i = j
siftUp
Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией .
Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем
для этого отца. Иными словами, слишком маленький элемент всплывает наверх.
Процедура выполняется за время .
function siftUp(i : int):
while a[i] < a[(i - 1) / 2] // i 0 — мы в корне
swap(a[i], a[(i - 1) / 2])
i = (i - 1) / 2
Извлечение минимального элемента
Выполняет извлечение минимального элемента из кучи за время . Извлечение выполняется в четыре этапа:
- Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата.
- Последний элемент копируется в корень, после чего удаляется из кучи.
- Вызывается для корня.
- Сохранённый элемент возвращается.
int extractMin():
int min = a[0]
a[0] = a[a.heapSize - 1]
a.heapSize = a.heapSize - 1
siftDown(0)
return min
Добавление нового элемента
Выполняет добавление элемента в кучу за время . Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры .
function insert(key : int):
a.heapSize = a.heapSize + 1
a[a.heapSize - 1] = key
siftUp(a.heapSize - 1)
Построение кучи за O(n)
| Определение: |
| -куча — это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно потомков. |
Дан массив Требуется построить -кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива — сделать нулевой элемент массива корнем, а дальше по очереди добавить все его элементы в конец кучи и запускать от каждого добавленного элемента . Временная оценка такого алгоритма . Однако можно построить кучу еще быстрее — за .
Представим, что в массиве хранится дерево ( корень, а потомками элемента являются ). Сделаем для вершин, имеющих хотя бы одного потомка: от до ,— так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены.
| Лемма: |
На выходе получим искомую кучу. |
| Доказательство: |
| До вызова для вершины, ее поддеревья являются кучами. После выполнения эта вершина с ее поддеревьями будут также являться кучей. Значит, после выполнения всех получится куча. |
| Лемма: | ||||||
Время работы этого алгоритма . | ||||||
| Доказательство: | ||||||
|
Число вершин на высоте в куче из элементов не превосходит . Высота кучи не превосходит . Обозначим за высоту дерева, тогда время построения не превосходит
Докажем вспомогательную лемму о сумме ряда.
| ||||||
Псевдокод алгоритма:
function buldHeap():
for i = a.heapSize / 2 downto 0
siftDown(i)
Слияние двух куч
Даны две кучи и , размерами и , требуется объединить эти две кучи.
Наивная реализация
Поочередно добавим все элементы из в . Время работы — .
function merge(a, b : Heap):
while b.heapSize > 0
a.insert(b.extractMin())
Реализация с помощью построения кучи
Добавим все элементы кучи в конец массива , после чего вызовем функцию построения кучи. Процедура выполняется за время .
function merge(a, b : Heap):
for i = 0 to b.heapSize - 1
a.heapSize = a.heapSize + 1
a[a.heapSize - 1] = b[i]
a.heapify()
Поиск k-ого элемента (очень коряво расписано с неверными индексами)
Требуется найти -ый по величине элемент в куче.
- Создаем новую кучу, в которой будем хранить пару , где — значение элемента, а — индекс элемента в основном массиве, и добавляем в нее корень кучи.
- Возьмем корень новой кучи и добавим её детей из основной кучи, после чего удалим корень. Проделаем этот шаг раз.
- В корне новой кучи будет находиться ответ.
Время работы алгоритма — .
При выгоднее запускать поиск k-ой порядковой статистики.