Двоичная куча — различия между версиями
Adel (обсуждение | вклад) (→Восстановление свойств кучи) |
Adel (обсуждение | вклад) |
||
Строка 22: | Строка 22: | ||
===Восстановление свойств кучи=== | ===Восстановление свойств кучи=== | ||
− | Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры | + | Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры <math> \mathrm {siftDown} </math> (просеивание вниз) |
− | и | + | и <math> \mathrm {siftUp} </math> (просеивание вверх). |
− | Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией | + | Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией <math> \mathrm {siftDown} </math>. |
− | Работа процедуры: если <tex>i</tex>-й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами <tex>i</tex>-й элемент с наименьшим из его сыновей, после чего выполняем | + | Работа процедуры: если <tex>i</tex>-й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами <tex>i</tex>-й элемент с наименьшим из его сыновей, после чего выполняем <math> \mathrm {siftDown} </math> для этого сына. |
Процедура выполняется за время <tex>O(\log{N})</tex>. | Процедура выполняется за время <tex>O(\log{N})</tex>. | ||
Строка 46: | Строка 46: | ||
siftDown(2 * i + 1) | siftDown(2 * i + 1) | ||
</code> | </code> | ||
− | Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией | + | Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией <math> \mathrm {siftUp} </math>. |
− | Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем | + | Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем <math> \mathrm {siftUp} </math> |
для этого отца. Иными словами, слишком большой элемент всплывает наверх. | для этого отца. Иными словами, слишком большой элемент всплывает наверх. | ||
Процедура выполняется за время <tex>O(\log{N})</tex>. | Процедура выполняется за время <tex>O(\log{N})</tex>. | ||
Строка 65: | Строка 65: | ||
# Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата. | # Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата. | ||
# Последний элемент копируется в корень, после чего удаляется из кучи. | # Последний элемент копируется в корень, после чего удаляется из кучи. | ||
− | # Вызывается | + | # Вызывается <math> \mathrm {siftDown} </math> для корня. |
# Сохранённый элемент возвращается. | # Сохранённый элемент возвращается. | ||
− | + | ||
− | + | '''function''' extractMin(): | |
− | + | min = A[0] | |
− | + | A[0] = A[A.heap_size - 1] | |
− | + | A.heap_size = A.heap_size - 1 | |
− | + | siftDown(0) | |
− | + | '''return''' min | |
− | |||
===Добавление нового элемента=== | ===Добавление нового элемента=== | ||
Выполняет добавление элемента в кучу за время <tex>O(\log{N})</tex>. | Выполняет добавление элемента в кучу за время <tex>O(\log{N})</tex>. | ||
− | Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры | + | Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры <math> \mathrm {siftUp} </math>. |
− | + | '''function''' insert(key): | |
− | insert(key) | + | A.heap_size = A.heap_size + 1 |
− | + | A[A.heap_size - 1] = key | |
− | + | siftUp(A.heap_size - 1) | |
− | |||
− | |||
==Построение кучи за O(N) == | ==Построение кучи за O(N) == | ||
Строка 93: | Строка 90: | ||
}} | }} | ||
− | Дан массив <tex>A[0.. N - 1].</tex> Требуется построить <tex>D</tex>-кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива - по очереди добавить все его элементы (сделать | + | Дан массив <tex>A[0.. N - 1].</tex> Требуется построить <tex>D</tex>-кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива - по очереди добавить все его элементы (сделать <math> \mathrm {siftDown} </math> для каждого). Временная оценка такого алгоритма <tex> O(N\log{N})</tex>. Однако можно построить кучу еще быстрее — за <tex> O(N) </tex>. |
− | Представим, что в массиве хранится дерево (<tex>A[0] - </tex> корень, а потомками элемента <tex>A[i]</tex> являются <tex>A[2i+1]...A[2i+D]</tex>). Сделаем | + | Представим, что в массиве хранится дерево (<tex>A[0] - </tex> корень, а потомками элемента <tex>A[i]</tex> являются <tex>A[2i+1]...A[2i+D]</tex>). Сделаем <math> \mathrm {siftDown} </math> для вершин, имеющих хотя бы одного потомка, начиная с конца(от <tex> n - 1</tex> до <tex>0</tex>) (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены). |
{{Лемма | {{Лемма | ||
|statement= На выходе получим искомую кучу. | |statement= На выходе получим искомую кучу. | ||
− | |proof= При вызове | + | |proof= При вызове <math> \mathrm {siftDown} </math> для вершины, ее поддерево является кучей, после выполнения <math> \mathrm {siftDown} </math> поддерево с этой вершиной будет являться кучей. Значит после выполнения всех <math> \mathrm {siftDown} </math> получится куча. |
}} | }} | ||
{{Лемма | {{Лемма |
Версия 02:27, 11 июня 2014
Содержание
Определение
Определение: |
Двоичная куча или пирамида — такое двоичное подвешенное дерево, для которого выполнены следующие три условия:
|
Удобнее всего двоичную кучу хранить в виде массива
, у которого нулевой элемент, — элемент в корне, а потомками элемента являются и . Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть , где — количество узлов дерева.Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за
. Двоичные кучи — частный случай приоритетных очередей.Базовые процедуры
Восстановление свойств кучи
Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры
(просеивание вниз) и (просеивание вверх). Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией . Работа процедуры: если -й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами -й элемент с наименьшим из его сыновей, после чего выполняем для этого сына. Процедура выполняется за время .function siftDown(i): // heap_size - количество элементов в куче if 2 * i + 1 <= A.heap_size left = A[2 * i + 1] // левый сын else left = inf if (2 * i + 2 <= A.heap_size) right = A[2 * i + 2] // правый сын else right = inf if (left == right == inf) return if (right <= left && right < A[i]) swap(A[2 * i + 2], A[i]) siftDown(2 * i + 2) if (left < A[i]) swap(A[2 * i + 1], A[i]) siftDown(2 * i + 1)
</code> Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией
.Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем
для этого отца. Иными словами, слишком большой элемент всплывает наверх. Процедура выполняется за время .function siftUp(i): if i == 0 return //Мы в корне if A[i] < A[i / 2] swap(A[i], A[i / 2]) siftUp(i / 2)
Извлечение минимального элемента
Выполняет извлечение минимального элемента из кучи за время
. Извлечение выполняется в четыре этапа:- Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата.
- Последний элемент копируется в корень, после чего удаляется из кучи.
- Вызывается для корня.
- Сохранённый элемент возвращается.
function extractMin(): min = A[0] A[0] = A[A.heap_size - 1] A.heap_size = A.heap_size - 1 siftDown(0) return min
Добавление нового элемента
Выполняет добавление элемента в кучу за время
. Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры .function insert(key): A.heap_size = A.heap_size + 1 A[A.heap_size - 1] = key siftUp(A.heap_size - 1)
Построение кучи за O(N)
Определение: |
-куча — это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно потомков. |
Дан массив Требуется построить -кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива - по очереди добавить все его элементы (сделать для каждого). Временная оценка такого алгоритма . Однако можно построить кучу еще быстрее — за .
Представим, что в массиве хранится дерево ( корень, а потомками элемента являются ). Сделаем для вершин, имеющих хотя бы одного потомка, начиная с конца(от до ) (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены).
Лемма: |
На выходе получим искомую кучу. |
Доказательство: |
При вызове | для вершины, ее поддерево является кучей, после выполнения поддерево с этой вершиной будет являться кучей. Значит после выполнения всех получится куча.
Лемма: | ||||||
Время работы этого алгоритма . | ||||||
Доказательство: | ||||||
Число вершин на высоте в куче из элементов не превосходит . Высота кучи не превосходит . Обозначим за высоту дерева, тогда время построения не превосходит
Докажем вспомогательную лемму о сумме ряда.
| ||||||