// STUHUB · C++ DATA STRUCTURES
Binary Heaps and Priority Queues: Sift Up, Sift Down, and Build-Heap
How a binary min-heap lives inside a plain array, why insert is O(log n) while build-heap is O(n), the sift-up/sift-down traces exams ask for, and heapify-down on the wrong child — the bug that returns a plausible wrong root.
Introduction
A binary heap is a complete binary tree with one extra rule — the heap property — stored inside a plain array with no pointers at all. That combination (array layout, near-complete shape, one local ordering rule) is what makes insert and pop O(log n) while peeking is O(1), and it is why priority queues are built on heaps. This note traces the four operations exams ask for — insert (sift up), pop (sift down), build-heap, and heapsort — on one small fixed array, then catalogues the traps.
The array layout: where the children are
Because the tree is complete (filled level by level, left to right), it fits an array with no gaps. With 0-based indexing:
- left child of
iis2i + 1 - right child of
iis2i + 2 - parent of
iis(i - 1) / 2(integer division)
The heap property comes in two flavours: in a min-heap every node is ≤ its children (so the minimum sits at index 0); in a max-heap every node is ≥ its children (maximum at index 0). Note carefully what the property does not say: it says nothing about left versus right siblings, and nothing about depth ordering across different subtrees. A heap is not a search structure — finding an arbitrary value still takes O(n). It answers exactly one question fast: what is the smallest (or largest) item?
// Min-heap on a plain int array. Size is the current element count.
int a[16];
int n = 0; // a[0..n-1] is the heap
inline int parent(int i) { return (i - 1) / 2; }
inline int left(int i) { return 2 * i + 1; }
inline int right(int i) { return 2 * i + 2; }Insert = place at the end, sift up
To insert, put the new value in the next free slot (keeping the tree complete) and sift up: while it is smaller than its parent, swap. Each swap fixes the one violated edge and cannot break the rest, because the new value was already ≥ everything below its old position.
Trace — insert 2 into the min-heap [5, 8, 6, 9, 12]:
- Append:
[5, 8, 6, 9, 12, 2], index 5, parent (5−1)/2 = 2 (value 6). - 2 < 6 → swap:
[5, 8, 2, 9, 12, 6], index 2, parent 0 (value 5). - 2 < 5 → swap:
[2, 8, 5, 9, 12, 6], index 0 — root. Stop.
Cost: at most one comparison per level ⇒ O(log n). Best case (value already ≥ parent) is O(1).
void siftUp(int i) { // used by insert
while (i > 0 && a[parent(i)] > a[i]) {
std::swap(a[i], a[parent(i)]);
i = parent(i);
}
}
void insert(int v) { a[n++] = v; siftUp(n - 1); }Pop = move the last leaf to the root, sift down
You cannot remove the root and leave a hole — the array must stay contiguous and the tree complete. So: save the root (the answer), move the last element to index 0, shrink the heap by one, then sift down: repeatedly swap the node with its smaller child until both children are ≥ it.
Why the smaller child? Swapping with the larger child of a min-heap can put a value above something smaller — breaking the property. Choosing the smaller child is the detail exam questions probe.
Trace — pop from [2, 8, 5, 9, 12, 6]:
- Answer is 2. Move last (6) to root, shrink:
[6, 8, 5, 9, 12]. - Children of 0: 8 (idx 1), 5 (idx 2). Smaller is 5. 6 > 5 → swap:
[5, 8, 6, 9, 12]. - Node now index 2, children: none (4 and 5 are out of range with n = 5). Stop.
Cost: O(log n) — the path from root to leaf has ⌊log₂ n⌋ levels.
void siftDown(int i) { // used by pop and build-heap
while (true) {
int smallest = i;
if (left(i) < n && a[left(i)] < a[smallest]) smallest = left(i);
if (right(i) < n && a[right(i)] < a[smallest]) smallest = right(i);
if (smallest == i) break; // heap property holds
std::swap(a[i], a[smallest]);
i = smallest; // descend
}
}
int pop() {
int top = a[0];
a[0] = a[--n]; // last leaf becomes root
siftDown(0);
return top;
}Build-heap: why it is O(n), not O(n log n)
Turning an arbitrary array into a heap: sift down every internal node, starting from the last parent (index n/2 − 1) and walking back to 0. Leaves are already valid one-element heaps, so they are skipped.
The complexity argument is the part worth being able to reproduce: a node at height h costs O(h) to sift down, and at most ⌈n / 2^(h+1)⌉ nodes have that height. Summing n · Σ h/2^(h+1) over heights converges to a constant — the series Σ h/2^h = 2 — so the total is O(n). The intuition: half the nodes are leaves (height 0, free to fix), and only a handful of nodes near the root can cascade the full log n.
This is why inserting n items one by one (O(n log n)) is the worse way to build a heap, and the discrepancy is a classic short-answer question.
void buildHeap() { // from arbitrary a[0..n-1]
for (int i = n / 2 - 1; i >= 0; --i)
siftDown(i); // last parent down to root
}Priority queues and heapsort
A priority queue is the abstract data type — insert item with priority, remove the highest-priority item — and the binary heap is its standard implementation, exactly as std::priority_queue is specified (a max-heap by default). std::greater<T> flips it to a min-heap:
std::priority_queue<int> maxpq; // max on top
std::priority_queue<int, std::vector<int>, std::greater<int>> minpq;Heapsort is build-heap followed by n pops that each write the removed root into the vacated tail slot: build (O(n)) plus n pops at O(log n) gives O(n log n) worst case, in place, but not stable. The exam contrast worth memorizing: heapsort guarantees n log n where quicksort does not, but quicksort's cache behavior wins in practice.
| Operation | Min-heap | Sorted array | Unsorted array |
|---|---|---|---|
| find-min | O(1) | O(1) | O(n) |
| insert | O(log n) | O(n) | O(1) |
| extract-min | O(log n) | O(1) | O(n) |
| build from n items | O(n) | O(n log n) | O(n) |
The mistakes that cost marks
- Sifting down against the larger child in a min-heap — produces a valid-looking array with a small value buried under a big one; the bug is silent until the grader checks the property.
- Forgetting
--nbefore or after moving the last element in pop — either the stale last element gets sifted again or the heap keeps an extra element; off-by-one central. - Building by n inserts and reporting O(n) — insert is O(log n) each, so that construction is O(n log n); only the sift-down-from-last-parent loop is O(n).
- Stopping sift-down at
left(i) < nbut readingright(i)unguarded — reads one past the heap end when the left child is the last element; the guard must cover both children. - Treating a heap as sorted.
[1, 5, 3, 8, 6]is a valid min-heap and visibly not sorted. Level order is not sort order — the only guaranteed position is the root.
Exercises with solutions
Work through each question before opening the solution below it.
Exercise 1
Show the min-heap array after each of these operations, starting from [3, 7, 5, 9, 12, 6]: insert 1, then pop twice.
Solution
Insert 1: append at index 6 → [3, 7, 5, 9, 12, 6, 1]; parent 2 (value 5): 1 < 5, swap → [3, 7, 1, 9, 12, 6, 5]; parent 0 (value 3): 1 < 3, swap → [1, 7, 3, 9, 12, 6, 5], at root, stop. First pop: answer 1; move last (5) to root, n = 6 → [5, 7, 3, 9, 12, 6]; children 7 and 3, smaller is 3: 5 > 3, swap → [3, 7, 5, 9, 12, 6]; node index 2 has no children in range. Stop. Second pop: answer 3; move last (6) to root, n = 5 → [6, 7, 5, 9, 12]; children 7 and 5, smaller is 5: 6 > 5, swap → [5, 7, 6, 9, 12]; index 2 has no children. Stop. Final heap: [5, 7, 6, 9, 12].
Exercise 2
An array of n items arrives unsorted. Your classmate builds a min-heap by calling insert n times and claims the construction is O(n). Where is the error, and what is the correct construction?
Solution
Each insert sifts up at most ⌊log₂ (i+1)⌋ levels, so n inserts cost Σ log i = O(n log n) — the claim undercounts by treating the average sift as O(1). The O(n) construction is the bottom-up loop: for i from n/2 − 1 down to 0, siftDown(i). Most nodes are near the bottom where sift-down is short (half are leaves and cost nothing), and the height-weighted series Σ n·h/2^(h+1) converges to O(n). Same final heap shape, asymptotically cheaper construction.
Exercise 3
In a min-heap stored as [2, 5, 4, 9, 7, 6, 8], is the value 7 the third smallest element? Explain using the heap property only.
Solution
Not guaranteed. The heap property says every node is ≤ its children, so 2 (the root) is the minimum. The second smallest is one of the root's children (5 or 4), and the third smallest is somewhere among the remaining nodes at heights ≤ 1 — but 7 sits at index 4 as a child of 5, and its sibling 9 and the entire subtree rooted at 4 (4, 6, 8) are only bounded below by their ancestors. Concretely, 4's subtree could have contained a value smaller than 7 in a different heap. A heap guarantees only the root's rank; ordering beyond the minimum requires popping (heapsort) or a different structure.
Which C++ standard does this page assume?
Standard C++17. C++ began as C with Classes in 1979 and first shipped commercially in 1985; what a compiler flag actually selects today is one of the ISO revisions below, and the committee has published a new one every 3 years since 2011. Build these listings with -std=c++17 and they compile as written.
| Revision | Published as | What it changes for the code on this page |
|---|---|---|
| C++98 | ISO/IEC 14882:1998 | The first ISO C++, and the dialect most data-structures courses still teach from: templates, the STL containers, and raw pointers doing the work. |
| C++03 | ISO/IEC 14882:2003 | A defect-fix revision. Nothing on these pages depends on it, and nothing on these pages is broken by it. |
| C++11 | ISO/IEC 14882:2011 | Where nullptr, auto, range-based for, move semantics and the unordered containers arrive. Every listing here writes nullptr rather than NULL because of it. |
| C++14 | ISO/IEC 14882:2014 | A small revision: generic lambdas and std::make_unique. Used only where it makes ownership clearer. |
| C++17 | ISO/IEC 14882:2017 | What every listing on StuHub targets and was compiled against. If you build these files, build them with -std=c++17. |
| C++20 | ISO/IEC 14882:2020 | Concepts, ranges and std::midpoint. Flagged in the prose where it offers a shorter correct form, never assumed by the code. |
| C++23 | ISO/IEC 14882:2024 | Not used here. Named so you can tell whether a snippet you found elsewhere will compile on a lab machine that predates it. |
Common questions
What does this page cover?
How a binary min-heap lives inside a plain array, why insert is O(log n) while build-heap is O(n), the sift-up/sift-down traces exams ask for, and heapify-down on the wrong child — the bug that returns a plausible wrong root.
How long does this page take to work through?
About 7 minutes of reading at 200 words per minute, plus 3 questions with worked solutions at the foot of the page. Reading it end to end is the slow way; the intended use is to find the section you are stuck on, then do the questions for that section with the solutions covered.
Which C++ standard do these examples target?
Standard C++17 — ISO/IEC 14882:2017. Every listing was compiled with -std=c++17 and -Wall -Wextra before publication, and the linked-structure examples were also run under AddressSanitizer. Where C++20 offers a shorter correct form, such as std::midpoint, the prose says so instead of quietly using it.
Is StuHub free, and do I need an account?
It is free and there is nothing to sign in to. No login, account or payment is required to read any of the 20 topics — StuHub is published by DUOCODE TECHNOLOGY alongside APRide, and the ride board's accounts have nothing to do with it.
Can I paste this code into my assignment?
Treat it as a reference, not as an answer key. StuHub is educational material only, it is not coursework and it is not affiliated with or endorsed by any institution, so your own submission rules decide what you may reuse. Every listing was compiled and run before publication, and you should still compile and test anything you take.
Why is everything written in C++ rather than pseudocode?
Because most of the mistakes worth catching are C++ mistakes, not algorithm mistakes: a lost pointer, a destructor that never runs, an index that underflows because it was unsigned. Pseudocode hides exactly the layer where a data-structures assignment is actually failed.
Where should I check what the standard library really guarantees?
cppreference for the day-to-day answer, and the WG21 working drafts when the exact wording matters — both are linked below. Compiler documentation settles the rest: a warning you cannot explain is usually the compiler being right.
Does StuHub replace my lecture notes?
No. It is written to sit beside them: your course decides what is examinable, in what notation, and with which library restrictions. Where this page and your module handbook disagree about scope, the handbook wins.
Is StuHub connected to Asia Pacific University?
No. It is an independent reference published by DUOCODE TECHNOLOGY, not affiliated with or endorsed by Asia Pacific University or any other institution. It was written for APU students because that is who asked for it, and it is open to anyone.
Where can I check this against the language itself?
Nothing on this page outranks the standard or the library reference. When this page and one of these disagree, they are right and we want to know.
- cppreference — the C++ standard library referenceThe fastest correct answer for what a container or algorithm actually guarantees.
- isocpp.org — the Standard C++ FoundationThe FAQ and the core guidelines, written by the people who define the language.
- ISO/IEC JTC1/SC22/WG21 — the C++ standards committeeWorking drafts of the standard itself, free to read, when the wording is the question.
- GCC online documentationWhat -Wall, -Wextra and the sanitizers used on these listings actually check.
- Microsoft Learn — C++ language documentationThe MSVC view, for readers whose lab machines build with Visual Studio.
Published 2026-08-09 · updated 2026-08-27 · DUOCODE TECHNOLOGY