Disclaimer: ChatGPT generated document.
Everything you wanted to know about heaps — the data-structure heap and the dynamic memory heap — in one place.
In C++, heap is an overloaded term referring to two unrelated concepts:
- Binary heap — the tree-based priority queue structure behind
std::make_heap,std::push_heap,std::pop_heap,std::priority_queue. - Memory heap (a.k.a. free store) — the region from which
new,delete,malloc, andfreeallocate dynamically sized memory blocks.
These two concepts:
- share no common implementation,
- share no overlapping invariants,
- exist for entirely different purposes,
- and the terminology collision is historical, not technical.
This guide explains both deeply and shows how to never confuse them again.
A binary heap is a:
- Complete binary tree All levels are filled except possibly the last, which is filled left-to-right.
- With the heap property:
- Max-heap: each parent ≥ each child
- Min-heap: each parent ≤ each child
Because it’s complete, it can be stored contiguously in std::vector without pointers or node objects.
Given a 0-indexed array a:
parent(i) = (i - 1) / 2left(i) = 2*i + 1right(i) = 2*i + 2
Example max-heap represented as [9, 5, 8, 1, 3, 4]:
9
/ \
5 8
/ \ /
1 3 4
| Operation | Complexity | Notes |
|---|---|---|
Access root (front()) |
O(1) | Always the extremum |
| Insert (push) | O(log n) | Bubble-up |
| Extract extremum (pop) | O(log n) | Bubble-down |
| Build from array | O(n) | Floyd’s heapify algorithm |
Note: Many beginners incorrectly assume heap build is O(n log n). It’s actually O(n) because lower levels cost less work.
All heap algorithms live in <algorithm>, not <queue>.
They operate on any random-access range — most commonly std::vector.
| Function | Description | Complexity |
|---|---|---|
std::make_heap(first,last) |
Creates a max-heap | O(n) |
std::make_heap(..., comp) |
Custom comparator → min-heap via std::greater<> |
O(n) |
std::push_heap(first,last) |
Insert the element at last-1 into heap |
O(log n) |
std::pop_heap(first,last) |
Move max to last-1, restore heap in [first,last-1) |
O(log n) |
std::sort_heap(first,last) |
Converts heap → sorted range | O(n log n) |
std::is_heap / is_heap_until |
Validation helpers | O(n) |
std::push_heap does not append elements. You must push into the container manually:
v.push_back(x);
std::push_heap(v.begin(), v.end());std::priority_queue is simply:
- a
std::vectorinternally plus - the heap algorithms
std::priority_queue<int> pq;std::priority_queue<
int,
std::vector<int>,
std::greater<int>
> min_pq;Binary heaps (including priority_queue) cannot efficiently support decrease_key, remove(element) or update-in-place.
For those, you need a Fibonacci heap, pairing heap, or indexed priority queue.
Prefer raw *_heap algorithms when you need:
- In-place heapsort
- Two-heap median maintenance (min-heap + max-heap)
- Access to all elements (e.g., custom decrease-key workaround)
- A priority queue built on a
std::vectorwith specific memory constraints - A priority queue that must be serialize-friendly (raw vector)
std::priority_queue is intentionally restrictive.
#include <vector>
#include <algorithm>
#include <functional>
#include <iostream>
int main() {
std::vector<int> v {3,1,4,1,5,9,2,6,5,3,5};
// 1. Build max heap
std::make_heap(v.begin(), v.end());
std::cout << "Max-heap root: " << v.front() << '\n'; // 9
// 2. Push new element
v.push_back(99);
std::push_heap(v.begin(), v.end());
std::cout << "After push: " << v.front() << '\n'; // 99
// 3. Pop max element
std::pop_heap(v.begin(), v.end()); // moves max to v.back()
std::cout << "Popped: " << v.back() << '\n';
v.pop_back();
// 4. Min-heap
std::make_heap(v.begin(), v.end(), std::greater<>{});
std::cout << "Min-heap root: " << v.front() << '\n';
// 5. Heapsort (ascending)
std::sort_heap(v.begin(), v.end(), std::greater<>{});
}The memory heap (free store):
- is a runtime-managed area used for variable-size allocations via
new,delete,malloc,free - has no relation whatsoever to binary heaps
- does not maintain ordering, parent/child relationships, or tree invariants
It’s essentially a sophisticated bookkeeping system on top of virtual memory.
Modern allocators never scan large lists for blocks. They operate via:
Allocations rounded to predefined size buckets (e.g., 32B, 48B, 64B).
Each size class maintains a linked list of freed blocks.
Fast per-thread allocation with no locking.
When a bin runs empty, the allocator grabs a new slab/page from the OS, subdivides it, and populates a free list.
- Round to nearest size class → 48 bytes
- Check thread-local 48-byte free list
- If available → pop one → O(1)
- If empty → refill from a page → amortized fast
- If page empty → request new pages from OS
Push block back into its size class free list → O(1).
| Allocator | Used by | Highlights |
|---|---|---|
| ptmalloc2 | glibc on Linux | Arenas, fast bins |
| jemalloc | FreeBSD, Firefox, Redis | Low fragmentation, scalable |
| tcmalloc | Google/Chromium | Thread-local caches, high throughput |
| mimalloc | Microsoft, Rust optional | Very predictable performance |
| rpmalloc/Hoard | Servers, HPC | Highly scalable multi-threading |
Different allocators dramatically change performance of C++ programs using new/delete heavily.
Wasted space inside allocated blocks due to rounding to size class. Typically small.
Pieces of free memory too small to satisfy large allocations. Good allocators greatly reduce this via:
- slab allocation
- segregated fits
- page recycling
- coalescing (merging adjacent free blocks)
| Feature | Binary Heap (data structure) | Memory Heap (dynamic allocation) |
|---|---|---|
| Purpose | Fast extract-min/max priority queue | Runtime memory allocation |
| Based on | Complete binary tree in array | Virtual memory + allocator metadata |
| Ordering | Required (min/max) | None |
| Typical ops | push_heap, pop_heap, make_heap |
new, delete, malloc, free |
| Insert complexity | O(log n) | O(1) amortized |
| Delete/Extract | O(log n) | O(1) amortized |
| Root access | O(1) | Not applicable |
| Lives in | C++ STL <algorithm> / <queue> |
Runtime library / libc |
| Shared concepts | None | None |
| Code snippet | Which heap? |
|---|---|
std::priority_queue<int> pq; |
Binary heap |
std::make_heap(v.begin(), v.end()) |
Binary heap |
int* p = new int[1000]; |
Memory heap |
malloc(64) / free(ptr) |
Memory heap |
| Concern: “largest element fast” | Binary heap |
| Concern: “memory leak” | Memory heap |
- The binary heap is an elegant O(log n) priority queue stored in a contiguous container.
C++ provides both low-level heap algorithms and
std::priority_queue. - The memory heap is a sophisticated allocator using bins, free lists, arenas, and thread caches to provide near-constant-time allocation.
- The two share nothing except a misleading name dating back to early Lisp/ALGOL memory terminology.
You now have a precise, corrected, and expert-level understanding of both meanings of “heap” in C++.
If you'd like, I can also:
✅ Add diagrams for memory allocator internals
✅ Add a “history of the terminology” sidebar
✅ Add examples using polymorphic allocators (std::pmr)
✅ Compare binary heaps with Fibonacci heaps, pairing heaps, and binary search trees
✅ Provide interview-style questions and answers on heaps
Would you like any of those?
