Fibonacci Heap — Junior Level¶
One-line summary: A Fibonacci heap is a meldable priority queue that achieves
O(1)amortised time forinsert,meld,find-min, anddecrease-key, paying for it with a singleO(log n)extract-min. It is the asymptotically optimal heap for graph algorithms like Dijkstra and Prim — but its constant factors are so large that pairing heaps and indexed binary heaps usually beat it in practice.
Table of Contents¶
- Introduction
- Prerequisites
- Glossary
- Core Concepts
- Big-O Summary
- Real-World Analogies
- Pros & Cons
- Why It Beats Binary Heap on Paper
- Code Examples
- Coding Patterns
- Error Handling
- Performance Tips
- Best Practices
- Edge Cases & Pitfalls
- Common Mistakes
- Cheat Sheet
- Visual Animation
- Summary
- Further Reading
Introduction¶
The Fibonacci heap was introduced by Michael L. Fredman and Robert E. Tarjan in their 1984 paper "Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms" (Tarjan won a Turing Award in part for this work). It is one of the most famous data structures in computer science precisely because it answers a single very specific question:
Can we make
decrease-keyfaster thanO(log n)for a meldable priority queue?
A binary heap can't — it has to bubble the changed element up to its place, paying O(log n). The Fibonacci heap defers almost all rebalancing work and pays for it only when you extract-min. The result: insert, meld, find-min, and decrease-key all run in O(1) amortised time, while extract-min and arbitrary delete run in O(log n) amortised.
This unlocked tighter bounds for many graph algorithms:
| Algorithm | Binary heap | Fibonacci heap |
|---|---|---|
| Dijkstra | O((V + E) log V) | O(E + V log V) |
| Prim's MST | O(E log V) | O(E + V log V) |
On sparse graphs (E ≈ V) both are roughly the same. On dense graphs (E ≈ V²) the Fibonacci version is asymptotically better. But asymptotic and practical are not the same thing — and that gap is exactly why this data structure is famous to read about but rare to deploy.
At junior level you should be able to:
- Explain what it is — a forest of heap-ordered trees.
- Recognise why it matters — the
O(1)decrease-key. - Identify when it is the right tool — almost never in production, often in coursework and contests.
Prerequisites¶
- Binary heap — see
../01-binary-heap/junior.md. - Linked list — circular doubly-linked lists are everywhere in this structure.
- Amortised analysis — the basic idea of "average cost over a sequence of operations." A formal treatment lives in
professional.md. - Graph algorithms that use a PQ — Dijkstra and Prim — to appreciate the motivation.
Glossary¶
| Term | Meaning |
|---|---|
| Fibonacci heap | A meldable PQ that maintains a forest of trees with a lazy structure. |
| Root list | The circular doubly-linked list of all tree roots. |
| min pointer | A direct pointer to the smallest-key root, giving O(1) find-min. |
| Tree degree | The number of children of a node. |
| Marked node | A node whose first child has been cut. A second cut triggers a "cascading cut." |
| Cascading cut | When a marked node loses another child, it too is cut from its parent and becomes a root. |
| Lazy meld | Concatenate two root lists in O(1); no consolidation. |
| Consolidation | The cleanup step inside extract-min that links roots of equal degree until at most one root per degree remains. |
| Potential function | Φ = (#roots) + 2 × (#marked nodes). Used in amortised analysis. |
| Tarjan's link constraint | A node of degree k must have at least F_{k+2} descendants — hence the name "Fibonacci." |
Core Concepts¶
1. A forest, not a single tree¶
A Fibonacci heap is not one tree — it is a list of trees, each obeying the heap property internally. The min pointer keeps the global minimum reachable in O(1).
Every node has pointers to: parent, left sibling, right sibling, first child, plus a degree counter and a "marked" boolean.
2. Lazy insert and meld¶
Both insert(x) and meld(h1, h2) just splice into the root list. No restructuring, no rebalancing. That's where the O(1) comes from — you defer work.
insert(x): add x to root list; update min if needed
meld(h1,h2): concatenate root lists; min = min(h1.min, h2.min)
3. decrease-key by cutting¶
When a node x's key drops below its parent's, you cut x from its parent and move it to the root list — no sift-up. If x's parent was already marked, you cut the parent too (cascading cut). Marking captures the idea "this node has already lost one child; if it loses another, the structure is degrading and we should rebalance now."
The cascading-cut bound is what guarantees a node of degree k retains F_{k+2} descendants — the Fibonacci numbers, hence the name.
4. extract-min: the only expensive operation¶
Three steps:
- Remove the min root; promote its children to roots.
- Consolidate — repeatedly link roots of equal degree until at most one per degree remains.
- Walk the root list to find the new min.
Consolidation uses an auxiliary array indexed by degree. After consolidation the root list has at most log_φ(n) ≈ 1.44 log₂ n trees.
5. Why it works (intuitive)¶
You spend O(1) on every cheap op but accumulate "debt" in the root list and the marked set. The potential function Φ = (#roots) + 2 × (#marked) tracks that debt. When extract-min finally consolidates, the work done is paid for by the drop in Φ. The amortised cost balances out.
Big-O Summary¶
| Operation | Amortised | Worst case |
|---|---|---|
insert | O(1) | O(1) |
meld | O(1) | O(1) |
find-min | O(1) | O(1) |
decrease-key | O(1) | O(log n) |
extract-min | O(log n) | O(n) |
delete | O(log n) | O(n) |
The worst-case O(n) for a single extract-min happens when the root list is already huge from many lazy inserts; consolidation has to merge them all. Over a sequence it averages back down to O(log n).
Real-World Analogies¶
| Concept | Analogy |
|---|---|
| Root list | A messy desk: papers piled in arbitrary order. The most urgent one is on top (the min pointer). |
| Lazy insert | Tossing a new paper onto the desk without sorting. |
| Cascading cut | When two stacks have collapsed onto a folder, you take the whole folder out and start over with a clean group. |
| Consolidation | When you finally need to file everything, you group same-sized stacks and combine them once. |
| Decrease-key by cut | Promoting an item directly to "needs attention now" instead of walking it up through a hierarchy. |
The analogy breaks down on amortisation: real desks don't have a potential function that tracks how much filing you owe.
Pros & Cons¶
| Pros | Cons |
|---|---|
O(1) amortised decrease-key and insert — best known for many graph algorithms. | Constants are so large that pairing heaps usually beat it in practice. |
O(1) meld — competitive with leftist/pairing heaps. | Each node carries 6+ pointers and flags; high memory overhead. |
Asymptotically optimal: Dijkstra in O(E + V log V). | Implementation is notoriously bug-prone (cascading cuts, marked flags, consolidation array). |
| Beautiful, instructive amortised analysis. | Cache performance is poor — many small allocations, random pointer chasing. |
| Reference data structure in many algorithm courses. | Almost no production system ships it (Boost Graph Library does, optionally; most do not). |
When to use: algorithm theory, contests that allow it, situations where decrease-key dominates and you've measured Fibonacci heap winning. When NOT to use: anywhere a binary or pairing heap is fast enough — which is almost always.
Why It Beats Binary Heap on Paper¶
In Dijkstra you do O(V) extract-mins and up to O(E) decrease-keys.
Binary heap: V·O(log V) + E·O(log V) = O((V + E) log V)
Fibonacci: V·O(log V) + E·O(1) = O(E + V log V)
When E = Ω(V log V) the Fibonacci version saves a logarithmic factor. For dense graphs that is real. For sparse graphs (E ≈ V) it is essentially the same.
Code Examples¶
A textbook Fibonacci heap implementation is several hundred lines. Below is a minimal teaching version that supports insert, min, extract_min, and decrease_key. Read it once, understand the lazy-insert idea, then in production always reach for a library or a binary heap.
Example: Minimal Fibonacci heap¶
Python¶
import math
class FibNode:
__slots__ = ("key", "value", "parent", "child", "left", "right", "degree", "mark")
def __init__(self, key, value=None):
self.key, self.value = key, value
self.parent = self.child = None
self.left = self.right = self # circular self-loop
self.degree = 0
self.mark = False
class FibHeap:
def __init__(self):
self.min = None
self.n = 0
def insert(self, key, value=None):
node = FibNode(key, value)
self._add_to_root(node)
if self.min is None or key < self.min.key:
self.min = node
self.n += 1
return node
def find_min(self):
return None if self.min is None else (self.min.key, self.min.value)
def extract_min(self):
z = self.min
if z is None:
return None
# Promote z's children to root list.
if z.child:
children = list(self._siblings(z.child))
for c in children:
c.parent = None
self._add_to_root(c)
# Remove z from root list.
z.left.right, z.right.left = z.right, z.left
if z is z.right:
self.min = None
else:
self.min = z.right
self._consolidate()
self.n -= 1
return z.key, z.value
def decrease_key(self, node, new_key):
if new_key > node.key:
raise ValueError("new key is larger")
node.key = new_key
parent = node.parent
if parent and node.key < parent.key:
self._cut(node, parent)
self._cascading_cut(parent)
if node.key < self.min.key:
self.min = node
# ----- helpers -----
def _siblings(self, start):
node = start
while True:
yield node
node = node.right
if node is start:
return
def _add_to_root(self, node):
node.parent = None
if self.min is None:
node.left = node.right = node
else:
node.left = self.min
node.right = self.min.right
self.min.right.left = node
self.min.right = node
def _link(self, y, x):
# remove y from root list
y.left.right, y.right.left = y.right, y.left
# make y a child of x
y.parent = x
y.left = y.right = y
if x.child is None:
x.child = y
else:
y.left = x.child
y.right = x.child.right
x.child.right.left = y
x.child.right = y
x.degree += 1
y.mark = False
def _consolidate(self):
max_deg = int(math.log2(self.n) * 2) + 2
a = [None] * max_deg
roots = list(self._siblings(self.min))
for w in roots:
x, d = w, w.degree
while a[d]:
y = a[d]
if y.key < x.key:
x, y = y, x
self._link(y, x)
a[d] = None
d += 1
a[d] = x
self.min = None
for node in a:
if node:
if self.min is None:
node.left = node.right = node
self.min = node
else:
self._add_to_root(node)
if node.key < self.min.key:
self.min = node
def _cut(self, node, parent):
# remove node from parent's child list
if node.right is node:
parent.child = None
else:
node.left.right, node.right.left = node.right, node.left
if parent.child is node:
parent.child = node.right
parent.degree -= 1
self._add_to_root(node)
node.mark = False
def _cascading_cut(self, y):
z = y.parent
if z:
if not y.mark:
y.mark = True
else:
self._cut(y, z)
self._cascading_cut(z)
if __name__ == "__main__":
h = FibHeap()
a = h.insert(5, "a")
b = h.insert(3, "b")
c = h.insert(8, "c")
h.decrease_key(c, 1)
print(h.extract_min()) # (1, 'c')
print(h.extract_min()) # (3, 'b')
print(h.extract_min()) # (5, 'a')
Go¶
// A teaching-grade Fibonacci heap. See the Python source for the same algorithm
// commented in full; the Go translation reproduces the structure faithfully.
package main
import (
"fmt"
"math"
)
type FibNode struct {
Key int
Value interface{}
Parent *FibNode
Child *FibNode
Left *FibNode
Right *FibNode
Degree int
Mark bool
}
type FibHeap struct {
Min *FibNode
N int
}
func New() *FibHeap { return &FibHeap{} }
func (h *FibHeap) Insert(key int, val interface{}) *FibNode {
n := &FibNode{Key: key, Value: val}
n.Left, n.Right = n, n
h.addToRoot(n)
if h.Min == nil || key < h.Min.Key {
h.Min = n
}
h.N++
return n
}
func (h *FibHeap) FindMin() *FibNode { return h.Min }
func (h *FibHeap) ExtractMin() *FibNode {
z := h.Min
if z == nil {
return nil
}
if z.Child != nil {
c := z.Child
for {
next := c.Right
c.Parent = nil
h.addToRoot(c)
if next == z.Child {
break
}
c = next
}
}
z.Left.Right = z.Right
z.Right.Left = z.Left
if z == z.Right {
h.Min = nil
} else {
h.Min = z.Right
h.consolidate()
}
h.N--
return z
}
func (h *FibHeap) addToRoot(n *FibNode) {
n.Parent = nil
if h.Min == nil {
n.Left, n.Right = n, n
return
}
n.Left = h.Min
n.Right = h.Min.Right
h.Min.Right.Left = n
h.Min.Right = n
}
func (h *FibHeap) link(y, x *FibNode) {
y.Left.Right = y.Right
y.Right.Left = y.Left
y.Parent = x
y.Left, y.Right = y, y
if x.Child == nil {
x.Child = y
} else {
y.Left = x.Child
y.Right = x.Child.Right
x.Child.Right.Left = y
x.Child.Right = y
}
x.Degree++
y.Mark = false
}
func (h *FibHeap) consolidate() {
maxDeg := int(math.Log2(float64(h.N))*2) + 2
a := make([]*FibNode, maxDeg)
roots := []*FibNode{}
cur := h.Min
if cur != nil {
for {
roots = append(roots, cur)
cur = cur.Right
if cur == h.Min {
break
}
}
}
for _, w := range roots {
x, d := w, w.Degree
for a[d] != nil {
y := a[d]
if y.Key < x.Key {
x, y = y, x
}
h.link(y, x)
a[d] = nil
d++
}
a[d] = x
}
h.Min = nil
for _, n := range a {
if n == nil {
continue
}
if h.Min == nil {
n.Left, n.Right = n, n
h.Min = n
} else {
h.addToRoot(n)
if n.Key < h.Min.Key {
h.Min = n
}
}
}
}
func main() {
h := New()
h.Insert(5, "a")
h.Insert(3, "b")
h.Insert(8, "c")
fmt.Println(h.ExtractMin().Key, h.ExtractMin().Key, h.ExtractMin().Key)
// 3 5 8
}
Java¶
// Teaching-grade Fibonacci heap. See Python for full comments.
public class FibHeap {
static class Node {
int key;
Object value;
Node parent, child, left, right;
int degree;
boolean mark;
Node(int k, Object v) { key = k; value = v; left = right = this; }
}
Node min;
int n;
public Node insert(int key, Object value) {
Node node = new Node(key, value);
addToRoot(node);
if (min == null || key < min.key) min = node;
n++;
return node;
}
public Node findMin() { return min; }
public Node extractMin() {
Node z = min;
if (z == null) return null;
if (z.child != null) {
Node c = z.child;
while (true) {
Node next = c.right;
c.parent = null;
addToRoot(c);
if (next == z.child) break;
c = next;
}
}
z.left.right = z.right;
z.right.left = z.left;
if (z == z.right) {
min = null;
} else {
min = z.right;
consolidate();
}
n--;
return z;
}
private void addToRoot(Node x) {
x.parent = null;
if (min == null) {
x.left = x.right = x;
return;
}
x.left = min;
x.right = min.right;
min.right.left = x;
min.right = x;
}
private void link(Node y, Node x) {
y.left.right = y.right;
y.right.left = y.left;
y.parent = x;
y.left = y.right = y;
if (x.child == null) x.child = y;
else {
y.left = x.child;
y.right = x.child.right;
x.child.right.left = y;
x.child.right = y;
}
x.degree++;
y.mark = false;
}
private void consolidate() {
int maxDeg = (int)(Math.log(n) / Math.log(2) * 2) + 2;
Node[] a = new Node[maxDeg];
java.util.List<Node> roots = new java.util.ArrayList<>();
Node cur = min;
do { roots.add(cur); cur = cur.right; } while (cur != min);
for (Node w : roots) {
Node x = w; int d = x.degree;
while (a[d] != null) {
Node y = a[d];
if (y.key < x.key) { Node t = x; x = y; y = t; }
link(y, x);
a[d] = null;
d++;
}
a[d] = x;
}
min = null;
for (Node m : a) {
if (m == null) continue;
if (min == null) { m.left = m.right = m; min = m; }
else { addToRoot(m); if (m.key < min.key) min = m; }
}
}
}
Coding Patterns¶
Pattern 1: Dijkstra with O(1) decrease-key¶
For each unsettled vertex, keep a handle returned from `insert`.
On relaxation, call `decrease_key(handle, new_dist)`.
In Python the FibHeap.insert above returns a FibNode — that's the handle.
Pattern 2: Online tournament¶
A streaming "best of so far" reporter that wants O(1) insert and O(1) find-min. Fibonacci is competitive with binary heap here, with the advantage of O(1) meld for combining streams.
Pattern 3: Avoid in practice¶
Honestly the most common pattern when you reach for a Fibonacci heap is to not — fall back to a binary heap or pairing heap and verify your benchmark first. Fibonacci heaps are taught primarily as an analysis tool, not a tool you reach for.
Error Handling¶
| Error | Cause | Fix |
|---|---|---|
decrease_key raises "new key is larger" | Programmer error. | Never call decrease_key with an equal or larger value. |
extract_min returns None repeatedly | Heap is empty. | Check find_min() first. |
| Cascading cut corrupts the root list | Forgot to clear mark after promotion. | Always set node.mark = false when adding to root. |
Infinite loop in consolidate | Sibling pointers broken. | Always restore node.left = node.right = node when isolating. |
Performance Tips¶
- Don't. Use a binary heap or a pairing heap unless your profile says otherwise.
- If you must, allocate nodes from a pool; the per-node
mallocis the dominant cost. - Pre-size the consolidation array based on
log_φ(n)rather than recomputing on everyextract_min.
Best Practices¶
- Document
decrease_keysemantics next to theinsertsignature — the handle return is non-obvious. - Wrap the structure behind an interface so you can swap in a binary heap to compare.
- Validate the invariant in tests: after each op, every parent dominates its children and every marked node has had exactly one child cut.
Edge Cases & Pitfalls¶
- Empty heap —
extract_minanddecrease_keymust guard againstmin == None. - Single element —
extract_minsimply nulls outmin. - Equal keys — perfectly fine; order between equals is unspecified.
- Inserting
nelements without an extract —extract_minpays for the consolidation; first one isO(n)worst case. - Marked node becomes root — always clear
markwhen promoting.
Common Mistakes¶
- Implementing
decrease_keyby sifting up — that defeats the entire purpose; you've reinvented a binary heap. - Forgetting cascading cut — accuracy holds but amortised bound silently regresses to
O(log n). - Maintaining a hash map from key to node — keys can repeat, mapping should be from handle to node.
- Not zeroing the
markfield when promoting a node — corrupts later cascades. - Sharing nodes across threads without locking — invariants break in races.
Cheat Sheet¶
| Operation | Amortised | Notes |
|---|---|---|
insert | O(1) | Splice into root list. |
meld | O(1) | Concatenate two root lists. |
find-min | O(1) | Single pointer. |
decrease-key | O(1) | Cut + cascading cut. |
extract-min | O(log n) | Consolidate by degree. |
delete | O(log n) | decrease-key to −∞, then extract-min. |
Potential function: Φ = #roots + 2 × #marked.
Visual Animation¶
See
animation.htmlfor an interactive view of: - lazyinsertand the growing root list, -decrease-keywith cascading cuts, -extract-minconsolidation by degree.
Summary¶
The Fibonacci heap is the asymptotically-best meldable priority queue for graph algorithms — O(1) amortised for everything except extract-min. It earns its name from the Fibonacci-like growth bound on node descendants under cascading cuts, and it earns its reputation as the textbook answer to "can decrease-key be faster than log n?" Just don't expect it to beat a humble binary heap in your benchmark unless your decrease-key load is enormous.
Further Reading¶
- Fredman & Tarjan, Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms, JACM 34(3), 1987.
- Introduction to Algorithms (CLRS), Chapter 19 — Fibonacci heaps.
- Knuth, TAOCP Vol. 3, §5.2.3 — heap origins, plus exercises that motivate Fibonacci.
- Boost Graph Library —
fibonacci_heapsource code. - Tarjan's amortised analysis lecture notes (Princeton).