Heap ও Priority Queue — array-তে বসানো tree
Heaps and Priority Queues
Complete binary tree flat array-তে বসে যায় — একটাও pointer নেই; heap-ই একমাত্র tree যার প্রতি ধাপ cache-friendly, আর priority queue-র ইঞ্জিন সেই heap।
আগে এটা বুঝি
Emergency room-এ রোগী আসে যে ক্রমে, দেখা হয় সে ক্রমে না — হার্ট- অ্যাটাক আঙুল-ভাঙার আগে ঢোকে। নিয়ম FIFO না, LIFO-ও না: সবচেয়ে জরুরি আগে।
এই ADT-টা এখনো পর্যন্ত আমরা সামলেছি দুই খারাপ উপায়ে:
- Unsorted array: ঢোকানো
O(1); “সবচেয়ে ছোট” খুঁজতে scanO(n) - Sorted array: min
O(1); ঢোকানো shiftO(n)— dynamic-array লেসনের সেই ব্যথা
দুই প্রান্তেই একটা O(n)। মাঝে এমন স্থাপত্য আছে? ঢোকানো আর extract
দুটোই logarithmic?
উত্তর দুই ধারণার বিয়ে: tree (log-depth) + array (contiguous memory) — শর্ত: tree-টা complete হতে হবে, তাহলেই pointer-ছাড়া, খাঁটি index-arithmetic-এ array-র ভেতরে বাস করে। নাম heap। আজ শেষে নিজের লেখা MinHeap দিয়ে ৫টা sorted list merge করবেন — database-এর sort-merge join, log-collector, map-reduce shuffle যেভাবে করে।
মূল ধারণা
Complete tree-in-array — জাদুটা কোথায়
Complete: শেষ level বাদে সব ভরা, শেষটা বাঁ-থেকে — মাঝে ফাঁক নেই। ঠিক ফাঁক-শূন্যতাই array-mapping-এর শর্ত: level-order-এ নম্বর = array-index!
tree: array:
index: 0 1 2 3 4 5
1 ┌───┬───┬───┬───┬───┬───┐
┌────┴────┐ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │
2 3 └───┴───┴───┴───┴───┴───┘
┌─┴─┐ ┌─┴─┐ ↑ root সবসময় [0]
4 5 6
parent(i) = (i−1)//2 left(i) = 2i+1 right(i) = 2i+2একটাও pointer নেই। Node class নেই, malloc নেই, GC pressure নেই; parent খোঁজা = integer divide। সবকিছু পাশাপাশি memory-তে — sift-এর প্রতি ধাপ প্রায় নিশ্চিত এক cache line-এ বা প্রতিবেশীতে। BST-র pointer-chase-cache-miss-গল্পের টিকা।
Heap property — অর্ধেক শৃঙ্খলা
Min-heap: প্রতিটা node ≤ children। Max-heap mirror। এই নিয়ম কী দেয়, কী দেয় না:
- ✓ Root-ই min — peek
O(1) - ✓ Extract-min মোটে
O(log n)ঝাঁকুনি - ✗ Array sorted না —
[1, 4, 3, 9, 5]valid min-heap! Sibling-দের মধ্যে কোনো সম্পর্ক নেই - ✗ Arbitrary key-খোঁজা
O(n)— heap search-tree না
“অর্ধেক শৃঙ্খলা”-ই ডিজাইনের চালাকি: যে প্রশ্ন করি (“কে min?”) শুধু তার গঠন রক্ষা করি — বাকি শৃঙ্খলার টাকা নষ্ট না। Priority queue-র চাহিদা exactly এটাই।
Push আর Pop — দুইটা ঝাঁকুনি
Push (sift-up): নতুনটা শেষে, parent-এর সাথে compare করে উপরে-উপরে:
push(2) into [1,4,3,9,5]:
[1,4,3,9,5,**2**] index 5; parent=2: a[5]=2 \< a[2]=3 → swap
[1,4,**2**,9,5,3] parent=0: a[2]=2 > a[0]=1 → থামুন ✓Pop (sift-down): root ফেরত; শেষ leaf root-এ (গঠন অক্ষত!), তারপর ছোট child-এর সাথে swap করে নিচে-নিচে:
pop() from [1,4,2,9,5,3]:
root=1 ফেরত; last=3 → root: [3,4,2,9,5]
children 4,2 → ছোট 2 → swap: [2,4,3,9,5] index 2 leaf — শেষ ✓দুটোই ≤ height = ⌊log₂ n⌋ ধাপ → O(log n); প্রতি ধাপ compare + adjacent-
swap — array-তে সস্তা।
Build-heap — counter-intuitive O(n)
n value দিয়ে heap: প্রতিটা আলাদা push = O(n log n)। Floyd-1964-এর
চাল: array যেমন আছে তেমন ধরে শেষ অর্ধ থেকে উল্টোদিকে প্রতিটা internal
node-এ sift-down —
for i in range(len(a) // 2 - 1, -1, -1):
sift_down(i)কেন linear? Height-sum: height h-তে ≤ n/2^(h+1) node, sift-খরচ h:
Σ h/2^h = 2 — convergent series-ই রহস্য। n=15 perfect tree:
| Height h | Node | per-node | মোট |
|---|---|---|---|
| 0 | 8 | 0 | 0 |
| 1 | 4 | ≤1 | 4 |
| 2 | 2 | ≤2 | 4 |
| 3 | 1 | ≤3 | 3 |
| মোট | 11 — n-এর চেয়ে কম |
বেশিরভাগ node leaf/প্রায়-leaf — কাজ শূন্য। Insert-by-insert-এ উল্টো: সব element full-height ভ্রমণের ঝুঁকিতে। ঘড়িতেও দেখবেন (experiment)।
Heapsort — এক লাইনের টিজার
Max-heap O(n) → root (max) বারবার last-এ swap + heap-size কমাও →
array নিজেই sorted। O(n log n) worst-case, zero extra memory, in-place
— sorting module-এ পূর্ণ আলোচনা।
ভেতরে কী ঘটছে
Locality — heap কেন hardware-এর প্রিয়
BST: প্রতি node আলাদা object, descent = unpredictable address, বড় tree- তে ধাপ-প্রতি cache miss। Heap-এ purity:
- Contiguous: পুরো structure এক array — prefetcher-বান্ধব
- Top levels এক line-এ: ৬৪-byte line = ৮× 8-byte int → root + নাতি-দাদি এক line-এ! ৯০% action ওখানেই
- Zero metadata: BST-র প্রতি node ২ pointer (১৬ byte) vs heap শূন্য
Locality of reference-এর textbook উদাহরণ দরকার হলে এটাই দেখান।
d-ary heap — চওড়া বনাম লম্বা
| binary | 4-ary | 1024-ary (B-tree-র গন্ধ!) | |
|---|---|---|---|
| Height | log₂ n | log₄ n (অর্ধেক) | log₁₀₂₄ n |
| Sift-down compare/node | 2 | 4 | 1024 |
| Sift-up compare | 1 | 1 | 1 |
| Layout | compact | compact | compact |
Sift-down প্রতি ধাপে d-compare খায়, কিন্তু ধাপ কমে — ধাপই তো সম্ভাব্য cache miss। Read-mostly, decrease-key-heavy workload-এ (Dijkstra frontier!) ৪-ary practical-এ প্রায়ই জেতে। d টেনে page-size-এ? B-tree বানিয়ে ফেলেছেন — গত-লেসনের চওড়া-node দর্শন, এখানেও।
Decrease-key আর indexed heap
Dijkstra-র দরকার: queue-র ভেতরের item-এর priority কমানো। Naive heap-
এ item-এর current-index জানার উপায় নেই — swaps-এ সবাই ঘোরে। সমাধান:
indexed heap — পাশে dict item → index, প্রতিটা swap-এ sync। দাম:
constant overhead; লাভ: genuine O(log n) decrease-key। Build-এ সংস্করণ
আছে।
Priority queue কোথায় চলছে
- OS schedulerrunqueue — পরের CPU slice কে পাবে; priority vs fairness
- Event loop timersNode.js/libuv timer queue min-heap: nearest deadline আগে
- Graph algorithmsDijkstra, Prim, A* — best-first frontier (আসন্ন modules)
- Streaming & mergingk-way merge, top-k, running median
CPU scheduling-এর পূর্ণ গল্প (multi-level feedback queue) operating-systems- এর cpu-scheduling লেসনে। একটা সৎ disclaimer: production kernel প্রায়ই heap না — Linux CFS rbtree, RT scheduler bitmap খাটায় (ordered iteration/ O(1) group-scan দরকারে); ADT তবু priority-queue-ই।
উদাহরণ
এক MinHeap-এর পূর্ণ জীবন
[5, 3, 8, 1] push-ক্রমে:
push 5: [5]
push 3: [5,3] → 3 \< parent 5 → swap → [3,5]
push 8: [3,5,8] → parent ছোট, জায়গামতো
push 1: [3,5,8,1] → 1\<5 swap → [3,1,8,5] → 1\<3 swap → [1,3,8,5]
tree: 1
/ \
3 8
/
5pop() দুইবার:
pop→1: last(5)→root [5,3,8]; children 3,8 → swap 3 → [3,5,8]
pop→3: last(8)→root [8,5]; child 5 → swap 5 → [5,8]
extract order এখন পর্যন্ত: 1, 3 — sorted! (heapsort-এর seed)একই values-এ heapify (Floyd-পথ):
initial as-is: [5,3,8,1]; last parent = 4//2−1 = 1
sift_down(1): a[1]=3 vs child 1 → ঠিক আছে
sift_down(0): 5 vs children 3,1 → ছোট 1(idx3) → swap → [1,3,8,5]
শেষ: [1,3,8,5] — মোট ২ sift-down, ২ swap; বড় input-এ quadratic-vs-linear ফারাক।ছবিতে
index: │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
value: │ A │ B │ C │ D │ E │ F │ G │ H │ I │ J │
A[0] ← parent(0) নেই
/ \
B[1] C[2] ← parent = (i−1)//2
/ \ / \
D[3] E[4] F[5] G[6] ← leaves start at n//2
/ \
H[7] I[8] ... J[9]
last parent = n//2 − 1 = 4 ← build-heap এখান থেকে উল্টো চলে
leaves = 5..9 ← অর্ধেক array, touch-ই লাগে না! [9] pop: 9 যাবে, last=2 → root
/ \ 2 vs 7,8 → swap with 7
[7] [8] ───────▶
/ \\ [7] [8]
[4] [2] / \\
[4] [2]
path: root → left-left — ২ ধাপ, ৪ compare, ২ swap।
Sibling 8 আর 4/2-subtree-র তুলনা হয়নি — heap তাদের সম্পর্ক জানে না,
জানার দরকারও নেই।নিজে চালিয়ে দেখুন
ঘড়িতে প্রমাণ — heapify vs insort, build-heap-এর O(n)
import heapq, bisect, time, random
random.seed(42)
N = 100_000
data = [random.randint(0, 10**9) for _ in range(N)]
# --- ১. heapify (build-heap) + N pops ---
t0 = time.perf_counter()
h = list(data); heapq.heapify(h)
for _ in range(N):
heapq.heappop(h)
t_heapify = time.perf_counter() - t0
# --- ২. N আলাদা heappush + pop ---
t0 = time.perf_counter()
h = []
for x in data:
heapq.heappush(h, x)
for _ in range(N):
heapq.heappop(h)
t_pushes = time.perf_counter() - t0
# --- ৩. sorted-array PQ: insort + pop(0) ---
t0 = time.perf_counter()
srt = []
for x in data:
bisect.insort(srt, x) # position খোঁজা O(log n), কিন্তু...
while srt:
srt.pop(0) # প্রতি pop = পুরো array left-shift O(n)!
t_insort = time.perf_counter() - t0
print(f'heapify + pops : {t_heapify*1000:8.1f} ms')
print(f'pushes + pops : {t_pushes*1000:8.1f} ms')
print(f'insort + pop(0): {t_insort*1000:8.1f} ms'
f' (heapify-র তুলনায় {t_insort/t_heapify:.0f}x)')laptop-নমুনা output (সংখ্যা machineভেদে বদলাবে, অনুপাত গল্প বলে):
heapify + pops : 56.2 ms
pushes + pops : 63.6 ms
insort + pop(0): 1967.2 ms (heapify-র তুলনায় 35x)তিন reading: (১) insort-এর ৩৫× — position-খোঁজা সস্তা, খুনি pop(0)-shift
আর insert-memmove; (২) heapify-vs-pushes ~১৩% — C-level heapq-তে চাপা,
pure-Python loop-এ বহুগুণ ফাটবে (নিজের build-এর MinHeap দিয়ে চালান);
(৩) list.pop(0) আসলে O(n) copy — deque দিলে pop-side সস্তা, insort-shift
থেকেই যাতে।
নিজে বাড়ান: N = 10k→100k→1M curve — insort quadratic (N×10 → time×~100), heap n-log-n প্রায় সরলরেখা।
100k random item-এ sorted-array PQ (insort+pop(0)) heap-কে ~৩০× খায় — insort-এর hidden O(n)-shift; আর heapify প্রতিটা আলাদা push-এর চেয়েও দ্রুত — build-heap linear-time-এর direct measurement।
নিজে বানান
MinHeap from scratch — heapify, decrease-key, k-way merge driver
- __init__(items) → bottom-up heapify
- _sift_up / _sift_down — প্রতিটা compare-swap explicit
- push / pop / peek — O(log n)
- decrease_key(i, new_val) — উপরে ভাসানো
- k_way_merge(lists): heap-এ (value, list_idx, elem_idx) রেখে ৫-list merge
class MinHeap:
"""0-indexed: parent=(i-1)//2, children=2i+1, 2i+2"""
def __init__(self, items=None):
self.a = list(items) if items else []
for i in range(len(self.a) // 2 - 1, -1, -1): # last parent → root
self._sift_down(i)
def __len__(self):
return len(self.a)
def push(self, x):
self.a.append(x)
self._sift_up(len(self.a) - 1)
def pop(self):
top, last = self.a[0], self.a.pop()
if self.a:
self.a[0] = last # গঠন অক্ষত রাখার চাল
self._sift_down(0)
return top
def peek(self):
return self.a[0]
def decrease_key(self, i, new_val):
assert new_val \<= self.a[i], 'increase-এ sift_down লাগত'
self.a[i] = new_val
self._sift_up(i)
def _sift_up(self, i):
a = self.a
while i > 0:
p = (i - 1) // 2
if a[i] \< a[p]:
a[i], a[p] = a[p], a[i]; i = p
else:
break
def _sift_down(self, i):
a, n = self.a, len(self.a)
while True:
l, r, m = 2*i + 1, 2*i + 2, i
if l \< n and a[l] \< a[m]: m = l
if r \< n and a[r] \< a[m]: m = r
if m == i:
return
a[i], a[m] = a[m], a[i]
i = m
def k_way_merge(lists):
"""k sorted list → এক sorted list, O(N log k)"""
h = MinHeap()
for li, lst in enumerate(lists):
if lst:
h.push((lst[0], li, 0)) # (মান, কোন list, কোন index)
out = []
while len(h):
v, li, j = h.pop()
out.append(v)
nxt = j + 1
if nxt \< len(lists[li]):
h.push((lists[li][nxt], li, nxt))
return out
if __name__ == '__main__':
import random
random.seed(1)
data = [random.randint(0, 99) for _ in range(15)]
h = MinHeap(data)
got = [h.pop() for _ in range(len(h))]
assert got == sorted(data)
print('pop order:', got[:6], '...')
tasks = [(9,'backup'), (7,'report'), (5,'email'), (3,'deploy'), (1,'monitor')]
q = MinHeap(tasks)
q.decrease_key(tasks.index((7, 'report')), (0, 'report'))
order = [q.pop() for _ in range(len(q))]
assert order[0] == (0, 'report')
print('after decrease:', order[:3])
lists = [[1,4,9,16],[2,6,11],[3,8],[0,5,12,20],[7,10]]
merged = k_way_merge(lists)
print('merged:', merged)
assert merged == sorted(sum(lists, []))
print('সব assert পাস')প্রত্যাশিত output:
pop order: [8, 12, 15, 17, 26, 32] ...
after decrease: [(0, 'report'), (1, 'monitor'), (5, 'email')]
merged: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20]
সব assert পাসmerge-হিসাব: heap-এ সবসময় ≤ k=5 entry; প্রতি output element = pop+push =
O(log k); মোট O(N log k) — naive concat+sort O(N log N), pairwise
O(N·k)-এর বিপরীতে। k ছোট, N বিশাল — external merge sort এখানেই দাঁড়িয়ে।
নিজে বাড়ান: (১) delete(i) = decrease_key(i, −∞)+pop; (২) MaxHeap —
comparison উল্টানো বনাম value-negate, pros লিখুন; (৩) indexed version —
pos dict swap-এ sync করে decrease_key(item, val) interface (Dijkstra-
ready); (৪) নিজের heap vs heapq benchmark — constant-factor gap টের পান,
asymptotic একই।
বাস্তব সিস্টেমে
Heap যেখানে যেখানে
- Event loops: Node.js/libuv timers min-heap — nearest deadline
root, tick-এ peek
O(1);setTimeout-এর পেছনে আজকের structure। - Database internals: PostgreSQL external sort-এর final merge phase k-way heap-merge; sort-merge join একই ইঞ্জিন।
- Graph algorithms: Dijkstra/Prim/A* frontier — shortest-paths ও MST modules-এর প্রাণ; সেখানে indexed/decrease-key বাধ্যতামূলক।
- Streaming: top-k trending (fixed max-heap), running median (two-heaps), sliding-window extremes।
OS-scheduler-এর theory আজ; multi-level feedback queue-র বাস্তবতা operating-systems module-এ cpu-scheduling লেসনে।
যে ভুলগুলো সবাই করে
“Heap-এ element sorted অবস্থায় থাকে।”
না — guarantee শুধু parent ≤ children। [1, 5, 2, 9, 8, 3] বৈধ min-heap,
যদিও 5 > 2! Sibling/cousin-দের মধ্যে ordering নেই। শুধু “min কে?”-এর
উত্তরের জন্য যতটুকু শৃঙ্খলা, ততটুকুই — বেশি শৃঙ্খলা = বেশি maintenance
cost প্রতি op-এ।
“Build-heap O(n) হলে প্রতি insert O(log n)-এর সাথে contradiction।”
ভিন্ন প্রশ্ন। N-আলাদা insert-এ প্রতিটা element full-height ভ্রমণের ঝুঁকি; heapify-তে অর্ধেক node leaf (খরচ ০), চতুর্থাংশ এক-ধাপ… height-sum ≤ 2n। একই elements, ভিন্ন কাজ-বণ্টন: heapify কাজ গভীরতার বিপরীত অনুপাতে দেয়। Counter-intuitive কিন্তু consistent — experiment-এ ঘড়িতেও মিলেছে।
“Priority queue = heap — synonym।”
Priority queue ADT — contract: insert/peek-min/delete-min (+optional decrease-key), complexity-প্রতিশ্রুতি নেই। Heap এক implementation। বিকল্প: sorted/unsorted array (ছোট n-এ জেতে!), balanced BST (ordering দরকারে), binomial/pairing/Fibonacci heap (Fibonacci-র decrease-key amortized O(1) — theory-র রাজা, practice-এ constants-ভারে বিরল)। “Design a PQ?” — প্রথম উত্তর operation-mix জিজ্ঞেস করা।
“Heap BST-র মতো searching-এও কাজে দেয়।”
Arbitrary key-খোঁজা O(n) — sibling-দের মধ্যে কোনো ক্রম নেই, target বাঁ-ডান দুই subtree-তেই থাকতে পারে; কিছুই বাদ পড়ে না, পুরো scan। BST- তে প্রতি ধাপে অর্ধেক বাদ যেত (“ছোট মানে বাঁয়ে”)। Membership-test দরকার হলে পাশে hash-set রাখুন। দুই structure, দুই চুক্তি — interchange নেই।
বুঝেছেন কি না দেখুন
1Array [9, 4, 7, 1, 3] valid min-heap? না হলে heapify দিয়ে ঠিক করুন
(0-indexed)।
স্মরণ
[9, 4, 7, 1, 3] valid min-heap? না হলে heapify দিয়ে ঠিক করুন
(0-indexed)।না — idx 3-এর value 1, parent(idx1)=4-এর চেয়ে ছোট। Last parent = 1।
sift_down(1): 4 vs children 1,3 → swap 1 → [9,1,7,4,3];
sift_down(0): 9 vs 1,7 → swap 1 → [1,9,7,4,3]; idx1-এ 9 vs 4,3 →
swap 3 → [1,3,7,4,9] ✓। মোট ৩ swap — কাজ শুধু ২ internal node-এ, ৩
leaf untouched।
2Min-heap [2, 4, 5, 8, 6, 7] — পরপর দুই pop: কী ফেরত, array কী?
প্রয়োগ
[2, 4, 5, 8, 6, 7] — পরপর দুই pop: কী ফেরত, array কী?Pop ১: ফেরত 2; last(7)→root [7,4,5,8,6]; swap 4 → [4,7,5,8,6];
idx1-এ 7 vs 8,6 → swap 6 → [4,6,5,8,7]।
Pop ২: ফেরত 4; last(7)→root [7,6,5,8]; children 6,5 → swap 5 →
[5,6,7,8]।
Extract: 2, 4। প্রতি pop-এ last-leaf root-এ (গঠন perfect থাকে), নামা
এক path-এ — তাই pop কখনো O(height) ছাড়ায় না।
3n=31 (perfect): build-heap worst-case মোট sift-down কত? Formula দিয়ে
হিসাব করুন।
যুক্তি
Height h-এ node 2^h, cost h: 16·0 + 8·1 + 4·2 + 2·3 + 1·4 = 26 — 31-এর চেয়ে কম।
Formula bound 2n = 62 upper — actual 26 (perfect-এ tight form আরো
নিচে)। Insert-by-insert হতো Θ(n log n)-path। Linear-vs-nlogn ফারাকই
Floyd-এর কয়েক পাতাকে ৬০ বছর textbook-এ রেখেছে।
4Dijkstra dense graph (V=10⁴, E≈V²): frontier-এ binary heap / 4-ary /
sorted array? Mix: delete-min V বার, decrease-key E বার।
ডিজাইন
Decrease-key E≈10⁸ বার — dominant; সেটাই সস্তা যেটায়।
Sorted array: decrease-key move O(V) → dead। Binary indexed-heap:
দুটোই O(log V) → ~১.৪×10⁹ ops — চলে কিন্তু ভারী। 4-ary indexed:
height অর্ধেক (log₄ V ≈ ৭), আর sift-up-এ child-count-compare হয় না —
E-dominated workload-এ জেতে; dense-Dijkstra benchmark-ও তাই বলে।
শিক্ষা: asymptotic-class-এর ভেতরে constants × operation-mix-weighted যোগফলই সিদ্ধান্ত।
5৫০০ sorted file, মোট ১০⁹ record — merge cost estimate: naive pairwise
vs k-way heap (read/write = ১ unit)।
প্রয়োগ
Naive pairwise: প্রথম file ৪৯৯ বার rewrite → write ≈ N·(k−1) ≈ ৫×10¹¹
(read-ও সমান) — quadratic-in-k, অসম্ভব। K-way heap: read+write ঠিক 2N = ২×10⁹; CPU-side প্রতি record O(log k) (~৯) heap-op — RAM-এ। I/O ফারাক
~২৫০×, disk-bound-এ সেটাই সময়। বাস্তবে buffer-pool + প্রতি file-এর current
record heap-এ — আজকের k_way_merge skeleton-ই।
এরপর কী
এরপর কী
Heap: pointer-free tree, O(log n) insert/extract, linear build —
priority queue-র সর্বজনীন ইঞ্জিন। কিন্তু সে শুধু “min/max” জানে — string
prefix-জাতীয় গঠনগত প্রশ্নে অপ্রস্তুত।
পরের লেসনে খোঁজার জগৎ বদলাবে: Trie — key character-ধরে edge-এ, lookup-খরচ dictionary-size-independent, শুধু word-length-driven। Keyboard- autocomplete, router-এর longest-prefix-match — trie-র বংশধর; সাথে সৌন্দর্য-vs-memory দ্বন্দ্ব আর তার সমাধান radix compression — নিজের হাতে implement করবেন।
আরও পড়ুন
- "Algorithm 232: Heapsort" (Communications of the ACM, 1964) — J. W. J. Williams · Heap-এর জন্মপত্র — heapsort-এর অংশ হিসেবে প্রথম প্রকাশ
- "Algorithm 245: Treesort 3" (Communications of the ACM, 1964) — Robert W. Floyd · Bottom-up heap construction — O(n) build-heap-এর মূল ভাবনা, একই জার্নালে কয়েক মাস পরে!
- Introduction to Algorithms, 3rd ed., Chapter 6 — Heapsort — Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein · Build-heap-এর O(n) bound-এর পূর্ণ প্রমাণ আর d-ary heap exercises