Двоичная куча
Определение
Определение: |
Двоичная куча или пирамида — такое двоичное подвешенное дерево, для которого выполнены следующие три условия:
|
Удобнее всего двоичную кучу хранить в виде массива
, у которого нулевой элемент, — элемент в корне, а потомками элемента являются и . Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть , где — количество узлов дерева.Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за
. Они являются частным случаем приоритетных очередей.Базовые процедуры
Восстановление свойств кучи
Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры
(просеивание вниз) и (просеивание вверх). Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией . Работа процедуры: если -й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами -й элемент с наименьшим из его сыновей, после чего выполняем для этого сына. Процедура выполняется за время .siftDown
function siftDown(i : int):
while 2 * i + 1 < A.heapSize //
— количество элементов в куче
left = A[2 * i + 1] // left — левый сын
if 2 * i + 2 < A.heapSize and A[2 * i + 2] <= left // A[2 * i + 2] — правый сын
swap(A[2 * i + 2], A[i])
i = 2 * i + 2
else if A[2 * i + 1] < A[i]
swap(A[2 * i + 1], A[i])
i = 2 * i + 1
else
break
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.heap_size - 1] A.heap_size = A.heap_size - 1 siftDown(0) return min
Добавление нового элемента
Выполняет добавление элемента в кучу за время
. Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры .
function insert(key : int): A.heap_size = A.heap_size + 1 A[A.heap_size - 1] = key siftUp(A.heap_size - 1)
Построение кучи за O(N)
Определение: |
-куча — это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно потомков. |
Дан массив Требуется построить -кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива — по очереди добавить все его элементы (сделать для каждого). Временная оценка такого алгоритма . Однако можно построить кучу еще быстрее — за .
Представим, что в массиве хранится дерево ( корень, а потомками элемента являются ). Сделаем для вершин, имеющих хотя бы одного потомка, начиная с конца (от до ) (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены).
Лемма: |
На выходе получим искомую кучу. |
Доказательство: |
При вызове | для вершины, ее поддеревья являются кучами. После выполнения эта вершина с ее поддеревьями будут также являться кучей. Значит, после выполнения всех получится куча.
Лемма: | ||||||
Время работы этого алгоритма . | ||||||
Доказательство: | ||||||
Число вершин на высоте в куче из элементов не превосходит . Высота кучи не превосходит . Обозначим за высоту дерева, тогда время построения не превосходит
Докажем вспомогательную лемму о сумме ряда.
| ||||||