Complete binary tree with heap property. Priority queues.
Self-serve tutorial - low prerequisites, straightforward concepts.
A heap is a data structure that’s “just ordered enough” to always give you the smallest (or largest) element quickly—without paying the full cost of keeping everything perfectly sorted.
A (binary) heap is a complete binary tree that satisfies a local heap-order property (min-heap or max-heap). Stored in an array, it supports peek-extreme in O(1), insert in O(log n) via sift-up, and remove-extreme in O(log n) via sift-down. It’s the standard implementation of a priority queue and is a key building block for algorithms like Dijkstra (often with an important caveat: many libraries don’t support true decrease-key).
Often you don’t need a fully sorted list—you just need to repeatedly answer questions like:
A heap is designed for exactly this pattern: frequent insert and frequent remove the min/max.
A binary heap is a binary tree with two constraints:
This ordering is local, not global. In a min-heap, the root is the minimum element, but the rest of the tree is not necessarily sorted.
From the heap-order property:
So:
A heap does not support:
This node is Difficulty 2–3/5. Core heap operations are approachable (2/5), but using heaps inside graph algorithms (like Dijkstra) introduces subtleties (often closer to 3/5). This lesson keeps Dijkstra-related details optional/advanced.
The complete-tree shape makes heaps efficient because the tree height stays small.
If is the number of nodes, the height is (more precisely, about ).
That’s the key reason sift-up and sift-down are fast: they only walk a single path from a leaf to the root (or vice versa), and that path length is at most the height.
A binary heap is almost always stored in an array rather than explicit node pointers.
Because the tree is complete, nodes can be laid out level-by-level (breadth-first order) with no gaps.
For index (0-based), the relationships are:
These formulas come from how a complete binary tree packs into an array.
Suppose the heap array is:
Index: 0 1 2 3 4 5 6
Value: 2 5 3 9 7 4 8
Tree structure (conceptually):
| Operation | Heap (binary) | Unsorted array | Sorted array |
|---|---|---|---|
| peek-min | O(1) | O(n) | O(1) |
| insert | O(log n) | O(1) | O(n) |
| remove-min | O(log n) | O(n) | O(1) if removing from front, but maintaining sortedness costs insert O(n) |
Heaps sit in the “sweet spot”: they keep just enough structure to make min/max cheap, without making insertion too expensive.
The heap property is local (parent vs. children). That means when you modify the heap in a controlled way, you typically break the property in only one area.
Heaps exploit this: after an insert or remove, you repair the heap by swapping along one path.
There are two fundamental repair operations:
We’ll describe everything using a min-heap. For a max-heap, flip the comparisons.
To preserve the complete-tree shape, you must add the new node at the next available spot: the end of the array.
This may violate heap order (the new value might be smaller than its parent), but the violation is on the path from that new leaf to the root.
Let i be the index of the newly appended item.
While i > 0 and heap[i] < heap[parent(i)]:
Each swap moves the element one level closer to its correct place.
Each iteration moves up one level. There are at most O(log n) levels.
So insert is O(log n).
We need to remove the root (the minimum), but if we simply delete it, we would create a hole and break the complete-tree shape.
Instead:
Now the shape is preserved, but heap order may be violated at the root (the moved element might be too large compared to children).
Starting at i = 0:
Stop when:
Each swap moves down one level, and there are O(log n) levels.
So removeMin is O(log n).
peekMin() just returns heap[0] (if non-empty), so O(1).
If you’re given an arbitrary array and want to turn it into a heap:
The O(n) result can feel surprising. Intuition: most nodes are near the bottom, and sifting down from them is very cheap.
A standard bottom-up approach for an array of length n:
This is the core of heapsort as well.
A priority queue is an abstract data type (ADT) with operations like:
A binary heap is the most common implementation.
If you:
You get an in-place sorting algorithm with O(n log n) time and O(1) extra space (ignoring recursion).
Heapsort is not stable, but it has strong worst-case guarantees.
Dijkstra’s algorithm conceptually needs a priority queue that supports:
A binary heap can support decrease-key efficiently if you can find the element’s index in the heap array (often by maintaining a map from node → heap index).
Many standard libraries (e.g., Python’s heapq, Java’s PriorityQueue) do not provide a direct decrease-key operation.
Common workaround:
This keeps the code simple but changes the operational profile:
If you later unlock the Shortest Paths node, you’ll see how this affects correctness and runtime in detail.
We have a min-heap stored as an array:
heap = [2, 5, 3, 9, 7, 4, 8]
Insert x = 1 and show each swap using parent(i) = ⌊(i−1)/2⌋.
Append 1 to the end to preserve complete-tree shape.
New heap: [2, 5, 3, 9, 7, 4, 8, 1]
Index of 1 is i = 7.
Compute parent(7) = ⌊(7−1)/2⌋ = ⌊6/2⌋ = 3.
Compare heap[7] = 1 with heap[3] = 9.
Since 1 < 9, swap.
Heap becomes: [2, 5, 3, 1, 7, 4, 8, 9]
Update i = 3.
Compute parent(3) = ⌊(3−1)/2⌋ = ⌊2/2⌋ = 1.
Compare heap[3] = 1 with heap[1] = 5.
Since 1 < 5, swap.
Heap becomes: [2, 1, 3, 5, 7, 4, 8, 9]
Update i = 1.
Compute parent(1) = ⌊(1−1)/2⌋ = 0.
Compare heap[1] = 1 with heap[0] = 2.
Since 1 < 2, swap.
Heap becomes: [1, 2, 3, 5, 7, 4, 8, 9]
Update i = 0.
Stop because i = 0 (at the root). The heap-order property is restored.
Insight: Insert only disturbed heap order along one leaf-to-root path. Sift-up fixes it with at most O(log n) swaps.
Start with the min-heap:
heap = [1, 2, 3, 5, 7, 4, 8, 9]
Perform removeMin() and show each swap using left(i)=2i+1 and right(i)=2i+2.
The minimum is at the root: min = heap[0] = 1.
To remove it while preserving shape, move the last element (9) to the root and pop the last slot.
Intermediate heap: [9, 2, 3, 5, 7, 4, 8]
Now heap order may be violated at i = 0.
At i = 0:
left(0)=1 → heap[1]=2
right(0)=2 → heap[2]=3
Choose the smaller child: index 1 (value 2).
Since heap[0]=9 > 2, swap.
Heap becomes: [2, 9, 3, 5, 7, 4, 8]
Update i = 1.
At i = 1:
left(1)=3 → heap[3]=5
right(1)=4 → heap[4]=7
Smaller child is index 3 (value 5).
Since heap[1]=9 > 5, swap.
Heap becomes: [2, 5, 3, 9, 7, 4, 8]
Update i = 3.
At i = 3:
left(3)=7 and right(3)=8 are out of bounds (heap size is 7).
So i=3 has no children.
Stop. Heap-order property is restored.
Insight: removeMin() replaces the root with the last element to keep the complete shape, then repairs order by pushing that element down a single root-to-leaf path.
Given an arbitrary array A = [7, 2, 6, 3, 9, 1, 5]. Convert it into a min-heap using bottom-up heapify (sift-down from last parent to root). Use 0-based indices.
We have n = 7. The last index is 6.
The last parent is parent(6) = ⌊(6−1)/2⌋ = ⌊5/2⌋ = 2.
So we will siftDown(i) for i = 2, 1, 0.
i = 2:
A[2]=6. Children:
left(2)=5 → A[5]=1
right(2)=6 → A[6]=5
Smallest child is index 5 (value 1).
Since 6 > 1, swap indices 2 and 5.
Array becomes: [7, 2, 1, 3, 9, 6, 5]
Now i becomes 5, which has no children → stop.
i = 1:
A[1]=2. Children:
left(1)=3 → A[3]=3
right(1)=4 → A[4]=9
Smallest child is index 3 (value 3).
Since 2 ≤ 3, no swap needed → stop.
i = 0:
A[0]=7. Children:
left(0)=1 → A[1]=2
right(0)=2 → A[2]=1
Smallest child is index 2 (value 1).
Since 7 > 1, swap indices 0 and 2.
Array becomes: [1, 2, 7, 3, 9, 6, 5]
Continue sifting down at i = 2.
i = 2:
A[2]=7. Children:
left(2)=5 → A[5]=6
right(2)=6 → A[6]=5
Smallest child is index 6 (value 5).
Since 7 > 5, swap indices 2 and 6.
Array becomes: [1, 2, 5, 3, 9, 6, 7]
Now i = 6 has no children → stop.
Result: [1, 2, 5, 3, 9, 6, 7] is a valid min-heap (each parent ≤ its children).
Insight: Bottom-up heapify works because subtrees near the bottom are already tiny heaps after at most a couple comparisons. Doing sift-down from the last parent to the root yields O(n) total work.
A binary heap is a complete binary tree + a local heap-order property (min-heap or max-heap).
The complete-tree shape keeps height at O(log n), enabling fast updates.
Array representation is standard; with 0-based indexing: parent(i)=⌊(i−1)/2⌋, left(i)=2i+1, right(i)=2i+2.
peek-min/peek-max is O(1) because the extreme element is at the root.
insert = append then sift-up; removeMin/removeMax = swap root with last, pop, then sift-down; both are O(log n).
Heaps are great for priority queues but do not support fast arbitrary search or maintaining fully sorted order.
Bottom-up build-heap (heapify) runs in O(n), faster than inserting n items (O(n log n)).
In many libraries, priority queues lack decrease-key; common practice is inserting duplicates and skipping stale entries when popped.
Confusing the heap property with a binary search tree: heaps do not guarantee left subtree < right subtree ordering or in-order sortedness.
Breaking the complete-tree shape by inserting/removing from the middle; the shape rule is what makes the array representation work.
Implementing sift-down but swapping with an arbitrary child instead of the smaller (min-heap) / larger (max-heap) child, which can leave violations behind.
Assuming decrease-key exists in standard library heaps; many require a workaround (duplicates + stale pop skipping) or a custom indexed heap.
Given a min-heap array H = [2, 6, 3, 9, 8, 5]. Insert x = 4 and write the resulting heap array.
Hint: Append 4, then repeatedly compare it with its parent and swap while it’s smaller.
Append: [2, 6, 3, 9, 8, 5, 4] (i=6)
parent(6)=⌊(6−1)/2⌋=2. Compare 4 with H[2]=3 → 4 is not smaller, so stop.
Result: [2, 6, 3, 9, 8, 5, 4].
Perform removeMin() on the min-heap H = [1, 4, 2, 9, 7, 3]. Show the final heap array.
Hint: Move last element to root, pop last, then sift down by swapping with the smaller child until order is restored.
Remove min=1. Move last element 3 to root and pop last:
[3, 4, 2, 9, 7]
Sift-down at i=0: children are 4 and 2 → smaller is 2 at index 2. Swap:
[2, 4, 3, 9, 7]
Now i=2 has no children (left(2)=5 out of bounds). Stop.
Final heap: [2, 4, 3, 9, 7].
You have an array A = [10, 1, 8, 3, 2]. Build a min-heap using bottom-up heapify and give the resulting array.
Hint: Start at i = parent(n−1) and sift down for i = 1 then i = 0.
n=5 → last index 4 → last parent parent(4)=⌊(4−1)/2⌋=1.
Start A=[10,1,8,3,2]
i=1: A[1]=1, children: left=3→3, right=4→2. Since 1 ≤ 2 and 1 ≤ 3, no swap.
i=0: A[0]=10, children: left=1→1, right=2→8. Smaller child is 1 at index 1. Swap:
A=[1,10,8,3,2]
Continue at i=1: A[1]=10, children: left=3→3, right=4→2. Smaller child is 2 at index 4. Swap:
A=[1,2,8,3,10]
Index 4 has no children. Done.
Result: [1,2,8,3,10].
Next, heaps become a critical tool in graph algorithms where you repeatedly select the smallest tentative distance:
Related nodes you may have already used implicitly: