Двоичная куча — различия между версиями
Sergej (обсуждение | вклад) (→Построение кучи за O(N)) |
|||
Строка 92: | Строка 92: | ||
==Построение кучи за O(N) == | ==Построение кучи за O(N) == | ||
{{Определение | definition = | {{Определение | definition = | ||
− | '''<tex> | + | '''<tex>D</tex>-куча''' {{---}} это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно <tex>D</tex> потомков. |
}} | }} | ||
− | Дан массив <tex> A[0.. | + | |
− | Представим, что в массиве хранится дерево ( <tex>A[0]</tex> — | + | Дан массив <tex>A[0.. N - 1].</tex> Требуется построить <tex>D</tex>-кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива {{--–}} по очереди добавить все его элементы (сделать sift_down для каждого). Временная оценка такого алгоритма <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>). Сделаем sift_down для вершин, имеющих хотя бы одного потомка, (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены). На выходе получим искомую кучу. | ||
+ | |||
{{Лемма | {{Лемма | ||
|statement= Время работы этого алгоритма <tex> O(N) </tex>. | |statement= Время работы этого алгоритма <tex> O(N) </tex>. | ||
− | |proof= | + | |proof= Число вершин на высоте <tex>h</tex> в куче из <tex>N</tex> элементов не превосходит <tex dpi = "120"> \left \lceil \frac{N}{D^h} \right \rceil </tex>. Высота кучи не превосходит <tex> \log_{D}N </tex>. Обозначим за <tex> H </tex> высоту дерева, тогда время построения не превосходит |
− | Число вершин на высоте <tex>h</tex> в куче из <tex> | + | |
+ | <tex dpi = "160"> \sum_{h = 1}^H \limits\frac{N}{D^h} \cdot D </tex> <tex dpi = "150"> \cdot h </tex> <tex dpi = "160"> = N \cdot D \cdot {\sum_{h = 1}^H \limits}\frac{h}{D^h}. </tex> | ||
+ | |||
+ | Докажем вспомогательную лемму о сумме ряда. | ||
− | |||
− | |||
{{Лемма | {{Лемма | ||
− | |statement= <tex dpi = "160"> {\sum_{h = 1}^\infty \limits}\frac{h}{ | + | |statement= <tex dpi = "160"> {\sum_{h = 1}^\infty \limits}\frac{h}{D^h} = \frac{D}{(D - 1)^2} . </tex> |
|proof= | |proof= | ||
− | Обозначим за <tex> S</tex> сумму ряда. Заметим, что | + | Обозначим за <tex>S</tex> сумму ряда. Заметим, что |
− | <tex dpi = "160"> \frac{ | + | <tex dpi = "160"> \frac{h}{D^h} = \frac{1}{D} \cdot \frac{n - 1}{D ^{n - 1}} + \frac{1}{D^n}. </tex> |
− | <tex dpi = "160">{\sum_{n = 1}^\infty \limits}\frac{1}{d^n} | + | <tex dpi = "160">{\sum_{n = 1}^\infty \limits}\frac{1}{d^n}</tex> {{---}} это сумма бесконечной убывающей геометрической прогрессии, и она равна <tex dpi = "160"> |
− | \frac{\frac{1}{ | + | \frac{\frac{1}{D}}{1 - \frac{1}{D}} = \frac{1}{D - 1}. </tex> |
− | Получаем <tex> S </tex> <tex dpi = "160" >= | + | Получаем <tex>S</tex> <tex dpi = "160" >=\frac{1}{D}</tex> <tex>\cdot S +</tex> <tex dpi = "160" > \frac{1}{D - 1}. </tex> Откуда <tex>S</tex> <tex dpi = "160"> = \frac{D}{(D - 1)^2}. </tex> |
}} | }} | ||
− | |||
− | |||
− | + | Подставляя в нашу формулу результат леммы, получаем <tex >N</tex> <tex dpi = "160">\cdot (\frac {D}{D - 1})^2 </tex> <tex> < 5 \cdot N </tex> <tex>=O(N).</tex> | |
}} | }} | ||
Версия 03:06, 11 июня 2013
Содержание
Определение
Определение: |
Двоичная куча или пирамида — такое двоичное подвешенное дерево, для которого выполнены следующие три условия:
|
Удобнее всего двоичную кучу хранить в виде массива
, у которого нулевой элемент, — элемент в корне, а потомками элемента являются и . Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть , где — количество узлов дерева.Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за
. Двоичные кучи — частный случай приоритетных очередей. Приоритетная очередь — это структура данных, которая позволяет хранить пары (значение и ключ) и поддерживает операции добавления пары, поиска пары с минимальным ключом и ее извлечение.Базовые процедуры
Восстановление свойств кучи
Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры sift_down (просеивание вниз) и sift_up (просеивание вверх). Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией sift_down(i). Работа процедуры: если
-й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами -й элемент с наименьшим из его сыновей, после чего выполняем sift_down() для этого сына. Процедура выполняется за время .
sift_down(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]) sift_down(2 * i + 2) if (left < A[i]) swap(A[2 * i + 1], A[i]) sift_down(2 * i + 1)
Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией sift_up(i).
Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем sift_up для этого отца. Иными словами, слишком большой элемент всплывает наверх. Процедура выполняется за время
.
sift_up(i) if (i == 0) return //Мы в корне if (A[i] < A[i / 2]) swap(A[i], A[i / 2]); sift_up(i / 2)
Извлечение минимального элемента
Выполняет извлечение минимального элемента из кучи за время
. Извлечение выполняется в четыре этапа:- Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата.
- Последний элемент копируется в корень, после чего удаляется из кучи.
- Вызывается sift_down(i) для корня.
- Сохранённый элемент возвращается.
extract_min() min = A[0] A[0] = A[A.heap_size - 1] A.heap_size = A.heap_size - 1 sift_down(0) return min
Добавление нового элемента
Выполняет добавление элемента в кучу за время
. Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры sift_up.
insert(key) A.heap_size = A.heap_size + 1 A[A.heap_size - 1] = key sift_up(A.heap_size - 1)
Построение кучи за O(N)
Определение: |
-куча — это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно потомков. |
Дан массив Требуется построить -кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива Шаблон:--– по очереди добавить все его элементы (сделать sift_down для каждого). Временная оценка такого алгоритма . Однако можно построить кучу еще быстрее — за .
Представим, что в массиве хранится дерево ( Шаблон:--— корень, а потомками элемента являются ). Сделаем sift_down для вершин, имеющих хотя бы одного потомка, (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены). На выходе получим искомую кучу.
Лемма: | ||||||
Время работы этого алгоритма . | ||||||
Доказательство: | ||||||
Число вершин на высоте в куче из элементов не превосходит . Высота кучи не превосходит . Обозначим за высоту дерева, тогда время построения не превосходит
Докажем вспомогательную лемму о сумме ряда.
| ||||||