Binary heap: array representation, percolate, buildHeap, heapsort
How a binary heap is stored in an array, traces of insert (percolate up) and deleteMin (percolate down), O(n) buildHeap and heapsort. The classic exam question: what does the array look like after these operations?
A heap is the structure behind a priority queue: you reach the smallest (or largest) element in O(1) at any time, and insertion and deletion are O(log n). It is thought of as a tree but it is stored in an array, with no pointers. Almost always the same question comes in the exam: “Insert in this order / do this many deleteMin / apply buildHeap, write the final array.” Someone who does the two shifting operations on this page (percolate up and percolate down) correctly will not miss these questions.
Golden rules:
- The structure rule: a heap is a complete binary tree. Levels are filled from left to right, with no gaps.
- The order rule (min-heap): every node is less than or equal to its children. The smallest element is at the root.
- Insertion goes up, deletion goes down. insert puts the element at the end and shifts it up. deleteMin puts the last element at the root and shifts it down.
- Going down, swap with the smaller child. If you swap with the larger child, the order rule breaks.
Array representation
Because the tree is complete, it is enough to write the nodes into an array in level order. In this course (and in Weiss) the priority queue heap starts at index 1, and a[0] stays empty. Then the formulas are very clean:
| What you want | Formula (1-indexed) | 0-indexed equivalent |
|---|---|---|
| parent | i / 2 | (i - 1) / 2 |
| left child | 2i | 2i + 1 |
| right child | 2i + 1 | 2i + 2 |
index: 1 2 3 4 5 6 7 8
array: [6, 14, 9, 20, 17, 11, 30, 25]
6
/ \
14 9
/ \ / \
20 17 11 30
/
25
The index of 25 is 8, its parent is 8 / 2 = 4, that is, 20. Every parent is smaller than its children: this is a min-heap. But there is no order between siblings (such as 14 and 9). A heap is not a BST.
The height of a heap with n elements is . For 8 elements it is 3.
insert: percolate up
- Put the new element in the first empty place (the end of the array, index size + 1).
- While it is smaller than its parent, swap it with its parent.
Let’s insert 8 into the heap above:
8 at index 9: parent a[4]=20 > 8 → up
8 at index 4: parent a[2]=14 > 8 → up
8 at index 2: parent a[1]=6 < 8 → stop
[6, 8, 9, 14, 17, 11, 30, 25, 20]
6
/ \
8 9
/ \ / \
14 17 11 30
/ \
25 20
In the worst case the new element goes all the way up to the root: O(log n).
deleteMin: percolate down
- Save the element at the root (the minimum); we will return it.
- Move the last element to the root and reduce the size by 1.
- Starting from the root: choose the smaller of the two children. If the child is smaller than the value you carry, swap and go down. If not, stop.
deleteMin on the 9-element heap from before:
6 is removed, the last element 20 goes to the root:
[20, 8, 9, 14, 17, 11, 30, 25]
i=1: children 8, 9 → 8 < 20, swap
i=2: children 14, 17 → 14 < 20, swap
i=4: child 25 (index 8) > 20 → stop
[8, 14, 9, 20, 17, 11, 30, 25]
PITFALL · The last element takes the place of the deleted one. There is no such thing as “moving the smaller child up to the root”. First the last element is put at the root, then it is shifted down. This way the tree stays complete and no gap appears.
buildHeap: O(n) construction
If you have n unordered elements there are two ways: n inserts (O(n log n)) or Floyd’s method. Floyd: put the elements into the array as they are, then call percolateDown at every node from the last internal node (n/2) toward the root. The leaves (those after n/2) are already heaps on their own, so they are not touched.
For [15, 12, 10, 8, 4, 6, 2, 9, 1, 3] (n = 10, i = 5, 4, 3, 2, 1):
percDown(5) -> [15, 12, 10, 8, 3, 6, 2, 9, 1, 4]
percDown(4) -> [15, 12, 10, 1, 3, 6, 2, 9, 8, 4]
percDown(3) -> [15, 12, 2, 1, 3, 6, 10, 9, 8, 4]
percDown(2) -> [15, 1, 2, 8, 3, 6, 10, 9, 12, 4]
percDown(1) -> [1, 3, 2, 8, 4, 6, 10, 9, 12, 15]
result: [1, 3, 2, 8, 4, 6, 10, 9, 12, 15] moves=8
In the last step, 15 sinks from the root all the way to the bottom (index 10): 1, 3 and 4 each move up one level.
Why O(n)? percolateDown at a node does at most as much work as that node’s height. Half of the nodes are leaves (work 0), a quarter have height 1, an eighth have height 2, … The total:
So the work is small at the bottom where most nodes are, and large at the top where nodes are few. The average is constant, and the total is O(n). When building with insert, the opposite happens: most elements are at the bottom and each can climb all the way to the root.
MinHeap in Java
public class MinHeap {
private int[] array = new int[11]; // array[0] is not used
private int size = 0;
public void insert(int x) {
if (size + 1 == array.length)
array = java.util.Arrays.copyOf(array, array.length * 2);
int hole = ++size;
while (hole > 1 && x < array[hole / 2]) {
array[hole] = array[hole / 2]; // move the parent down
hole /= 2;
}
array[hole] = x;
}
public int findMin() { return array[1]; }
public int deleteMin() {
if (size == 0) throw new RuntimeException("empty heap");
int min = array[1];
array[1] = array[size--]; // last element to the root
percolateDown(1);
return min;
}
private void percolateDown(int hole) {
int tmp = array[hole];
while (hole * 2 <= size) {
int child = hole * 2;
if (child != size && array[child + 1] < array[child]) child++;
if (array[child] < tmp) array[hole] = array[child];
else break;
hole = child;
}
array[hole] = tmp;
}
public void buildHeap(int[] items) {
array = new int[items.length + 1];
size = items.length;
for (int i = 0; i < items.length; i++) array[i + 1] = items[i];
for (int i = size / 2; i > 0; i--) percolateDown(i);
}
public String toString() { // array[1..size]
return java.util.Arrays.toString(
java.util.Arrays.copyOfRange(array, 1, size + 1));
}
}
The code does not swap values at each step. It moves a “hole” and writes the element into place once at the end. A swap makes 3 assignments, moving the hole makes 1.
MinHeap h = new MinHeap();
for (int x : new int[]{6, 14, 9, 20, 17, 11, 30, 25}) h.insert(x);
h.insert(8);
System.out.println("+8 -> " + h);
System.out.println("deleteMin = " + h.deleteMin() + " -> " + h);
+8 -> [6, 8, 9, 14, 17, 11, 30, 25, 20]
deleteMin = 6 -> [8, 14, 9, 20, 17, 11, 30, 25]
Heapsort
Sorting with a heap: make the array a max-heap, then move the largest (the root) to the end and shrink the heap by one. No extra array is needed. In heapsort the array starts at index 0 (the array to be sorted is already like this), and the children are 2i + 1 and 2i + 2.
- buildMaxHeap: from i = n/2 - 1 down to 0, percolateDown (for max) at each.
- n - 1 times: swap a[0] with the last element of the heap, reduce the heap size by 1, and shift a[0] down.
For [4, 10, 3, 5, 1]:
== heapsort [4, 10, 3, 5, 1]
after buildMaxHeap: [10, 5, 3, 4, 1]
after deleteMax #1: [5, 4, 3, 1, 10] (heap part size 4)
after deleteMax #2: [4, 1, 3, 5, 10] (heap part size 3)
after deleteMax #3: [3, 1, 4, 5, 10] (heap part size 2)
after deleteMax #4: [1, 3, 4, 5, 10] (heap part size 1)
At each step the sorted part on the right of the array grows by one. buildMaxHeap: for i=1 (10) the children 5, 1 are smaller, no change; for i=0 (4) the children are 10, 3, the larger 10 moves up, 4 is at index 1 with children 5, 1, the larger 5 moves up. Result [10, 5, 3, 4, 1].
| Property | Heapsort |
|---|---|
| Time | O(n log n) best, average, worst |
| Extra memory | O(1), in place |
| Stable? | No (the order of equal keys can change) |
Common mistakes
| Wrong idea | The truth |
|---|---|
| A heap is ordered like a BST, left < right | There is only parent-child order; siblings are free |
| In deleteMin the smaller child moves to the root and the gap stays below | The last element is put at the root and shifted down; the tree stays complete |
| While shifting down I swap with the first (left) child | Swap with the smaller of the two children (the larger in a max-heap) |
| buildHeap = n inserts | Floyd: percolateDown from n/2 to 1; O(n) and may give a different array |
| buildHeap starts at the root | It goes from the last internal node (n/2) toward the root |
| In a 1-indexed array the children are 2i+1, 2i+2 | That is the 0-indexed formula; 1-indexed: 2i, 2i+1 |
| Search in a heap is O(log n) | Searching for an arbitrary element is O(n); only the min is O(1) |
| Heapsort is stable | It is not; for stable O(n log n) use merge sort |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
In a binary heap stored in an array starting at index 1, the node at index 13 has which parent index and which child indices?
Show answer
In a 1-indexed array: parent , left child , right child .
13 / 2 = 6 (integer division), 2·13 = 26, 2·13 + 1 = 27. These children exist only if the array size is at least 26.
Which array (index 0 unused, listed from index 1) is a valid min-heap?
- [2, 5, 3, 8, 6, 4, 9]
- [2, 5, 3, 4, 6, 7, 9]
- [2, 5, 3, 8, 6, 1, 9]
Show answer
For every i >= 2, a[i] >= a[i/2] must hold.
In the second, a[4] = 4 is smaller than its parent a[2] = 5: broken.
In the third, a[6] = 1 is smaller than its parent a[3] = 3: broken.
In the first, 5, 3 >= 2; 8, 6 >= 5; 4, 9 >= 3. Valid.
What is the height of a binary heap containing 100 elements? (A single node has height 0.)
Show answer
A heap is a complete binary tree, and its height is . , so 6.
Give the time complexity of each min-heap operation: findMin, insert, deleteMin, buildHeap (n items).
Show answer
The smallest element is always at a[1]: O(1). insert and deleteMin shift at most as many steps as the height (log n).
If buildHeap did n inserts it would be O(n log n); with Floyd's method (percolate down from the bottom) the total work is the sum of the heights of the nodes, and this is O(n).
Insert 10, 4, 7, 1, 8, 3 one at a time into an empty min-heap. Show the array (from index 1) after all insertions.
Show answer
+10: [10]. +4: index 2, the parent 10 is larger, move up: [4, 10].
+7: index 3, the parent 4 is smaller, it stays: [4, 10, 7].
+1: index 4, the parent 10 → up (index 2), the parent 4 → up (index 1): [1, 4, 7, 10].
+8: index 5, the parent 4 is smaller: [1, 4, 7, 10, 8].
+3: index 6, the parent 7 → up (index 3), the parent 1 is smaller, stop: [1, 4, 3, 10, 8, 7].
Starting from the heap [1, 4, 3, 10, 8, 7], perform deleteMin twice. Show the array after both operations.
Show answer
1st deleteMin: 1 is removed, the last element 7 comes to the root: [7, 4, 3, 10, 8]. The children are 4 and 3, the smaller 3 < 7: swap. 7 is at index 3 and has no children: [3, 4, 7, 10, 8].
2nd deleteMin: 3 is removed, the last element 8 goes to the root: [8, 4, 7, 10]. The children are 4, 7, the smaller 4 < 8: [4, 8, 7, 10]. 8 is at index 2, its child 10 (index 4) is larger, stop.
Apply buildHeap (Floyd's bottom-up method) to the array [9, 6, 8, 2, 5, 7, 1, 3] to make a min-heap. Show the resulting array.
Show answer
n = 8, percolateDown for i = 4, 3, 2, 1 in order.
i=4 (2): its child is 3, 2 is smaller, no change.
i=3 (8): the children are 7, 1, the smaller is 1: [9, 6, 1, 2, 5, 7, 8, 3].
i=2 (6): the children are 2, 5 → 2 moves up; 6 is at index 4, its child 3 < 6 → 3 moves up: [9, 2, 1, 3, 5, 7, 8, 6].
i=1 (9): the children are 2, 1 → 1 moves up; 9 is at index 3, the children are 7, 8 → 7 moves up: [1, 2, 7, 3, 5, 9, 8, 6].
Insert the same keys 9, 6, 8, 2, 5, 7, 1, 3 one at a time into an empty min-heap. Show the final array. Is it the same as the buildHeap result [1, 2, 7, 3, 5, 9, 8, 6]?
Show answer
With one-by-one insertion, each new element is moved up. The trace: [9] → [6, 9] → [6, 9, 8] → [2, 6, 8, 9] → [2, 5, 8, 9, 6] → [2, 5, 7, 9, 6, 8] → [1, 5, 2, 9, 6, 8, 7] → +3: index 8, the parent 9 → up, the parent 5 → up, the parent 1 stops it: [1, 3, 2, 5, 6, 8, 7, 9].
Both methods give a valid heap but the arrays are different. In the exam, pay attention to which method is asked for.
Insert 20, 15, 30, 10, 40 one at a time into an empty max-heap (index 1 based). Show the final array.
Show answer
In a max-heap the parent must be greater than or equal to the child; a new element moves up if it is larger than its parent.
[20] → [20, 15] → +30: the parent 20 is smaller, move up: [30, 15, 20] → +10: [30, 15, 20, 10] → +40: index 5, the parent 15 → up, the parent 30 → up: [40, 30, 20, 10, 15].
Apply buildHeap to [15, 12, 10, 8, 4, 6, 2, 9, 1, 3] to make a min-heap. Show the resulting array.
Show answer
n = 10, i = 5, 4, 3, 2, 1:
i=5 (4): its child is 3 → [15, 12, 10, 8, 3, 6, 2, 9, 1, 4].
i=4 (8): the children are 9, 1 → 1 moves up: [15, 12, 10, 1, 3, 6, 2, 9, 8, 4].
i=3 (10): the children are 6, 2 → 2 moves up: [15, 12, 2, 1, 3, 6, 10, 9, 8, 4].
i=2 (12): the children are 1, 3 → 1 moves up; at index 4 the children are 9, 8 → 8 moves up: [15, 1, 2, 8, 3, 6, 10, 9, 12, 4].
i=1 (15): the children are 1, 2 → 1; at index 2 the children are 8, 3 → 3; at index 5 its child is 4 → 4; 15 sinks to index 10: [1, 3, 2, 8, 4, 6, 10, 9, 12, 15].
Heapsort (max-heap, array indexed from 0, ascending order) is applied to [5, 2, 9, 1, 7, 3]. Show the array after building the max-heap, and after each of the first two deleteMax steps.
Show answer
buildMaxHeap (0-indexed, children 2i+1, 2i+2), i = 2, 1, 0:
i=2 (9): its child is 3, no change. i=1 (2): the children are 1, 7 → 7 moves up: [5, 7, 9, 1, 2, 3]. i=0 (5): the children are 7, 9 → 9 moves up; 5 is at index 2, its child 3 is smaller: [9, 7, 5, 1, 2, 3].
deleteMax 1: a[0]=9 is swapped with a[5]=3, the heap size is 5: [3, 7, 5, 1, 2 | 9]. 3 sinks: the children are 7, 5 → 7; at index 1 the children 1, 2 are smaller: [7, 3, 5, 1, 2, 9].
deleteMax 2: 7 is swapped with a[4]=2, the size is 4: [2, 3, 5, 1 | 7, 9]. 2 sinks: the children are 3, 5 → 5; index 2 has no children: [5, 3, 2, 1, 7, 9].
The rest: [3, 1, 2, 5, 7, 9] → [2, 1, 3, 5, 7, 9] → [1, 2, 3, 5, 7, 9].
Write the private method percolateDown(int hole) of a min-heap stored in int[] array (index 1 based) with size elements.
Show answer
Instead of swapping the value at each step, the "hole" is moved down: fewer assignments are made.
private void percolateDown(int hole) {
int tmp = array[hole];
while (hole * 2 <= size) { // at least a left child exists
int child = hole * 2;
if (child != size && array[child + 1] < array[child])
child++; // pick the smaller child
if (array[child] < tmp)
array[hole] = array[child]; // child moves up
else
break;
hole = child;
}
array[hole] = tmp;
}Common mistakes: forgetting the child != size check and looking at a right child that does not exist, or picking the larger child instead of the smaller (that would make a max-heap).Verification: All array states (insert, deleteMin, buildHeap steps, heapsort intermediate states, max-heap insertion) and the heap check were produced with Java (verify/SEN2212/W07.java, java W07.java). The MinHeap class on the page was run in the same file and gave the same arrays as a second, hand-written implementation.