C/C++ Arena

Step 5 of 6

A binary heap by hand

A binary heap is a tree with a clever trick: it's stored in a plain array, with no pointers at all. The tree is always complete (every level full except possibly the last, which fills from the left), so the nodes can be numbered level by level, and the parent/child links are just arithmetic:

index:    0   1   2   3   4   5
parent(i) = (i - 1) / 2      children(i) = 2i + 1, 2i + 2
           2                a = [2, 5, 3, 9, 6, 4]
         /   \
        5     3             children of index 1 (the 5): indexes 3 and 4 (9 and 6)
       / \   /              parent of index 5 (the 4): index 2 (the 3)
      9   6 4

In a min-heap, every parent is ≤ its children, so the minimum is always at index 0. (The tree is not sorted beyond that: 9 and 4 are on different branches, in no particular order.)

Here's sift up, printed step by step:

#include <iostream>
#include <utility>
#include <vector>

void print(const std::vector<int>& a) {
    for (int x : a) std::cout << x << " ";
    std::cout << "\n";
}

int main() {
    std::vector<int> a = {2, 5, 3, 9, 6, 4};
    a.push_back(1);                             // new element at index 6
    print(a);
    std::size_t i = a.size() - 1;
    while (i > 0 && a[i] < a[(i - 1) / 2]) {    // smaller than its parent?
        std::swap(a[i], a[(i - 1) / 2]);
        i = (i - 1) / 2;
        print(a);
    }
    std::cout << "min is " << a[0] << "\n";
}
2 5 3 9 6 4 1 
2 5 1 9 6 4 3 
1 5 2 9 6 4 3 
min is 1

Why it's O(log n)

A complete tree with n nodes has about log₂ n levels. Sift up moves one level per swap, and so does sift down, so both operations do at most log n swaps. This is exactly what's inside std::priority_queue.

Sift down for pop

After moving the last element to index 0 (and removing the last slot), repeat: find the smaller of the node's children (check that each child index is < size); if it's smaller than the node, swap and continue from there; otherwise stop. Swapping with the smaller child is what keeps the heap rule true for both branches.

Your turn: implement MinHeap::push, MinHeap::pop (assume non-empty) and top.

Previous: Level order with a queue Next: Challenge: a trie for autocomplete