What is a Fibonacci Heap?

A Fibonacci Heap is a lazy, mergeable priority queue composed of a collection of heap-ordered trees. It is famous for supporting decrease-key and insert operations in O(1) amortized time, making it the mathematical foundation for optimizing algorithms like Dijkstra’s shortest path and Prim’s minimum spanning tree to complexity.

Explanation

  • A Fibonacci Heap relaxes the structural balance rules of binary heaps. Instead of maintaining a perfect binary tree, it delays consolidating trees until an extract-min operation occurs (lazy merging).
  • Multiple trees of varying degrees reside in a circular doubly-linked root list.

Real-World Analogy

  • Imagine a lazy office assistant.
  • When documents arrive, the assistant doesn’t file them in alphabetical folders immediately (which is expensive). Instead, they pile them onto a desk in a loose stack ( lazy insert).
  • Only when the boss asks for the lowest-numbered document (extract-min) does the assistant clean the entire desk, organizing all papers into neat folders of matching sizes (consolidation) to speed up future requests.

Why Fibonacci Heaps?

  • In graph algorithms like Dijkstra’s, the decrease-key operation is called far more frequently ( times) than extract-min ( times).
  • A standard Binary Heap requires for decrease-key, leading to total time.
  • Fibonacci heaps achieve amortized time for decrease-key, improving the bounds to , which is a huge speedup for dense graphs.

How It Works

Node Attributes

  • Every node contains:
    • key and value
    • Pointers: parent, child, left (sibling), right (sibling)
    • degree: The number of children.
    • mark: A boolean flag indicating whether the node has lost a child since it was made a child of another node. This is the secret to keeping tree heights logarithmic.

Lazy Merging & Root List

  • Inserting and melding simply link circular lists together without re-sorting or reorganizing.
Min Pointer -> [Root Node: 3] <-> [Root Node: 17] <-> [Root Node: 24]
                    /                      /
             [Child: 8]               [Child: 30]

Consolidation (during Extract Min)

  • When the minimum node is extracted, its children are moved to the root list.
  • Then, consolidation traverses the root list, merging trees of the same degree together.
  • When linking two trees, the one with the larger key becomes a child of the one with the smaller key.

Decrease Key & Cascading Cuts

  • When key is decreased:
    • If ‘s new key violates the heap property (smaller than its parent ), we cut from and move it to the root list.
    • We then perform a cascading cut on :
      • If is unmarked, mark it.
      • If is already marked, cut from its parent , move it to the root list, unmark it, and recursively check .

Time & Space Complexity

OperationAmortized ComplexityWorst-case ComplexitySpace Complexity
Insert
Find Min
Meld (Union)
Decrease Key auxiliary
Extract Min auxiliary

Implementation

  • Fibonacci Heap Implementation insert, decrease-key, cascading cuts, and consolidated extract-min.

    The following implementations show a lazy Fibonacci Heap with

import math
 
class FibonacciHeapNode:
    def __init__(self, key, value=None):
        self.key = key
        self.value = value if value is not None else key
        self.degree = 0
        self.parent = None
        self.child = None
        self.left = self
        self.right = self
        self.mark = False
 
class FibonacciHeap:
    def __init__(self):
        self.min_node = None
        self.num_nodes = 0
 
    def insert(self, key, value=None):
        """Insert a new node in O(1) time."""
        node = FibonacciHeapNode(key, value)
        if self.min_node is None:
            self.min_node = node
        else:
            self._add_to_root_list(node)
            if node.key < self.min_node.key:
                self.min_node = node
        self.num_nodes += 1
        return node
 
    def get_min(self):
        return self.min_node
 
    def merge(self, other_heap):
        """Merge two Fibonacci heaps in O(1) time."""
        new_heap = FibonacciHeap()
        new_heap.min_node = self.min_node
        
        if self.min_node and other_heap.min_node:
            self_min_next = self.min_node.right
            other_min_prev = other_heap.min_node.left
            
            self.min_node.right = other_heap.min_node
            other_heap.min_node.left = self.min_node
            
            self_min_next.left = other_min_prev
            other_min_prev.right = self_min_next
            
            if other_heap.min_node.key < self.min_node.key:
                new_heap.min_node = other_heap.min_node
        elif other_heap.min_node:
            new_heap.min_node = other_heap.min_node
            
        new_heap.num_nodes = self.num_nodes + other_heap.num_nodes
        return new_heap
 
    def extract_min(self):
        """Remove and return the minimum node in O(log n) amortized time."""
        z = self.min_node
        if z is not None:
            if z.child is not None:
                # Add children to root list
                children = self._get_nodes_in_list(z.child)
                for child in children:
                    self._add_to_root_list(child)
                    child.parent = None
            
            # Remove z from root list
            z.left.right = z.right
            z.right.left = z.left
            
            if z == z.right:
                self.min_node = None
            else:
                self.min_node = z.right
                self._consolidate()
            
            self.num_nodes -= 1
        return z
 
    def decrease_key(self, x, new_key):
        """Decrease key of node x to new_key in O(1) amortized time."""
        if new_key > x.key:
            raise ValueError("New key is greater than current key")
        x.key = new_key
        y = x.parent
        if y is not None and x.key < y.key:
            self._cut(x, y)
            self._cascading_cut(y)
        if x.key < self.min_node.key:
            self.min_node = x
 
    def _cut(self, x, y):
        # Remove x from child list of y
        if y.child == x:
            if x.right == x:
                y.child = None
            else:
                y.child = x.right
        
        # Remove x from sibling list
        x.left.right = x.right
        x.right.left = x.left
        y.degree -= 1
        
        # Add x to root list
        self._add_to_root_list(x)
        x.parent = None
        x.mark = False
 
    def _cascading_cut(self, y):
        z = y.parent
        if z is not None:
            if not y.mark:
                y.mark = True
            else:
                self._cut(y, z)
                self._cascading_cut(z)
 
    def _add_to_root_list(self, node):
        node.left = self.min_node
        node.right = self.min_node.right
        self.min_node.right.left = node
        self.min_node.right = node
 
    def _get_nodes_in_list(self, start):
        nodes = []
        curr = start
        while True:
            nodes.append(curr)
            curr = curr.right
            if curr == start:
                break
        return nodes
 
    def _consolidate(self):
        if self.num_nodes <= 0:
            return
        # Max degree upper bound log_phi(n)
        max_degree = int(math.log(self.num_nodes) / math.log(1.618)) + 2
        A = [None] * max_degree
 
        root_nodes = self._get_nodes_in_list(self.min_node)
        for w in root_nodes:
            x = w
            d = x.degree
            while d < len(A) and A[d] is not None:
                y = A[d]
                if x.key > y.key:
                    x, y = y, x
                self._link(y, x)
                A[d] = None
                d += 1
            if d < len(A):
                A[d] = x
 
        self.min_node = None
        for i in range(len(A)):
            if A[i] is not None:
                if self.min_node is None:
                    self.min_node = A[i]
                    A[i].left = A[i]
                    A[i].right = A[i]
                else:
                    self._add_to_root_list(A[i])
                    if A[i].key < self.min_node.key:
                        self.min_node = A[i]
 
    def _link(self, y, x):
        # Remove y from root list
        y.left.right = y.right
        y.right.left = y.left
        
        # Make y a child of x
        y.parent = x
        if x.child is None:
            x.child = y
            y.left = y
            y.right = 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
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <stdexcept>
 
struct FibonacciHeapNode {
    int key;
    int value;
    int degree;
    bool mark;
    FibonacciHeapNode* parent;
    FibonacciHeapNode* child;
    FibonacciHeapNode* left;
    FibonacciHeapNode* right;
 
    FibonacciHeapNode(int k, int val) {
        key = k;
        value = val;
        degree = 0;
        mark = false;
        parent = nullptr;
        child = nullptr;
        left = this;
        right = this;
    }
};
 
class FibonacciHeap {
private:
    FibonacciHeapNode* min_node;
    int num_nodes;
 
    void add_to_root_list(FibonacciHeapNode* node) {
        node->left = min_node;
        node->right = min_node->right;
        min_node->right->left = node;
        min_node->right = node;
    }
 
    void remove_from_sibling_list(FibonacciHeapNode* node) {
        node->left->right = node->right;
        node->right->left = node->left;
    }
 
    std::vector<FibonacciHeapNode*> get_nodes_in_list(FibonacciHeapNode* start) {
        std::vector<FibonacciHeapNode*> nodes;
        if (!start) return nodes;
        FibonacciHeapNode* curr = start;
        do {
            nodes.push_back(curr);
            curr = curr->right;
        } while (curr != start);
        return nodes;
    }
 
    void link_nodes(FibonacciHeapNode* y, FibonacciHeapNode* x) {
        remove_from_sibling_list(y);
        y->parent = x;
        if (!x->child) {
            x->child = y;
            y->left = y;
            y->right = 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;
    }
 
    void consolidate() {
        if (num_nodes <= 0) return;
        int max_degree = static_cast<int>(std::log(num_nodes) / std::log(1.618)) + 2;
        std::vector<FibonacciHeapNode*> A(max_degree, nullptr);
 
        std::vector<FibonacciHeapNode*> root_nodes = get_nodes_in_list(min_node);
        for (FibonacciHeapNode* w : root_nodes) {
            FibonacciHeapNode* x = w;
            int d = x->degree;
            while (d < max_degree && A[d] != nullptr) {
                FibonacciHeapNode* y = A[d];
                if (x->key > y->key) {
                    std::swap(x, y);
                }
                link_nodes(y, x);
                A[d] = nullptr;
                d++;
            }
            if (d < max_degree) {
                A[d] = x;
            }
        }
 
        min_node = nullptr;
        for (int i = 0; i < max_degree; ++i) {
            if (A[i] != nullptr) {
                if (!min_node) {
                    min_node = A[i];
                    min_node->left = min_node;
                    min_node->right = min_node;
                } else {
                    add_to_root_list(A[i]);
                    if (A[i]->key < min_node->key) {
                        min_node = A[i];
                    }
                }
            }
        }
    }
 
    void cut(FibonacciHeapNode* x, FibonacciHeapNode* y) {
        if (y->child == x) {
            if (x->right == x) {
                y->child = nullptr;
            } else {
                y->child = x->right;
            }
        }
        remove_from_sibling_list(x);
        y->degree--;
        add_to_root_list(x);
        x->parent = nullptr;
        x->mark = false;
    }
 
    void cascading_cut(FibonacciHeapNode* y) {
        FibonacciHeapNode* z = y->parent;
        if (z != nullptr) {
            if (!y->mark) {
                y->mark = true;
            } else {
                cut(y, z);
                cascading_cut(z);
            }
        }
    }
 
    void destroy_heap(FibonacciHeapNode* start) {
        if (!start) return;
        std::vector<FibonacciHeapNode*> nodes = get_nodes_in_list(start);
        for (FibonacciHeapNode* node : nodes) {
            destroy_heap(node->child);
            delete node;
        }
    }
 
public:
    FibonacciHeap() : min_node(nullptr), num_nodes(0) {}
 
    ~FibonacciHeap() {
        destroy_heap(min_node);
    }
 
    FibonacciHeapNode* insert(int key, int value) {
        FibonacciHeapNode* node = new FibonacciHeapNode(key, value);
        if (!min_node) {
            min_node = node;
        } else {
            add_to_root_list(node);
            if (node->key < min_node->key) {
                min_node = node;
            }
        }
        num_nodes++;
        return node;
    }
 
    FibonacciHeapNode* get_min() const {
        return min_node;
    }
 
    FibonacciHeapNode* extract_min() {
        FibonacciHeapNode* z = min_node;
        if (z != nullptr) {
            if (z->child != nullptr) {
                std::vector<FibonacciHeapNode*> children = get_nodes_in_list(z->child);
                for (FibonacciHeapNode* child : children) {
                    add_to_root_list(child);
                    child->parent = nullptr;
                }
            }
            remove_from_sibling_list(z);
            if (z == z->right) {
                min_node = nullptr;
            } else {
                min_node = z->right;
                consolidate();
            }
            num_nodes--;
        }
        return z;
    }
 
    void decrease_key(FibonacciHeapNode* x, int new_key) {
        if (new_key > x->key) {
            throw std::invalid_argument("New key is greater than current key");
        }
        x->key = new_key;
        FibonacciHeapNode* y = x->parent;
        if (y != nullptr && x->key < y->key) {
            cut(x, y);
            cascading_cut(y);
        }
        if (x->key < min_node->key) {
            min_node = x;
        }
    }
 
    bool is_empty() const {
        return num_nodes == 0;
    }
};

When to Use

Use Fibonacci Heaps When:

  • ✅ You are implementing shortest-path algorithms (like Dijkstra’s) or minimum spanning tree algorithms (like Prim’s) on large, dense graphs.
  • ✅ The number of decrease-key operations vastly outnumbers the number of extract-min operations.
  • ✅ You need a priority queue that supports fast meld/union operations in time.

Avoid When:

  • ❌ You only need basic priority queue operations (standard binary heaps have significantly lower constant-factor overheads and are easier to implement).
  • ❌ Memory footprint is extremely tight — Fibonacci heaps store four pointers per node (parent, child, left, right) and tracking flags, which consumes a lot of memory.

Variations & Related Concepts

  • Binary Heap: Simple array-backed heap with insertions, deletions, and decrease-keys.
  • Binomial Heap: A predecessor to Fibonacci heaps, structured as a set of binomial trees, supporting meld in time.
  • Pairing Heap: A self-adjusting heap variant with excellent practical performance, often preferred over Fibonacci heaps due to simpler code and smaller memory overhead, despite slightly higher theoretical bounds for decrease-key.

Key Takeaways

  • A Fibonacci Heap is a lazily evaluated priority queue structured as a collection of heap-ordered trees.
  • Inserting and merging heaps simply append nodes to a circular root list in time.
  • During extract_min, trees of equal degrees are consolidated (linked) in amortized time.
  • Decreasing a key cuts the node and cascadingly cuts parents that have lost a second child, ensuring the tree heights remain .
  • It is highly theoretical; although optimal for asymptotic limits ( for Dijkstra), its large constant factors mean binary or pairing heaps are often faster in practice.

More Learn

GitHub & Webs