Heaps and Priority Queues
This chapter implements a binary heap and the priority queue built on it.
Why This Matters
A heap gives O(log n) insert and O(1) access to the maximum (or minimum) element — the core of priority queues, which drive schedulers, shortest-path algorithms, event simulation, and top-k problems.
Prerequisites
c.ds.6— binary trees (conceptually).
Core Concept
A binary heap is a complete binary tree stored in an array, satisfying the heap property: each parent is ≥ its children (max-heap) or ≤ (min-heap).
Array layout: for a node at index i (0-based):
- left child:
2*i + 1 - right child:
2*i + 2 - parent:
(i - 1) / 2
Because it is complete, no pointer overhead; the whole heap is one contiguous array.
Examples
Max-heap push and pop
#include <stdlib.h>
typedef struct {
int *data;
size_t size;
size_t capacity;
} Heap;
static void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
static void sift_up(Heap *h, size_t i)
{
while (i > 0) {
size_t p = (i - 1) / 2;
if (h->data[p] >= h->data[i]) break;
swap(&h->data[p], &h->data[i]);
i = p;
}
}
static void sift_down(Heap *h, size_t i)
{
for (;;) {
size_t l = 2*i + 1, r = 2*i + 2, m = i;
if (l < h->size && h->data[l] > h->data[m]) m = l;
if (r < h->size && h->data[r] > h->data[m]) m = r;
if (m == i) break;
swap(&h->data[i], &h->data[m]);
i = m;
}
}
int heap_push(Heap *h, int v)
{
/* grow h->data if needed (realloc), then */
h->data[h->size] = v;
sift_up(h, h->size);
h->size++;
return 0;
}
int heap_pop(Heap *h, int *out)
{
if (h->size == 0) return -1;
*out = h->data[0];
h->data[0] = h->data[--h->size];
sift_down(h, 0);
return 0;
}
How It Works
Insert appends at the end and sift_up restores the heap property by swapping with the parent. Pop returns the root, moves the last element to the root, and sift_down restores the property by swapping with the larger child. Both are O(log n).
Variations
Min-heap vs. max-heap
Reverse the comparison. A min-heap gives the smallest element first.
Build-heap (heapify)
Building a heap from an unsorted array is O(n) by sifting down from the last non-leaf, better than n separate O(log n) inserts.
Other heaps
Binomial, Fibonacci, and pairing heaps offer different trade-offs (e.g., faster decrease-key for Dijkstra).
Common Mistakes
- Off-by-one in the 0-based index formulas.
- Forgetting to reallocate the underlying array.
- Confusing max-heap and min-heap comparisons.
- Not updating
sizeafter pop.
Undefined Behavior
- Reading beyond the allocated array.
VERIFIED - Use-after-free of the buffer.
Portability
- Plain C, fully portable.
Under the Hood
The heap is contiguous and cache-friendly, unlike a pointer-based tree. sift_up/sift_down are tight loops the compiler can optimize well.
Practical Usage
- Use a priority queue for Dijkstra/A*, scheduling, and event simulation.
- Use a heap for top-k and median maintenance.
- Prefer an array heap over a tree for performance.
Exercises
1. Implement heap_push, heap_pop, and heap_peek. 2. Implement heapify in O(n) from an unsorted array. 3. Convert the max-heap to a min-heap. 4. Use a priority queue to implement a small job scheduler.
Deep Challenge
Implement a priority queue with a decrease_key operation (for Dijkstra), and explain why a simple binary heap with a position index is needed, or why a Fibonacci heap would be better for dense graphs.
Related Concepts
c.ds.2— dynamic arrays (storage).c.alg.3— Dijkstra/A*.c.alg.1— heapsort.
References
- Standard data-structure literature (CLRS, Sedgewick).
Verification
- Heap index formulas and O(log n) operations.
VERIFIED - Build-heap is O(n).
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Heap array layout
- [ ] Sift up/down
- [ ] Push/pop/peek
- [ ] Heapify
- [ ] Priority queue
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.ds.heap | 0 | 6 |
| c.ds.priority-queue | 0 | 6 |