HUFFMAN Trees
HUFFMAN Trees
Trees Maintain the Fibonacci Property: Each tree in the heap follows the Fibonacci property,
where the number of children of any node is related to Fibonacci numbers, ensuring logarithmic
time complexity for many operations.
Nodes Can Have Arbitrary Number of Children: Unlike binary heaps, nodes in Fibonacci heaps
can have any number of children, allowing for a more flexible structure.
Lazy Consolidation Strategy: Consolidation of trees (merging trees of the same size) is delayed
until necessary, which helps in achieving better amortized time complexity for heap operations.
Time Complexity Analysis
Key Points for Fibonacci Heaps
Insert: Inserting a new element into a Fibonacci Heap is done in O(1) amortized time, as the new node is simply
added to the root list without requiring any further restructuring.
Find-Min: Finding the minimum element is O(1), as the heap maintains a pointer to the minimum node.
Extract-Min: Removing the minimum element involves consolidating the heap by restructuring and merging trees
with the same degree. This process takes O(logn) amortized time, where 𝑛n is the number of nodes in the heap.
Decrease-Key: Decreasing the key of a node is 𝑂(1) amortized time. This operation involves updating the key,
potentially cutting the node from its parent (if it violates the min-heap property), and cascading cuts up the tree
if necessary.
Delete: Deleting a node is achieved by decreasing its key to −∞ (or the minimum possible value), which ensures it
becomes the minimum node. Then, an Extract-Min operation is performed to remove it. The total time complexity
is 𝑂(log𝑛) amortized.
Merge: Merging two Fibonacci Heaps is 𝑂(1), as it only involves concatenating the root lists of the two heaps. The
minimum pointer is updated if necessary.
Implementation
class FibonacciHeap {
private:
Node* minNode;
#include <iostream> int nodeCount;
#include <cmath>
#include <unordered_map> void link(Node* y, Node* x) {
y->left->right = y->right;
struct Node {
y->right->left = y->left;
int key;
int degree;
Node* parent;
y->parent = x;
Node* child; if (x->child == nullptr) {
Node* left; x->child = y;
Node* right; y->left = y->right = y;
bool mark; } else {
y->right = x->child;
Node(int k) : key(k), degree(0), parent(nullptr), child(nullptr), left(this), y->left = x->child->left;
right(this), mark(false) {}
x->child->left->right = y;
};
x->child->left = y;
}
x->degree++;
y->mark = false;
}
void consolidate() { void cut(Node* x, Node* y) {
int maxDegree = static_cast<int>(std::log2(nodeCount)) + 1;
if (x->right == x) {
std::vector<Node*> degreeTable(maxDegree, nullptr);
y->child = nullptr;
} else {
std::vector<Node*> rootList;
x->right->left = x->left;
Node* current = minNode;
x->left->right = x->right;
if (current != nullptr) {
if (y->child == x) {
do {
y->child = x->right;
rootList.push_back(current);
current = current->right;
}
} while (current != minNode); }
} y->degree--;
x->parent = nullptr;
for (Node* w : rootList) { x->left = x->right = x;
Node* x = w; x->mark = false;
int d = x->degree; minNode->left->right = x;
while (degreeTable[d] != nullptr) { x->left = minNode->left;
Node* y = degreeTable[d]; x->right = minNode;
if (x->key > y->key) { minNode->left = x;
std::swap(x, y); if (x->key < minNode->key) {
} minNode = x;
link(y, x); }
degreeTable[d] = nullptr; }
d++;
} void cascadingCut(Node* y) {
degreeTable[d] = x; Node* z = y->parent;
} if (z != nullptr) {
if (!y->mark) {
minNode = nullptr; y->mark = true;
for (Node* x : degreeTable) { } else {
if (x != nullptr) {
cut(y, z);
if (minNode == nullptr || x->key < minNode->key) {
cascadingCut(z);
minNode = x;
}
}
}
}
}
}
}
z->left->right = z->right;
public: z->right->left = z->left;
FibonacciHeap() : minNode(nullptr), nodeCount(0) {} if (z == z->right) {
minNode = nullptr;
} else {
Node* insert(int key) {
minNode = z->right; int main() {
Node* newNode = new Node(key);
consolidate(); FibonacciHeap heap;
if (minNode == nullptr) { }
minNode = newNode; nodeCount--;
Node* n1 = heap.insert(10);
} else { }
Node* n2 = heap.insert(20);
minNode->left->right = newNode;
int minValue = z->key;
Node* n3 = heap.insert(5);
newNode->left = minNode->left;
delete z; Node* n4 = heap.insert(8);
newNode->right = minNode;
minNode->left = newNode; return minValue;
} std::cout << "Extracted Min: " << heap.extractMin() << std::endl; //
if (key < minNode->key) {
Should print 5
minNode = newNode;
void decreaseKey(Node* x, int newKey) {
} if (newKey > x->key) { heap.decreaseKey(n2, 1);
} throw std::invalid_argument("New key is greater than current
std::cout << "Extracted Min: " << heap.extractMin() << std::endl; //
nodeCount++; key.");
Should print 1
return newNode; }
} x->key = newKey;
Node* y = x->parent; heap.deleteNode(n1);
int extractMin() { if (y != nullptr && x->key < y->key) {
cut(x, y); while (!heap.isEmpty()) {
Node* z = minNode;
cascadingCut(y); std::cout << "Extracted Min: " << heap.extractMin() << std::endl;
if (z != nullptr) {
} }
if (z->child != nullptr) { if (x->key < minNode->key) {
Node* child = z->child; minNode = x;
return 0;
do { }
}
Node* nextChild = child->right; }
minNode->left->right = child;
child->left = minNode->left; void deleteNode(Node* x) {
child->right = minNode; decreaseKey(x, INT_MIN);
extractMin();
minNode->left = child;
}
child->parent = nullptr;
child = nextChild; bool isEmpty() const {
} while (child != z->child); return minNode == nullptr;
} }
};
Research Papers and References
1. Original Paper
Reference:
Fredman, M. L., & Tarjan, R. E. (1987). Fibonacci heaps and their uses in improved network optimization
algorithms. Journal of the ACM, 34(3), 596-615.
Key Contributions:
Introduction of Fibonacci Heaps: This paper is foundational and introduces Fibonacci Heaps, a novel
data structure that supports a variety of operations with improved amortized time complexities.
Efficient Decrease-Key: The innovation was the O(1)O(1)O(1) amortized time for the decrease-key
operation, critical for algorithms like Dijkstra's and Prim's.
Improved Algorithms: Demonstrated how Fibonacci Heaps improve network optimization algorithms
(e.g., shortest paths and minimum spanning trees) by reducing their overall time complexity.
Theoretical Foundations: Provided detailed proofs for the amortized analysis of all operations,
particularly consolidation during extract-min.
2. Performance Analysis
Reference:
Brodal, G. S. (2013). A survey on priority queues. Space-Efficient Data Structures, Streams, and
Algorithms, 150-163.
Key Contributions:
Comparison with Other Priority Queues: Surveyed various priority queue implementations,
including binary heaps, binomial heaps, and Fibonacci heaps, to provide insights into their relative
performance.
Theoretical Strengths and Weaknesses: Highlighted the trade-offs of Fibonacci Heaps, including
their excellent amortized performance but higher overhead for practical use due to complex
implementations.
Applicability in Algorithms: Showed where Fibonacci Heaps excel (e.g., graph algorithms requiring
frequent decrease-key operations) and where simpler data structures might be preferable.
3. Practical Implementations
Reference:
Moret, B. M., & Shapiro, H. D. (1991). An empirical analysis of algorithms for constructing a minimum spanning tree.
Proceedings of the 2nd Workshop on Algorithms and Data Structures, 400-411.
Key Contributions:
Experimental Results: Conducted practical experiments to evaluate the performance of Fibonacci Heaps in real-
world scenarios.
Challenges in Implementation: Highlighted the complexity and overhead involved in implementing Fibonacci
Heaps, which can make them slower in practice than simpler alternatives like binary heaps.
Relevance to Minimum Spanning Trees: Analyzed how Fibonacci Heaps perform in Kruskal's and Prim's
algorithms for constructing minimum spanning trees.
Trade-Offs Between Theory and Practice: Discussed why the theoretical advantages of Fibonacci Heaps don't
always translate into better performance in practical applications.
Advantages and Disadvantages:
Conclusion
Fibonacci heaps are an advanced data structure with excellent theoretical
performance, particularly for operations like decrease-key, insert, and merge,
which are crucial in graph algorithms such as Dijkstra's shortest path and
Prim's minimum spanning tree. Their amortized time complexity significantly
outperforms simpler structures like binary heaps, making them a preferred
choice in scenarios requiring frequent key updates. However, the practical
drawbacks—such as complex implementation, higher memory usage, and poor
cache performance—often make them less effective in real-world applications.
Despite these challenges, Fibonacci heaps remain a valuable tool for scenarios
where the focus is on optimizing theoretical bounds rather than practical
execution speed.
Thank You