Heaps & Heapsort
Heap ও Heapsort
1. The Heap Property
A binary heap is a complete binary tree where every parent is ≥ both children (max-heap) or ≤ both children (min-heap). It is stored as a 0-indexed array with parent/child arithmetic — no pointers needed.
| Index | Formula |
|---|---|
| parent(i) | (i − 1) / 2 |
| left(i) | 2·i + 1 |
| right(i) | 2·i + 2 |
[90, 75, 80, 30, 60, 70, 50].
2. The Two Workhorses: Sift-Up & Sift-Down
push(x): append x at the end, then sift up while x > parent. pop(): replace root with last element, shrink, then sift down while it is smaller than the larger child. Both O(log n).
#include <bits/stdc++.h>
using namespace std;
struct MaxHeap {
vector<int> h;
void siftUp(int i) {
while (i > 0) {
int p = (i - 1) / 2;
if (h[p] >= h[i]) break;
swap(h[p], h[i]); i = p;
}
}
void siftDown(int i) {
int n = h.size();
while (2*i + 1 < n) {
int c = 2*i + 1;
if (c+1 < n && h[c+1] > h[c]) c++;
if (h[i] >= h[c]) break;
swap(h[i], h[c]); i = c;
}
}
void push(int x) { h.push_back(x); siftUp(h.size() - 1); }
int top() { return h[0]; }
void pop() {
h[0] = h.back(); h.pop_back();
if (!h.empty()) siftDown(0);
}
};
int main() {
MaxHeap mh;
for (int x : {3, 10, 1, 7, 15, 9}) mh.push(x);
while (!mh.h.empty()) { cout << mh.top() << " "; mh.pop(); }
}
Output: 15 10 9 7 3 1 — descending. That is exactly heapsort.
3. Build-Heap Is O(n) — The Beautiful Proof
Naïve thinking: n inserts × O(log n) = O(n log n). But if we start from the array and sift down from i = n/2 − 1 down to 0, the total work is:
Σ (number of nodes at height h) × O(h) = Σ (n / 2^(h+1)) × h = O(n)
The geometric series converges: most nodes are leaves with height 0; only a few are deep.
অধিকাংশ নোডই leaf — তাদের জন্য sift-down O(0)। তাই মোট কাজ O(n)।
#include <bits/stdc++.h>
using namespace std;
void siftDown(vector<int>& a, int i, int n) {
while (2*i + 1 < n) {
int c = 2*i + 1;
if (c+1 < n && a[c+1] > a[c]) c++;
if (a[i] >= a[c]) return;
swap(a[i], a[c]); i = c;
}
}
void heapsort(vector<int>& a) {
int n = a.size();
// O(n) build
for (int i = n/2 - 1; i >= 0; i--) siftDown(a, i, n);
// O(n log n) extract
for (int i = n - 1; i > 0; i--) {
swap(a[0], a[i]);
siftDown(a, 0, i);
}
}
int main() {
vector<int> a = {5, 2, 8, 1, 9, 3, 7};
heapsort(a);
for (int x : a) cout << x << " ";
}
4. Practical Pattern: K-th Largest with a Min-Heap of Size k
Maintain a min-heap of the k largest values seen so far. Each new value: push, and if size exceeds k, pop the minimum. After all values, the root is the k-th largest. O(n log k).
5. Practice Problems
-
Find the k-th smallest element of an array using a max-heap of size k.size-k max-heap দিয়ে k-th smallest বের করুন।
✨ Show Answer (উত্তর দেখুন)
a1.cpp#include <bits/stdc++.h> using namespace std; int main() { vector<int> a = {7,10,4,3,20,15}; int k = 3; priority_queue<int> pq; for (int x : a) { pq.push(x); if ((int)pq.size() > k) pq.pop(); } cout << pq.top(); } -
Connect ropes to minimise total cost (always join the two shortest).Rope-গুলো এমনভাবে join করুন যাতে মোট cost সর্বনিম্ন হয়।
✨ Show Answer (উত্তর দেখুন)
a2.cpp#include <bits/stdc++.h> using namespace std; int main() { vector<int> r = {4,3,2,6}; priority_queue<int, vector<int>, greater<>> pq(r.begin(), r.end()); long long cost = 0; while (pq.size() > 1) { int a = pq.top(); pq.pop(); int b = pq.top(); pq.pop(); cost += a + b; pq.push(a + b); } cout << cost; } -
Running median of a stream of integers using two heaps (max-heap + min-heap).দুটি heap ব্যবহার করে stream-এর running median বের করুন।
✨ Show Answer (উত্তর দেখুন)
a3.cpp#include <bits/stdc++.h> using namespace std; int main() { priority_queue<int> lo; // max-heap (lower half) priority_queue<int, vector<int>, greater<>> hi; // min-heap (upper half) for (int x : {1,3,5,2,4,6,7}) { lo.push(x); hi.push(lo.top()); lo.pop(); if (hi.size() > lo.size()) { lo.push(hi.top()); hi.pop(); } double med = lo.size() == hi.size() ? (lo.top() + hi.top()) / 2.0 : lo.top(); cout << med << " "; } } -
Sort a nearly-sorted array (each element at most k positions away from its sorted slot) in O(n log k).প্রায়-sorted array (k দূরত্বের মধ্যে সব) — O(n log k)-এ sort করুন।
✨ Show Answer (উত্তর দেখুন)
Approach: push first k+1 elements into a min-heap. For each remaining element, pop one (the smallest of the window) into the output, push the new one. After all input is exhausted, drain the heap.
-
Top-k frequent words in a list (lexicographic tie-break).Top-k frequent word — tie-break dictionary order-এ।
✨ Show Answer (উত্তর দেখুন)
a5.cpp#include <bits/stdc++.h> using namespace std; int main() { vector<string> w = {"i","love","leetcode","i","love","coding"}; int k = 2; map<string,int> cnt; for (auto& s : w) cnt[s]++; auto cmp = [](auto& a, auto& b) { return a.second != b.second ? a.second < b.second : a.first > b.first; }; priority_queue<pair<string,int>, vector<pair<string,int>>, decltype(cmp)> pq(cmp); for (auto& p : cnt) pq.push(p); while (k--) { cout << pq.top().first << " "; pq.pop(); } } -
Use std::make_heap / push_heap / pop_heap on a vector to confirm the STL heap behaves the same.STL heap functions দিয়ে নিজেদের impl-এর সাথে আউটপুট মিলিয়ে দেখুন।
✨ Show Answer (উত্তর দেখুন)
a6.cpp#include <bits/stdc++.h> using namespace std; int main() { vector<int> v = {3,10,1,7,15,9}; make_heap(v.begin(), v.end()); while (!v.empty()) { cout << v.front() << " "; pop_heap(v.begin(), v.end()); v.pop_back(); } } -
Why is heapsort O(n log n) worst-case but quicksort isn't? When would you choose heapsort over quicksort?Heapsort worst-case-এও O(n log n) কেন? কখন quicksort-এর বদলে heapsort বেছে নেবেন?
✨ Show Answer (উত্তর দেখুন)
Answer: heapsort always extracts log-n levels exactly n times — the worst case is the average case. Quicksort's worst-case is O(n²) on adversarial inputs (sorted with bad pivot). Choose heapsort when worst-case guarantees matter (real-time systems, kernel) or memory must be O(1) (heapsort is in-place; quicksort uses O(log n) stack).
Summary — Module 15
A binary heap is a complete tree stored as an array with parent/child arithmetic.
push and pop are O(log n); build-heap is O(n). Heapsort uses build-heap + repeated
extract-max in O(n log n) worst-case, in-place. The same structure is the engine of
std::priority_queue — used by Dijkstra, Huffman, and many top-k problems.