C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
167: Implementing a Min-Heap and Max-Heap in C++
I've seen this specific bug more times than I can count when developers first try to implement their own heap. You've got your array indices correct, your push logic seems fine, but the moment you start popping elements, the "Min-Heap" starts returning values that are definitely not the minimum. Let's look at a typical broken siftDown implementation.
// BROKEN MIN-HEAP SIFTDOWN
void siftDown(int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int smallest = index;
if (left < size && data[left] < data[smallest]) {
smallest = left;
}
if (right < size && data[right] < data[smallest]) {
smallest = right;
}
if (smallest != index) {
std::swap(data[index], data[smallest]);
siftDown(smallest);
}
}
The 'Wrong Child' Swap Bug
At first glance, this looks correct. It checks the left child, then the right child, and swaps with the smallest. But here is the subtle disaster: if both children are smaller than the parent, but the right child is actually the absolute minimum of the three, this code might work—but if you slightly tweak the logic or the comparison operators, it's easy to end up swapping the parent with the left child first, even if the right child was the better candidate for the root. Wait, actually, the logic above is mostly correct for a Min-Heap, but the real-world mistake I'm talking about is when learners do this:
// THE ACTUAL COMMON MISTAKE
if (left < size && data[left] < data[index]) {
std::swap(data[left], data[index]);
siftDown(left);
return; // Exits early!
}
if (right < size && data[right] < data[index]) {
std::swap(data[right], data[index]);
siftDown(right);
}
Do you see the problem? The second snippet swaps with the left child the moment it finds any value smaller than the parent. It ignores the right child entirely if the left child was also smaller. This violates the heap property because the right child might have been even smaller than the left child. To maintain a valid Min-Heap, the absolute smallest of the parent and its two children must end up at the parent position.
Navigating the Array-Based Tree
Since a heap is a complete binary tree, we don't use pointers. We use a std::vector. I find it's easiest to remember the index math like this: if your current node is at i, its children are at 2i + 1 and 2i + 2. To go backward to the parent, you use (i - 1) / 2. Integer division handles the floor automatically, which is exactly what we want.
The Sift-Up Process for Insertions
When you add an element, you can't just drop it at the top; that would break the heap order immediately. Instead, you append it to the end of the vector (the bottom-rightmost leaf of the tree) and "bubble" it up. I call this siftUp. You compare the new element with its parent; if it's smaller (for a Min-Heap), you swap them and repeat the process until the element finds its rightful place or hits the root.
void siftUp(int index) {
while (index > 0) {
int parent = (index - 1) / 2;
if (data[index] < data[parent]) {
std::swap(data[index], data[parent]);
index = parent;
} else {
break;
}
}
}
Maintaining Order during Deletions
Deleting the root is where the real work happens. You can't just remove the top element, or you'll have a hole in your tree. The trick I've always used is: swap the root with the very last element in the vector, pop the last element off (which is now the old root), and then siftDown the new root to its correct position. This preserves the "complete tree" structure while restoring the heap property.
Toggling Between Min and Max Heaps
The only structural difference between a Min-Heap and a Max-Heap is the comparison operator. If you're writing this for a production system, don't write two separate classes. I recommend using a template with a comparator, similar to how std::priority_queue works. By passing in std::less<T> or std::greater<T>, you can switch the entire behavior of your heap without changing a single line of the sifting logic.
📋 Practical Task
Build a Priority-Based Task Scheduler
Instead of a simple heap of integers, create a Task struct that contains a std::string description and an int priority. Implement a Max-Heap that stores these Task objects.
Your implementation must include:
- A
push(Task t)method that maintains the Max-Heap property based on thepriorityvalue. - A
pop()method that removes and returns the task with the highest priority. - A
peek()method to view the top task without removing it.
Test your scheduler by inserting tasks in a random order (e.g., "Fix typo" priority 1, "Server crash" priority 10, "Update docs" priority 3) and verifying that "Server crash" is the first task popped from the heap.
There are no comments for now.