Foundationপ্রথম নীতি থেকে
LEVEL 6লেসন ১০/২৪কঠিন৫০ মিনিট

Heap ও Priority Queue — array-তে বসানো tree

Heaps and Priority Queues

Complete binary tree flat array-তে বসে যায় — একটাও pointer নেই; heap-ই একমাত্র tree যার প্রতি ধাপ cache-friendly, আর priority queue-র ইঞ্জিন সেই heap।

এই লেসন শেষে আপনি পারবেন

  • complete binary tree-কে array-index arithmetic দিয়ে pointer ছাড়া represent করে parent-child relation হাতে হিসাব করতে পারবেন
  • sift-up/sift-down mechanics দিয়ে push, pop, heapify trace করে প্রতিটা swap-এর কারণ বলতে পারবেন
  • build-heap কেন O(n), যখন n-টা insert O(n log n) — height-sum argument দিয়ে প্রমাণ-খসড়া লিখতে পারবেন
  • priority queue-র application (scheduler, event-loop timer, k-way merge, Dijkstra teaser) চিনে কোন variant (d-ary, indexed) লাগবে তা বেছে নিতে পারবেন
  • নিজের MinHeap দিয়ে ৫টা sorted list merge করে O(N log k)-এর সুবিধা মেপে দেখাতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

Emergency room-এ রোগী আসে যে ক্রমে, দেখা হয় সে ক্রমে না — হার্ট- অ্যাটাক আঙুল-ভাঙার আগে ঢোকে। নিয়ম FIFO না, LIFO-ও না: সবচেয়ে জরুরি আগে।

এই ADT-টা এখনো পর্যন্ত আমরা সামলেছি দুই খারাপ উপায়ে:

  • Unsorted array: ঢোকানো O(1); “সবচেয়ে ছোট” খুঁজতে scan O(n)
  • Sorted array: min O(1); ঢোকানো shift O(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=0⌊log⁡2n⌋n2h+1⋅h  ≤  n∑h=0∞h2h  =  2n\sum_{h=0}^{\lfloor \log_2 n \rfloor} \frac{n}{2^{h+1}} \cdot h \;\le\; n \sum_{h=0}^{\infty} \frac{h}{2^h} \;=\; 2n

Σ h/2^h = 2 — convergent series-ই রহস্য। n=15 perfect tree:

Height hNodeper-nodeমোট
0800
14≤14
22≤24
31≤33
মোট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 — চওড়া বনাম লম্বা

binary4-ary1024-ary (B-tree-র গন্ধ!)
Heightlog₂ nlog₄ n (অর্ধেক)log₁₀₂₄ n
Sift-down compare/node241024
Sift-up compare111
Layoutcompactcompactcompact

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 কোথায় চলছে

Priority queue-র চারটা প্রাণকেন্দ্র
  1. OS schedulerrunqueue — পরের CPU slice কে পাবে; priority vs fairness
  2. Event loop timersNode.js/libuv timer queue min-heap: nearest deadline আগে
  3. Graph algorithmsDijkstra, Prim, A* — best-first frontier (আসন্ন modules)
  4. 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
             /
            5

pop() দুইবার:

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-ই লাগে না!
Dual identity — এক array, দুই চোখে দুই রূপ; index-সূত্র দোভাষী।
        [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 তাদের সম্পর্ক জানে না,
জানার দরকারও নেই।
Sift-down decision path — প্রতি ধাপে ২ compare, ১ swap; height-bounded।

নিজে চালিয়ে দেখুন

EXPERIMENT

ঘড়িতে প্রমাণ — heapify vs insort, build-heap-এর O(n)

Python 3.8+· ১০ মিনিট
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।

নিজে বানান

BUILD IT

MinHeap from scratch — heapify, decrease-key, k-way merge driver

Python · ●●○○○
  1. __init__(items) → bottom-up heapify
  2. _sift_up / _sift_down — প্রতিটা compare-swap explicit
  3. push / pop / peek — O(log n)
  4. decrease_key(i, new_val) — উপরে ভাসানো
  5. 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 নেই।

বুঝেছেন কি না দেখুন

1

Array [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।

2

Min-heap [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) ছাড়ায় না।

3

n=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-এ রেখেছে।

4

Dijkstra 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