Как стать автором
Поиск
Написать публикацию
Обновить

Кучи. Часть 1. Биномиальная куча

Время на прочтение4 мин
Количество просмотров31K
Здравствуйте, Хабросообщество!

На хабре есть описание множества интересных структур данных, таких как деревья отрезков, дуча и т.п. Если Вам интересны сложные структуры данных, то добро пожаловать под кат! В своем цикле статей я рассмотрю разные виды куч и способы их применения на практике:
1) Биномиальную кучу
2) Левую кучу
3) Фибоначчиеву кучу
4) Применение этих структур данных на практике

Постановка задачи:
Построить структуру данных, в которой будет храниться множество наших объектов (разных в зависимости от задачи), у каждого объекта будет поле ключ, по которому мы быстро сможем находить минимальный элемент. Для этой структуры должны бать возможны операции:
Make – создание новой пустой кучи,
Insert – вставка нового элемента в кучу,
Minimum – минимальный ключ,
ExtractMin – извлечение минимума,
Merge – слияние 2-х куч,
Decrease – уменьшение ключа,
Delete – удаление объекта.

Многие знакомы с простейшими способами реализации этой структуры, такими как массив :) и двоичная куча. На них я не буду подробно останавливаться ввиду их простоты и общеизвестности.

Как известно, для двоичной кучи асимптотика перечисленных выше операций такова:
Make – O(1)
Merge – O(N)
Insert – O(log N) – где N – количество элементов в куче.
Minimum – O(1)
ExtractMin – O(log N)
Decrease – O(log N)
Delete – O(log N)

Описывать алгоритм работы двоичной кучи я не буду, так как он неоднократно описывался везде, в том числе и на Хабре. Для тех, кто не знаком с двоичной кучей, я бы порекомендовал прежде, чем продолжать чтение ознакомиться с ней.

Далее рассмотрим более интересную структуру данных, которая называется биномиальная куча.

Биномиальная куча
Биномиальная куча – это множество биномиальных деревьев с некоторыми ограничениями. Мы их введем чуть позже.

Биномиальное дерево – дерево, которое задается рекуррентно:
Bi – это Bi – 1, в котором левым сыном корня сделали дерево Bi – 1.
B0 — это просто вершина.
Примеры для B0, B2, B3:
image

У биномиального дерева(Bk) есть ряд интересных свойств:
T.1. 2k вершин
T.2. Высота дерева k
T.3. Ci k вершин глубины i (вот почему они называются биномиальными: Ci k биномиальный коэффициент).
T.4. Дети корня – это B, Bk – 2, …, B0 – именно в этом порядке.
T.5. Максимальная высота вершины в биномиальном дереве O(log N)
Доказываются свойства по индукции. Предлагаю читателям самим провести доказательство, для лучшего понимания деревьев.

Итак, теперь вернемся к биномиальным кучам. Биномиальная куча – множество биномиальных деревьев, со следующими ограничениями:
1) В каждом из биномиальных деревьев сохраняется свойство кучи.
2) Нет двух деревьев одинакового размера
3) Деревья упорядочены по размеру.

Поговорим о том, как биномиальная куча будет храниться в программе. Мы будем использовать метод “левый сын – правый брат”. Будем хранить корневой список(root_list, его длина root_list.length), в котором будут корни биномиальных деревьев, в порядке возрастания высоты. У каждой вершины будут следующие поля:
data – данные, которые хранятся в вершине(по ним мы и находим минимум)
right – правый брат
child – левый сын
degree– степень вершины(очевидно деревья в биномиальной куче упорядоченны по этому полю)

Сразу же заметим:
Свойство H.1:
Длина root_list.length = O(log N), где N — количество элементов в куче.
Для доказательства достаточно заметить, что из-за T.1, наличие дерева Bk в двоичной записи числа.

Перейдем к описанию операций, которые можно проводить с биномиальными кучами:

Make
Задача: создать пустую кучу.
Алгоритм: создаем пустой список root_list.
Сложность: очевидно, время работы O(1).

Merge
Задача: объединить 2 кучи в 1.
Алгоритм: сначала объединим корневые списки куч в 1 корневой список, поддерживая упорядоченность по degree. Алгоритм аналогичен слиянию 2-х массивов в mergeSort:
Храним по указателю на начало списков и в результирующий список записываем минимальный из них, тот откуда только что записали сдвигаем на следующий. Далее проходимся от начала до конца нового полученного корневого списка и сливаем деревья одинакового размера в 1. Могут быть случаи:
1) Только 2 дерева одинакового размера. Тогда объединяем их.
2) 3 дерева одинакового размера. Объединяем 2 последних.
При объединении двух деревьев нужно лишь посмотреть в корне какого из них меньший ключ и сделать другое дерево левым сыном корня этого дерева.

Пример, того, что получается после объединения двух куч:
image

Сложность: Время работы O(root_list1.length) + O(root_list2.length) = (по свойству H.1) = O(log N).
За один проход (O(log N )) мы получим объединенное биномиальное дерево. Получаем, что общая сложность O(log N).

Insert
Задача: вставить новый элемент в кучу.
Алгоритм: Создаем кучу из одного элемента и объединяем с нашей кучей.
Сложность: O(1) + O(log(N)) = O(log(N)).

Minimum
Задача: найти минимум в куче.
Алгоритм: очевидно, минимум находится в корневом списке, то есть, чтобы его найти нужно пройтись по корневому списку.
Сложность: O(root_list.length) = O(log(N)).

ExtractMin
Задача: удалить минимальный элемент.
Алгоритм: находим его при помощи Minimum. Удаляем его из корневого списка. Из перевернутого списка его детей делаем root_list для новой кучи (H1) и объединяем исходную кучу с H1.
Сложность: так как каждая операция в извлечении минимума работает за O(log N): O(log N) + O(log N) + O(log N) = O(log N)

Decrease
Задача: уменьшить значение data в данной вершине.
Алгоритм: уменьшаем значение в вершине. Тогда свойство кучи будет возможно нарушено для нашей вершины и ее предка, тогда меняем их местами. Продолжаем процесс, пока наша вершина не “всплывет” на свое место. Алгоритм работает также, как аналогичный в двоичной куче.
Сложность: В худшем случае наша вершина будет всплывать до корня, то есть мы совершим O(log N ) действий (вершина на каждом шаге “всплывает” на уровень выше, а высота биномиального дерева по T.5 O(log N))

Delete
Задача: удалить произвольный элемент.
Алгоритм: сначала уменьшим при помощи Decrease значение в вершине до минимально возможного. А затем удалим минимальный в куче (ExtractMin).
Сложность: O(log N) + O(log N) = O(log N)

Заключение.
Мы рассмотрели структуру данных биномиальная куча и доказали ее асимптотику.
В следующей статье на основе биномиальной кучи мы построим чуть более сложную структуру данных, а именно Фибоначчиеву кучу.

Поиграться с биномиальными кучами можно на rain.ifmo.ru/cat/view.php/vis/heaps/binomial-2001
Почитать более подробно можно в Т.Кормен «Алгоритмы: построение и анализ.».
Всем спасибо за внимание, и до новых встреч!
Теги:
Хабы:
Всего голосов 53: ↑45 и ↓8+37
Комментарии14

Публикации

Ближайшие события