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.

Open visualization fullscreen ↗

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 presentBinary analogyResult at order kCarry to k+1
00 + 0emptynone
11 + 0keep itnone
21 + 1emptylink → Bk+1
3 (two + carry)1 + 1 + 1keep onelink 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

OperationBinary heapBinomial heapFibonacci heap
Find-minO(1)O(1) cachedO(1)
InsertO(log n)O(1) amortizedO(1) amortized
Delete-minO(log n)O(log n)O(log n) amortized
Decrease-keyO(log n)O(log n)O(1) amortized
Merge (union)O(n)O(log n)O(1) amortized
LayoutArray (contiguous)Pointer forestPointer forest
Constant factorsTinyModerateLarge

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 parent must be set to None, 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.