The site's twenty-first node-linked trees entry, and the
one genuine gap left in its own mergeable-priority-queue family: Fibonacci Heap,
Pairing Heap,
Leftist Heap, and
Skew Heap all answer the same question — insert,
find-min, extract-min, merge two heaps into one — but not one of those four pages ever names the
structure that came first. J. Vuillemin invented the binomial heap (he called it a binomial queue)
in 1978, six years before Fredman and Tarjan built the Fibonacci heap specifically to go faster at
one operation this page also builds: decrease-key. A binomial heap gives every single
operation — not just the average over a long run — an O(log n) ceiling, including
decrease-key. That is the one combination none of the four newer pages offer: Fibonacci
and Pairing Heap only get decrease-key to O(1) by settling for an
amortized bound, and Leftist and Skew Heap sidestep the question entirely by not building
decrease-key at all. This page is the "no free lunch, no shortcuts" member of the
family — every call, on its own, provably bounded — and its shape does something none of the other
four can claim either: the forest of trees it maintains is, digit for digit, the binary
representation of how many elements it holds.
Insert a few values and watch the forest below the controls — and watch the binary readout in
the log line under it. Every heap holds at most one tree of each degree, a tree of
degree k always has exactly 2^k nodes, and a tree exists at degree
d if and only if bit d of the current size is set. Insert enough values to
watch a run of trees collapse into one bigger tree — that's a carry, the same thing that happens
when a binary counter rolls from 0111 to 1000. Click any node to select
it, then Decrease Key to lower it below its parent and watch it bubble straight up
its own tree, swapping places with each ancestor along the way, until it either reaches the root or
stops being smaller than its new parent.
d, compare their roots; the smaller becomes the parent and the larger is prepended to
its child list, making a single tree of degree d+1. One comparison, one pointer move,
O(1).A, one from B, and a possible carry from the degree below), and
link resolves any pair of them into a carry for the next degree up — the same
zero/one/two/three-input logic a hardware full adder uses for a bit and its carry. O(log
n) worst case, one pass over at most O(log n) degrees.merge the current forest with a brand-new
single-node degree-0 tree holding x. Exactly the same operation as adding 1 to a
binary counter, carries and all.O(1).0 through k-1 (a
degree-k tree's children, in the order link built them, are exactly one
tree of each smaller degree) — in other words, removing one root hands back an entire ready-made
sub-forest for free. merge that sub-forest into whatever's left of the main forest.
O(log n) worst case: scanning up to O(log n) roots for the minimum, then
one merge.x's key in place, then walk
upward: while x has a parent whose key is now larger, swap the two keys
(not the nodes — the tree's shape never changes) and continue from the parent. Stops at the root or
the first ancestor that's still smaller. No marks, no cuts, no cascading — just a straight climb
bounded by the tree's own height, O(log n) worst case on every single call.decreaseKey(x, -Infinity) followed by extractMin().Every other mergeable heap on this site earns its bound from a per-node invariant (Leftist Heap's
null-path-length field) or a potential-function argument over an amortized sequence (Fibonacci
Heap's marks, Skew Heap's heavy/light argument). A binomial heap earns its bound from something
much more literal: at any moment, its forest holds at most one tree per degree, and
a tree of degree d always holds exactly 2^d nodes — so the presence or
absence of a tree at each degree is exactly one bit of the heap's size, written in binary, from
degree 0 (the ones bit) upward. A heap holding 13 elements (binary 1101) has, and can
only have, trees of degree 3, 2, and 0. There's no other valid shape for 13 elements, ever — the
forest's shape isn't a design choice made by whichever sequence of inserts and merges built it, it's
forced entirely by n itself.
Why merge behaves like binary addition. Combining two forests means combining
two binary numbers, and combining numbers digit-by-digit-with-carry is precisely what
merge does: at each degree, link is the operation that turns "two trees
of degree d" into "one tree of degree d+1, carried up" — the exact
shape of a full adder's carry-out. Because both input forests have at most O(log n)
trees, and the whole pass is one linear walk over degrees with O(1) work each,
merge is O(log n), full stop — not an average, an actual bound on every
single call, because the number of degrees a forest can span is bounded on every single call, not
just typically.
Why insert is worst-case O(log n) but amortized O(1). This is the exact same
accounting Fibonacci Heap's own lazy inserts and
Dynamic Array's doubling resize both rely on, just
applied to a binary counter instead of a growing array: incrementing 0111 to
1000 ripples through three carries, but most increments touch only the last bit or two.
Charge each carry to the insert that originally set the bit it's now clearing, and the books balance
across any sequence — see the measured link counts in Pitfalls for the exact
numbers, not just the argument. decrease-key gets no equivalent discount. Its cost
is the height of whichever tree the target node sits in, and nothing about repeated calls makes that
height shrink or spreads the cost elsewhere the way a carry chain's cost gets paid down by the
inserts that caused it — every single call really does cost up to O(log n), which is
exactly why this page's own headline claim is a genuine worst-case bound, not an amortized one
dressed up to look like one.
class BNode {
constructor(key) {
this.key = key;
this.degree = 0;
this.parent = null;
this.children = []; // children[0] is the most-recently-attached (highest-degree) child
}
}
class BinomialHeap {
#trees = []; // sparse array indexed by degree; #trees[d] is a root or undefined
#size = 0;
#min = null;
get size() { return this.#size; }
peek() { return this.#min ? this.#min.key : null; }
#refreshMin() {
this.#min = null;
for (const t of this.#trees) if (t && (this.#min === null || t.key < this.#min.key)) this.#min = t;
}
static #link(y, x) { y.parent = x; x.children.unshift(y); x.degree++; return x; }
static #merge(a, b) {
const len = Math.max(a.length, b.length) + 1;
const out = new Array(len).fill(undefined);
let carry;
for (let d = 0; d < len; d++) {
const bits = [a[d], b[d], carry].filter(Boolean);
carry = undefined;
if (bits.length === 0) { out[d] = undefined; }
else if (bits.length === 1) { out[d] = bits[0]; }
else if (bits.length === 2) {
out[d] = undefined;
const [p, q] = bits;
carry = p.key <= q.key ? BinomialHeap.#link(q, p) : BinomialHeap.#link(p, q);
} else { // all three slots occupied — keep one, link the other two
out[d] = bits[0];
const [, p, q] = bits;
carry = p.key <= q.key ? BinomialHeap.#link(q, p) : BinomialHeap.#link(p, q);
}
}
while (out.length && out[out.length - 1] === undefined) out.pop();
return out;
}
insert(key) {
const node = new BNode(key);
this.#trees = BinomialHeap.#merge(this.#trees, [node]);
this.#size++;
this.#refreshMin();
return node;
}
extractMin() {
if (this.#size === 0) return null;
const min = this.#min;
const childTrees = [];
for (const c of min.children) { c.parent = null; childTrees[c.degree] = c; }
const remaining = this.#trees.slice();
remaining[min.degree] = undefined;
this.#trees = BinomialHeap.#merge(remaining, childTrees);
this.#size--;
this.#refreshMin();
return min.key;
}
decreaseKey(node, newKey) {
if (newKey >= node.key) return false;
node.key = newKey;
let cur = node;
while (cur.parent && cur.parent.key > cur.key) {
const p = cur.parent;
[cur.key, p.key] = [p.key, cur.key];
cur = p;
}
if (this.#min === null || cur.key < this.#min.key) this.#min = cur;
return true;
}
}
Verified with a seeded, reproducible stress harness: 20,000 randomized trials of 40 interleaved
insert / extract-min / decrease-key calls each (800,000 operations total), checking after
every single operation that (1) every tree's heap-order property holds top to bottom, (2)
every tree of degree d has exactly d children whose degrees are exactly
{0, 1, ..., d-1} — the structural definition of a binomial tree, not just heap order —
(3) a tree exists at degree d if and only if bit d of the heap's current
size is set, checked against the real binary representation of n at every step, (4) the
full multiset of live keys matches an independent oracle array's, and (5) the cached min pointer's
key matches that same oracle's true minimum. Zero failures across all 800,000 operations. The
harness was self-tested first against two deliberately broken copies of this exact model — one that
always links the second tree under the first regardless of which key is smaller (19,984 of 20,000
trials failed, almost always on the very first merge), and one where decreaseKey never
refreshes the min pointer after bubbling a node past the current minimum (7,070 of 20,000 trials
failed) — both caught immediately, confirming the checks have real teeth before trusting a clean run
on the real model to mean anything.
The shipped demo above runs its own copy of this logic (adapted to log each step and expose live
node identities for click-to-select, instead of returning silently) — extracted the actual functions
out of this page's own <script> via Node's vm module and re-ran an
equivalent 8,000-trial harness (over 300,000 more operations) driving the real Insert / Extract Min /
Decrease Key button handlers through simulated clicks, same five checks as above. Zero mismatches.
Insert is not worst-case O(1) here, unlike Fibonacci Heap. Fibonacci Heap's
insert is genuinely constant on every call — it never merges anything until the next
extractMin forces it to. This structure has no such lazy escape hatch: every insert
immediately merges, so a long run of trailing 1-bits in the current size means a long carry chain
right now. Directly instrumenting the reference model confirms the exact shape of this cost rather
than asserting it: inserting one more element into a heap already sized at 3, 7, 15, 31, 63,
127, 255, 1023, and 32,767 (each one less than a power of two — every bit set)
triggers exactly 2, 3, 4, 5, 6, 7, 8, 10, and 15 link operations
respectively, matching the count of trailing 1-bits exactly at every size tested — a real,
measured O(log n) worst case, not a theoretical one that never actually shows up.
decrease-key's bound has no amortized rescue, and that's structural, not a missed
optimization. Measured directly: build a single tree of degree k (exactly
2^k elements, one tree, no other shape is possible at that size) for
k = 4, 8, 12, and 16, then find the node buried deepest in it. It sits
exactly 4, 8, 12, and 16 levels down every time — precisely k,
never less — and decreasing that one node's key bubbles it the full k levels to become
the new root every time. Unlike this page's own insert cost, there is no cheaper amortized story to
tell here: nothing about a sequence of prior operations pays this cost down, because the tree's
shape never changes on a decrease-key call the way a lazy insert's un-consolidated root list does.
This is exactly the structural cost Fibonacci Heap's cut-and-cascade machinery exists to avoid —
by tearing the tree apart on every decrease-key instead of preserving it, Fibonacci Heap trades this
page's honest worst-case guarantee for an amortized O(1) one.
A count-of-three merge step is easy to get wrong by assuming only two trees can ever
collide at one degree. A first-draft implementation that only checks a[d]
against b[d] and forgets a carry can arrive at the same degree simultaneously — the
genuine three-way case a full binary-addition analogy demands — silently drops one of the three
trees on the floor instead of correctly keeping one and carrying two. The stress harness's key-set
check (item 4 above) catches this immediately, but only because it compares the full
multiset against an oracle after every call; a check that only verifies heap order or degree shape
on the surviving trees would never notice a whole tree quietly vanishing.
decrease-key go faster if a structure is willing to
settle for an amortized guarantee instead of a per-call one? Every later entry in this family —
Fibonacci, Pairing,
Leftist, and
Skew Heap — is answering some variant of the tradeoff
this page's own forest-of-trees design first posed.O(1) amortized decrease-key is faster on average, but a single call can
still cost up to O(log n) in the worst case if enough marked ancestors have
accumulated — this page's own O(log n) is worse on average but never any worse than
that ceiling on any individual call, the same worst-case-over-average bargain
Leftist Heap already makes for merge, insert, and
extract-min, just extended to cover decrease-key too.O(1)" claims about
incrementing counters, resizing arrays, and inserting into this heap are all, underneath, the same
argument about carries.Time: link is O(1). merge is
O(log n) worst case, where n is the combined size of both heaps — one pass
over at most O(log n) degrees. insert is O(log n) worst case
(a full carry chain), O(1) amortized (see Why it works). peek is
O(1) via the cached min pointer. extractMin is O(log n) worst
case — scanning up to O(log n) roots plus one merge.
decreaseKey is O(log n) worst case, bounded by the height of the tree the
target node sits in, with no amortized discount (see Pitfalls). Space:
O(n) — one node per element, each carrying a parent pointer and a children list, the
same per-node overhead as every other tree-based heap on this site.
This site's guide, Choosing a Search
Tree, groups this entry with Fibonacci, Pairing, Leftist, and Skew Heap as answering a
different question than the guide's own seven comparison entries — a mergeable priority queue, not
an ordered set, with no general search for an arbitrary key. Among those five, it's
the oldest (Vuillemin, 1978, the structure Fibonacci Heap was built to beat) and the only one that
bounds every operation, including decreaseKey, worst-case rather than amortized — the
"no free lunch" combination none of the other four offer.