Data Structures
The Binomial Heap
The mergeable priority queue that unions like binary addition
A binomial heap is a mergeable priority queue built as a forest of heap-ordered binomial trees, holding at most one tree of each order. Its defining trick is union: merging two heaps mirrors adding two binary numbers, carrying and linking as you go, so it costs O(log n) — and insert, delete-min, and decrease-key all fall out of union in O(log n) worst case. Introduced by Jean Vuillemin in 1978, it was the first heap to make merge cheap, and the conceptual ancestor of the Fibonacci heap.
- Union (merge)O(log n)
- InsertO(log n) worst, O(1) amortized
- Find-minO(log n), or O(1) cached
- Delete-min / Decrease-keyO(log n)
- SpaceO(n), O(1) pointers per node
- Trees per heap≤ ⌊log₂ n⌋ + 1
- Invented byJean Vuillemin, 1978
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
Why the binomial heap matters
A plain binary heap is one of the most efficient data structures ever devised for a single priority queue: insert and delete-min in O(log n), find-min in O(1), all in a cache-friendly array with no pointers. But it has one glaring weakness — you cannot merge two of them quickly. Concatenating two binary heaps of size n and re-heapifying is O(n). If your algorithm builds many small queues and repeatedly fuses them, that O(n) merge becomes the bottleneck.
The binomial heap solves exactly this. It gives up the flat array for a forest of trees, and in exchange makes union the primitive operation — everything else is defined in terms of it — running in O(log n). Insert is "union with a one-node heap." Delete-min is "cut out the minimum tree, then union its children back in." Because merge is cheap, binomial heaps power algorithms that combine sub-solutions: parallel and distributed priority queues, incremental graph algorithms, and any setting where queues split and recombine.
Historically it matters even more as an idea. When Fredman and Tarjan wanted a heap with O(1) amortized decrease-key to speed up Dijkstra and Prim's MST, they started from the binomial heap and relaxed its rigid invariant, arriving at the Fibonacci heap. The binomial heap is the clean, provably-correct scaffold on which lazier, faster heaps were built.
The binomial tree Bk
Everything rests on one recursively-defined shape. A binomial tree of order k, written Bk, is:
- B0 — a single node.
- Bk — take two Bk−1 trees and make the root of one the leftmost child of the other's root.
From this definition, three properties follow by induction, and they are the numbers you must memorize:
- Bk has exactly 2k nodes.
- Bk has height k.
- The root of Bk has exactly k children, and those children are (from left to right, by convention) roots of Bk−1, Bk−2, …, B1, B0.
- There are exactly C(k, d) — "k choose d" — nodes at depth d. That binomial coefficient is where the name comes from.
A node's degree equals its number of children, and the order of a tree equals the degree of its root. Linking two trees of the same order is a single pointer operation — O(1) — and it is the atomic move behind every binomial-heap operation.
Union as binary addition
Here is the elegant heart of the structure. A binomial heap holds at most one binomial tree of each order. That single invariant makes the heap a physical embodiment of a binary number: a heap of n elements has a Bk in its forest if and only if bit k is set in the binary representation of n. Thirteen elements is 1101₂, so the forest is exactly {B3, B2, B0} = 8 + 4 + 1 nodes.
Merging two heaps is therefore adding two binary numbers. You walk both root lists in increasing order of order, and at each order you might have zero, one, two, or three trees present (two inputs plus a carry). Two trees of order k link into one tree of order k+1 — that is the carry propagating to the next bit. The rules, bit for bit:
| Trees of order k present | Binary analogy | Result at order k | Carry to k+1 |
|---|---|---|---|
| 0 | 0 + 0 | empty | none |
| 1 | 1 + 0 | keep it | none |
| 2 | 1 + 1 | empty | link → Bk+1 |
| 3 (two + carry) | 1 + 1 + 1 | keep one | link other two → Bk+1 |
Because n has at most ⌊log₂ n⌋ + 1 bits, there are at most that many trees, and each order does O(1) work. Union is O(log n). This is the whole reason the structure exists.
How the operations work, step by step
Insert(x). Make a one-node heap (a lone B0) and union it in. That is "add 1 to a binary counter." A single insert can trigger a cascade of carries — B0+B0→B1, then B1+B1→B2, and so on — so the worst case is O(log n). But by the same accounting argument that shows a binary counter costs O(1) amortized per increment, a sequence of inserts averages O(1) each.
Find-min. The minimum is always at the root of some tree (heap order guarantees no child beats its parent). Scan the ≤ log n roots: O(log n). Keep a cached pointer to the minimum root and it is O(1), updated during the operations that change the roots.
Delete-min (extract-min). Find the minimum root — say it heads a Bk — and remove it. Its k children are themselves roots of Bk−1, …, B0: a perfectly valid binomial heap. Reverse that child list and union it back into the remaining forest. Two O(log n) steps (find + union) give O(log n) total.
Decrease-key. Lower a node's key, then sift it up its own binomial tree, swapping with the parent while heap order is violated. Bk has height k, so at most O(log n) swaps. Crucially, the tree shape never changes — only key values move — which is what keeps binomial heaps simple and cut-free, unlike the cascading cuts of the Fibonacci heap.
Delete(x). Decrease-key x to −∞ so it bubbles to a root, then delete-min. O(log n).
Binomial heap vs binary heap vs Fibonacci heap
| Operation | Binary heap | Binomial heap | Fibonacci heap |
|---|---|---|---|
| Find-min | O(1) | O(1) cached | O(1) |
| Insert | O(log n) | O(1) amortized | O(1) amortized |
| Delete-min | O(log n) | O(log n) | O(log n) amortized |
| Decrease-key | O(log n) | O(log n) | O(1) amortized |
| Merge (union) | O(n) | O(log n) | O(1) amortized |
| Layout | Array (contiguous) | Pointer forest | Pointer forest |
| Constant factors | Tiny | Moderate | Large |
The trade is clear. A binary heap is unbeatable for a single queue you never merge — contiguous memory, no pointer chasing, trivial code. The binomial heap earns its keep the moment merge is a first-class operation: it turns an O(n) fuse into O(log n). The Fibonacci heap pushes decrease-key and merge to O(1) amortized (which is what makes Dijkstra provably O(E + V log V)), but its constant factors and pointer overhead are so punishing that binary and binomial heaps usually win on real inputs.
Implementation in Python
Each node stores a key, a parent, a leftmost child, a right sibling, and its degree. The heap is a singly-linked root list kept sorted by increasing degree. Union is the workhorse; every other operation calls it.
class Node:
__slots__ = ("key", "parent", "child", "sibling", "degree")
def __init__(self, key):
self.key = key
self.parent = None
self.child = None # leftmost child
self.sibling = None # next root / next sibling
self.degree = 0
def link(child, parent):
"""Make `child` the new leftmost child of `parent`. Both are B_k -> B_(k+1)."""
child.parent = parent
child.sibling = parent.child
parent.child = child
parent.degree += 1
def merge_root_lists(a, b):
"""Merge two degree-sorted root lists into one (like merging sorted lists)."""
dummy = Node(None); tail = dummy
while a and b:
if a.degree <= b.degree:
tail.sibling = a; a = a.sibling
else:
tail.sibling = b; b = b.sibling
tail = tail.sibling
tail.sibling = a if a else b
return dummy.sibling
def union(h1, h2):
"""Add two binomial heaps like binary numbers: O(log n)."""
head = merge_root_lists(h1, h2)
if head is None:
return None
prev, curr, nxt = None, head, head.sibling
while nxt:
if curr.degree != nxt.degree or (nxt.sibling and nxt.sibling.degree == curr.degree):
# different order, or three-in-a-row: advance, let the pair link next round
prev, curr = curr, nxt
elif curr.key <= nxt.key:
curr.sibling = nxt.sibling # absorb nxt as child of curr (carry)
link(nxt, curr)
else:
if prev is None: head = nxt
else: prev.sibling = nxt
link(curr, nxt) # curr becomes child of nxt
curr = nxt
nxt = curr.sibling
return head
def insert(head, key):
return union(head, Node(key)) # O(1) amortized
def find_min(head):
best, node = None, head
while node:
if best is None or node.key < best.key: best = node
node = node.sibling
return best # O(log n) roots scanned
def extract_min(head):
if head is None: return None, None
# locate the minimum root and its predecessor
prev_min, minroot, prev, node = None, head, None, head
while node:
if node.key < minroot.key: prev_min, minroot = prev, node
prev, node = node, node.sibling
# unlink the min root from the root list
if prev_min is None: head = minroot.sibling
else: prev_min.sibling = minroot.sibling
# reverse the min root's children into their own heap, then union back
child, new_head = minroot.child, None
while child:
nxt = child.sibling
child.sibling, child.parent = new_head, None
new_head, child = child, nxt
return union(head, new_head), minroot.key # O(log n)
Note that the whole family of operations reduces to union. That is the design principle: keep one hard function correct, and derive everything else. The three-in-a-row check in the loop is the "1 + 1 + 1" carry case — without it you would corrupt the degree-sorted invariant.
A worked example: inserting 1 through 5
Watch the forest count in binary as you insert five elements into an empty heap. Each insert is "+1" on a binary counter, and each carry is a link.
- Insert 1 → n = 1 =
1₂. Forest: {B0}. - Insert 2 → n = 2 =
10₂. Two B0 collide, link into one B1 (carry). Forest: {B1}. - Insert 3 → n = 3 =
11₂. New B0 sits beside B1. Forest: {B1, B0}. - Insert 4 → n = 4 =
100₂. B0+B0→B1, then B1+B1→B2 — a two-step carry cascade. Forest: {B2}. - Insert 5 → n = 5 =
101₂. New B0 beside B2. Forest: {B2, B0}.
The forest at every step is exactly the binary expansion of n — that is the invariant made visible. The occasional expensive insert (the 2-link cascade at n = 4) is offset by the many cheap ones, which is precisely why the amortized cost is O(1).
Common misconceptions and pitfalls
- "Binomial heap and binary heap are the same thing." No. A binary heap is one complete tree in an array; a binomial heap is a forest of specially-shaped trees using pointers. The names rhyme; the structures do not.
- "Find-min is O(1)." Only if you cache a pointer to the minimum root. The raw structure requires scanning the ≤ log n roots, so a naive find-min is O(log n). Remember to refresh the cache after union and extract-min.
- "A single insert is O(1)." It is O(1) amortized, not worst case. One unlucky insert into a heap of size 2k − 1 triggers k links and costs O(log n). Distinguish amortized from worst-case in interviews.
- Breaking the degree-sorted root list. Union assumes root lists are sorted by increasing degree and that no two trees share an order after the pass. Skip the three-in-a-row carry case and you will end up with duplicate orders, silently violating the invariant.
- Forgetting to clear parent pointers. When you reverse a deleted root's children into a new heap, each child's
parentmust be set toNone, or later decrease-key sift-ups will walk into freed nodes. - Expecting O(1) decrease-key. That is the Fibonacci heap. In a binomial heap, decrease-key sifts up a full tree height, so it is O(log n).
History: Vuillemin 1978 and the road to Fibonacci heaps
Jean Vuillemin introduced binomial heaps (originally "binomial queues") in his 1978 Communications of the ACM paper, "A Data Structure for Manipulating Priority Queues." The breakthrough was making merge cheap — O(log n) instead of the O(n) forced by array heaps — which unlocked algorithms that repeatedly combine queues. Mark R. Brown extended the analysis the same year, formalizing the amortized costs and the lazy variants.
Six years later, Michael Fredman and Robert Tarjan asked whether decrease-key and merge could be made O(1) amortized. Starting from the binomial heap and deliberately relaxing its "one tree per order" rigidity — allowing multiple trees of a given order between consolidations, and using cascading cuts — they produced the Fibonacci heap in 1984, which lowered Dijkstra's and Prim's asymptotics. The binomial heap is thus both a practical mergeable queue in its own right and the intellectual seed of an entire family of amortized heaps.
Frequently asked questions
What is the time complexity of a binomial heap?
Union, insert, delete-min (extract-min), and decrease-key all run in O(log n) worst case. Find-min is O(log n) if you scan the root list, or O(1) if you cache a pointer to the minimum root. A single insert is O(log n) worst case but O(1) amortized over a sequence of inserts, because each carry in the binary-counter union is paid for by a prior linking step. Space is O(n) with O(1) overhead per node (parent, child, sibling, degree).
How does merging two binomial heaps work?
Union walks the two root lists in increasing order of tree order, exactly like adding two binary numbers bit by bit. Two trees of the same order B_k link into one tree of order B_(k+1) — that is the carry. Whenever three trees of the same order collide (one from each heap plus a carry), you keep one and link the other two. Because there are at most log2(n) distinct orders, the whole merge finishes in O(log n).
What is a binomial tree?
A binomial tree of order k, written B_k, is defined recursively: B_0 is a single node, and B_k is two B_(k-1) trees where one becomes the leftmost child of the other's root. B_k has exactly 2^k nodes, height k, and its root has k children which are themselves B_0, B_1, ..., B_(k-1). The number of nodes at depth d is the binomial coefficient C(k, d) — which is where the name comes from.
How is a binomial heap different from a binary heap?
A binary heap is a single complete tree stored in an array; it does insert and delete-min in O(log n) but cannot merge two heaps faster than O(n) because you must re-heapify the concatenation. A binomial heap is a pointer-based forest of binomial trees that merges in O(log n). If you never need to merge, a binary heap is faster in practice (contiguous memory, no pointers). If merging is a core operation, the binomial heap wins asymptotically.
Why can a binomial heap hold at most one tree of each order?
It is a structural invariant that makes the binary-counter analogy exact. A heap of n nodes is represented by the binary expansion of n: if bit k is set, the forest contains exactly one B_k (which has 2^k nodes). Since every integer has a unique binary representation, the multiset of tree orders is determined by n, and the root list has at most floor(log2(n)) + 1 trees.
How does decrease-key work in a binomial heap?
You lower the key at a node, then bubble it up toward the root of its own binomial tree, swapping with its parent whenever the heap order is violated — just like sift-up in a binary heap. The height of a B_k is k, so the walk is at most O(log n) swaps. Unlike the Fibonacci heap, there is no cascading cut; the tree shape is untouched, only key values move.
Who invented the binomial heap and why does it matter?
Jean Vuillemin introduced binomial heaps in 1978 in his paper 'A Data Structure for Manipulating Priority Queues'. It was the first heap to make merge cheap — O(log n) instead of O(n) — which made it a natural fit for algorithms that repeatedly combine priority queues. Mark Brown analyzed its amortized behavior in 1978, and in 1984 Fredman and Tarjan generalized the idea into the Fibonacci heap to speed up Dijkstra and Prim.