EssayAI
Блог
Блог
Математика и алгоритмы

Биномиальная куча: операции и слияние за O(log n)

22 февраля 2026Время чтения: 8 минут
#биномиальная куча#биномиальное дерево#структуры данных#mergeable heap#куча
Биномиальная куча: операции и слияние за O(log n)

Биномиальная куча - структура данных типа «приоритетная очередь», которая поддерживает все стандартные операции кучи (insert, find-min, extract-min, decrease-key, delete) за O(logn)O(\log n) и при этом умеет дополнительно объединять две кучи (merge) за то же время. Бинарная куча на массиве этого не умеет: её слияние требует O(n)O(n). Биномиальная куча относится к семейству mergeable heap и служит мостом между обычной бинарной кучей и более продвинутой Фибоначчиевой.

Биномиальное дерево BkB_k

Биномиальное дерево определяется рекурсивно:

  • B0B_0 - одиночный узел.
  • BkB_k при k1k \ge 1 получается из двух копий Bk1B_{k-1}: одна копия становится самым левым ребёнком корня другой.

Иначе говоря, чтобы построить B3B_3, берут два B2B_2 и подвешивают второй к корню первого. Корень растущего дерева получает ещё одного сына, его степень увеличивается на единицу.

Из определения сразу следуют ключевые числовые свойства:

  • В BkB_k ровно 2k2^k узлов.
  • Высота BkB_k равна kk.
  • На глубине ii (от 00 до kk) находится (ki)\binom{k}{i} узлов - отсюда и имя «биномиальное».
  • Степень корня BkB_k равна kk, и его дети - корни деревьев Bk1,Bk2,,B0B_{k-1}, B_{k-2}, \dots, B_0 в порядке убывания ранга.

Эти свойства легко доказываются индукцией: при склейке двух Bk1B_{k-1} удваивается число узлов (22k1=2k2 \cdot 2^{k-1} = 2^k), а число элементов на уровне ii нового дерева складывается из элементов уровня ii старого дерева и уровня i1i-1 второго - это тождество Паскаля (ki)=(k1i)+(k1i1)\binom{k}{i} = \binom{k-1}{i} + \binom{k-1}{i-1}.

Биномиальная куча как лес

Биномиальная куча размера nn - это лес биномиальных деревьев, удовлетворяющий двум условиям:

  1. Heap-order property. В каждом дереве ключ родителя не больше ключа любого ребёнка (для min-кучи). Минимум всей кучи лежит в корне одного из деревьев.
  2. Уникальность рангов. В лесу не может быть двух деревьев с одинаковым kk. Если n=(btbt1b1b0)2n = (b_t b_{t-1} \dots b_1 b_0)_2 в двоичной записи, то BkB_k присутствует в куче тогда и только тогда, когда bk=1b_k = 1.

Это даёт прямое соответствие «куча размера nn» \leftrightarrow «двоичная запись nn». Число деревьев в лесу не превосходит log2n+1\lfloor \log_2 n \rfloor + 1 - именно поэтому все операции укладываются в логарифм.

Корни деревьев соединяют в односвязный список, упорядоченный по возрастанию ранга. У каждого узла хранят ключ, степень, указатель на родителя, на самого левого ребёнка и на правого «брата» (sibling).

Объединение двух куч как сложение двоичных чисел

Операция merge - сердце биномиальной кучи. Пусть есть две кучи H1H_1 размера n1n_1 и H2H_2 размера n2n_2. Их корневые списки уже отсортированы по рангу - сначала их сливают в один список (как в merge-sort, за O(logn)O(\log n)).

Затем по списку идут слева направо и применяют правило: если встречаются два соседних дерева одинакового ранга kk, их сливают в одно дерево Bk+1B_{k+1} - корень с большим ключом становится ребёнком корня с меньшим. Это даёт «перенос» в следующий разряд - ровно как при сложении n1+n2n_1 + n_2 в двоичной системе.

Аккуратность нужна, когда подряд идут три дерева ранга kk: тогда первое оставляют как есть, а второе и третье склеивают (это соответствует случаю 1+1+1=1121+1+1=11_2 при сложении в столбик с уже имеющимся переносом). В итоге результирующий лес снова состоит из деревьев с уникальными рангами, а суммарное время - O(log(n1+n2))O(\log(n_1 + n_2)).

Основные операции

Через merge выражаются почти все остальные операции.

  • find-min - пройти по корневому списку, выбрать минимум среди logn+1\le \log n + 1 корней. Время: O(logn)O(\log n). Если хранить указатель на минимальный корень и обновлять его при каждой модификации, получается O(1)O(1) amortized.
  • insert(x) - создать однонодовую кучу-сингл B0B_0 и слить её с основной. Амортизированно O(1)O(1), в худшем случае O(logn)O(\log n).
  • extract-min - найти корень с минимальным ключом, удалить его из корневого списка, перевернуть список его детей (они идут Bk1,Bk2,,B0B_{k-1}, B_{k-2}, \dots, B_0, а для merge нужен возрастающий порядок) и слить полученный лес с оставшейся частью. Время: O(logn)O(\log n).
  • decrease-key(v, k') - заменить ключ узла vv на меньший kk' и «всплыть» вверх, обменивая местами с родителем, пока heap-order не восстановится. Высота не превышает logn\log n, отсюда O(logn)O(\log n). Реальные данные при swap переносить накладно - обычно меняют только значения ключей и сателлитные данные.
  • delete(v) - выполняется как decrease-key(v, -\infty) с последующим extract-min. Тоже O(logn)O(\log n).
  • build из массива - последовательные insert дают O(n)O(n) амортизированно (геометрическая сумма переносов аналогична построению бинарной кучи).

Сводная таблица сложностей:

insert:O(1) amort., O(logn) worstfind-min:O(logn) (или O(1) с указателем)extract-min:O(logn)merge:O(logn)decrease-key:O(logn)delete:O(logn)\begin{aligned} \text{insert} &: O(1)\ \text{amort.},\ O(\log n)\ \text{worst} \\ \text{find-min} &: O(\log n)\ (\text{или}\ O(1)\ \text{с указателем}) \\ \text{extract-min} &: O(\log n) \\ \text{merge} &: O(\log n) \\ \text{decrease-key} &: O(\log n) \\ \text{delete} &: O(\log n) \end{aligned}

Ленивая версия и Фибоначчиева куча

Если откладывать «приведение в порядок» (объединение деревьев одинаковых рангов) до момента extract-min, получится ленивая биномиальная куча: insert и merge становятся O(1)O(1) в худшем случае, а вся работа концентрируется в extract-min, который теперь amortized O(logn)O(\log n). На этой идее построена Фибоначчиева куча (Fredman, Tarjan, 1984): она добавляет каскадные срезы при decrease-key и достигает амортизированного O(1)O(1) для всех операций, кроме extract-min и delete (O(logn)O(\log n) amortized).

Фибоначчиева куча асимптотически лучше биномиальной для алгоритма Дейкстры на плотных графах, но имеет большие константы и сложнее в реализации - поэтому в учебных и инженерных задачах биномиальная куча остаётся компромиссом «простота против гибкости».

Где применяется

  • Алгоритмы кратчайших путей и MST. Дейкстра и Прим на разреженных графах - там, где decrease-key вызывается часто. Биномиальная куча даёт O((V+E)logV)O((|V| + |E|) \log |V|).
  • Mergeable priority queue. Когда задачи разбиваются по подсистемам и затем нужно объединить их очереди - например, в распределённых системах или при слиянии планировщиков двух процессов.
  • Дискретно-событийное моделирование. Когда несколько генераторов событий ведут отдельные очереди, которые объединяются в общую таймлайн.
  • Branch-and-bound / A* с подзадачами. Подзадачи каждого поддерева можно держать в собственной куче и сливать при обходе.

Сравнение: бинарная, биномиальная, Фибоначчиева

ОперацияБинарная (массив)БиномиальнаяФибоначчиева
find-minO(1)O(1)O(logn)O(\log n) / O(1)O(1)O(1)O(1)
insertO(logn)O(\log n)O(1)O(1) amort.O(1)O(1)
extract-minO(logn)O(\log n)O(logn)O(\log n)O(logn)O(\log n) amort.
decrease-keyO(logn)O(\log n)O(logn)O(\log n)O(1)O(1) amort.
mergeO(n)O(n)O(logn)O(\log n)O(1)O(1)

Бинарная куча выигрывает по константам и локальности памяти, но проигрывает там, где нужны частые слияния. Биномиальная - сбалансированный «mergeable» вариант. Фибоначчиева оптимальна асимптотически, но дорога в реализации и константах.

Частые ошибки

  • Путают BkB_k с полным бинарным деревом высоты kk. В BkB_k ровно 2k2^k узлов, а в полном бинарном дереве высоты kk их 2k+112^{k+1}-1. Структуры разные: у BkB_k корень имеет степень kk.
  • Считают, что в куче может быть несколько деревьев одного ранга. Нет - это нарушает инвариант, ровно как «две единицы в одном разряде» нарушают двоичную запись. Любая операция, нарушившая правило, обязана выполнить серию сливаний.
  • Забывают развернуть список детей при extract-min. Дети корня хранятся в порядке убывания ранга, а корневой список - в порядке возрастания. Без разворота merge даст неверный лес.
  • Пытаются делать decrease-key через указатель в массив. В биномиальной куче нет фиксированного массива: чтобы быстро найти узел по «дескриптору», операция insert должна возвращать handle на конкретный узел, а пользователь обязан хранить его.
  • Меряют производительность только по асимптотике. На малых nn и без merge бинарная куча почти всегда быстрее: меньше указателей, лучше кэш.

FAQ

Чем биномиальная куча отличается от бинарной? Бинарная - это массив с heap-order, она проста и кэш-дружелюбна, но merge требует O(n)O(n). Биномиальная - лес деревьев, merge за O(logn)O(\log n) ценой указательной структуры и больших констант.

Почему именно «биномиальная»? Потому что на глубине ii дерева BkB_k ровно (ki)\binom{k}{i} узлов - биномиальный коэффициент. Это прямо следует из рекурсивного определения и тождества Паскаля.

Когда выбирать биномиальную, а когда Фибоначчиеву? Если в алгоритме доминирует decrease-key (плотные графы в Дейкстре, специальные алгоритмы MST) - Фибоначчиева теоретически быстрее. Если важна простота, отладка и предсказуемость - биномиальная или даже бинарная.

Коротко

Биномиальная куча - лес биномиальных деревьев BkB_k с уникальными рангами и heap-order; число и форма деревьев определяются двоичной записью nn. Все базовые операции работают за O(logn)O(\log n), а ключевая merge сводится к сложению двоичных чисел с переносами. Это удобная mergeable-структура, более гибкая, чем бинарная куча, и более простая в реализации, чем Фибоначчиева.

Доверьте текст нейросети EssayAI

Открыть EssayAI

Бесплатно, на русском языке и без VPN

Читайте также