Foundationপ্রথম নীতি থেকে
LEVEL 6লেসন ৫/২৪মাঝারি৩৫ মিনিট

Stack, Queue, Deque — discipline থেকে শক্তি

Stacks, Queues and Deques

Stack, Queue আর Deque নতুন storage না — এরা access-discipline: কোন প্রান্তে ঢোকা-বেরোনা ঠিক করলেই recursion, scheduling, BFS/DFS আর undo-র পুরো machinery abstract level-এ ওঠে।

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

  • LIFO আর FIFO-কে access-discipline contract হিসেবে সংজ্ঞায়িত করে ব্যাখ্যা করতে পারবেন — underlying storage বদলালেও discipline টিকে থাকে কেন
  • Stack/Queue/deque-এর implementation choices-এর head/tail trade-off বিশ্লেষণ করতে পারবেন — কোন array-end combination কোন অপারেশনকে O(1) আর কোনটাকে O(n) বানায়
  • Call stack, undo history, DFS/BFS worklist, scheduler runqueue — প্রতিটা system-এ কোন discipline কেন খাপ খায় তা মেলাতে পারবেন
  • Monotonic stack pattern দিয়ে next-greater-element সমস্যা linear time-এ solve করতে পারবেন আর amortized argument দিয়ে justify করতে পারবেন
  • Circular buffer দিয়ে fixed-memory queue implement করে head/tail wraparound handle করতে পারবেন

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

আগে এটা বুঝি

আজকের তিনটা structure নতুন memory layout শেখায় না — গত দুই লেসনের array আর linked node দিয়েই সবগুলো বানানো যায়। নতুন জিনিসটা contract: কোন প্রান্তে ঢোকা যাবে, কোন প্রান্তে বেরোনো যাবে। এই restriction-ই এদের শক্তি — যখন আপনি কোডে stack বা queue নাম দেখেন, আপনি ওই মুহূর্তেই পুরো behavior জানেন, implementation না দেখেই।

পৃথিবীটা এদের দিয়ে ভরা: প্রতিটা function call আপনার হয়ে call stack- এ push হচ্ছে (operating-systems module-এর stack-and-heap লেসন মনে আছে?), প্রতিটা Ctrl+Z undo stack pop করছে, BFS queue দিয়ে graph ঘুরছে, OS scheduler runqueue দিয়ে process বেছে নিচ্ছে। Discipline শেখা মানে এসব system-এর shared grammar শেখা।

আজকের প্রশ্ন তিনটা: (১) LIFO/FIFO contract ঠিক কী promise করে, আর কী করে না? (২) Array-র কোন প্রান্ত দিয়ে ঢোকানো-বেরোনো করলে implementation সস্তা হয় — আর deque সেই trade-off কীভাবে ভাঙে? (৩) একটা plain stack দিয়ে “পরের বড় element” জাতীয় মনে-হয়-O(n²) সমস্যা কীভাবে linear হয়ে যায়?

মূল ধারণা

তিনটা contract

StructureInsertRemoveInspectContract
Stackpush (top)pop (top)peek/topLIFO — last in, first out
Queueenqueue (back)dequeue (front)frontFIFO — first in, first out
Dequeadd front/backremove front/backboth endsদুই প্রান্তেই full access

খেয়াল করুন কী নেই contract-এ: middle insertion, search by value, random index। এগুলো deliberately বাদ — interface ছোট মানে invariant শক্ত, reasoning সহজ। Stack-এ কোনো element “k-th from bottom” চাওয়া contract-violation; ওই দাবিটা করতে চাইলে আপনি আসলে অন্য structure চাইছেন।

Implementation — প্রান্ত-নির্বাচনের গণিত

Array দিয়ে stack: top-কে tail রাখলে push/pop দুই-ই slot write — O(1), কোনো shift নেই। Perfect fit।

Queue-টা tricky: দুইটা প্রান্ত দরকার (front-এ বেরোনো, back-এ ঢোকা)। Array-র head (index 0) দিয়ে dequeue করলে প্রতিবার সব shift — O(n)! তাই options:

option A: two-index sliding window      option B: circular buffer

[ f . . . . b ]                         [ b . f . . . . . ]
  dequeue -> f++                          dequeue -> f++
  enqueue -> b++                          enqueue -> b++ (wrap!)
  সমস্যা: window ডানে সরতে থাকে,          head/tail modulo capacity —
  বাঁ দিকের জায়গা মরে যায়                 সব slot recycle হয় ✓

Python list.pop(0) option-A-র সবচেয়ে খারাপ রূপ (shift included); collections.deque হলো block-linked structure — দুই প্রান্তেই O(1), গত লেসনে দেখা hybrid। Circular buffer fixed-capacity world-এর উত্তর — build-এ বানাব।

কোথায় কে বাস করে

  • Call stack — function call = frame push, return = pop। Recursion আসলে stack discipline-এর syntax sugar; stack overflow মানে এই structure-টাই capacity-র বাইরে।
  • Undo — প্রতিটা edit push; Ctrl+Z pop। Redo হলো দ্বিতীয় stack — undo-pop করা item সেখানে push।
  • BFS worklist — queue: আবিষ্কৃত-order-এ process, shortest-path guarantee (unweighted)।
  • Scheduler runqueue — round-robin মানে circular queue: চলা process back-এ re-enqueue।
  • Browser history — back stack + forward stack, navigation-এ দুই stack-এর মধ্যে transfer।

ভেতরে কী ঘটছে

Deque-এর ভেতরে — blocked compromise

CPython-এর collections.deque: ৬৪-slot contiguous block-গুলোর একটা doubly-linked chain। Layout:

deque [a,b,c,...,z]

        ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
        │ blk0: 64 ptr │◀──▶│ blk1: 64 ptr │◀──▶│ blk2: 64 ptr │
        └──────────────┘    └──────────────┘    └──────────────┘
              ▲                                        ▲
           leftmost                                 rightmost

appendleft মানে blk0-এ ফাঁকা slot থাকলে এক write — নতুন block allocate শুধু ভরা হলে (amortized again!)। popleft symmetric। Indexing i = block-chain walk + offset — ধীর কিন্তু সম্ভব। Design lesson: pure linked (per-node overhead) আর pure array (end-op shift) — দুই চরমের খরচ block-size parameter দিয়ে tune করা যায়। Block size ১ হলে linked list, অসীম হলে single array — deque হলো dial-এর মাঝের ঘর।

Monotonic stack — pattern-টা নিজে

সমস্যা: array-র প্রতিটা element-এর ডানে প্রথম বড় element কোনটা?

Naive: প্রতি element-এর জন্য ডানে scan — O(n²)। Monotonic stack: elements-কে stack-এ রাখা হয় strictly decreasing অবস্থায়। নতুন element x এলে stack-এর top থেকে সব ছোট element pop করে দাও — প্রতিটা pop হওয়া element-এর answer হলো x। তারপর x push।

xs = [3, 1, 4, 1, 5]

x=3: stack [3]
x=1: 1 ছোট 3-এর, push     → [3,1]
x=4: pop 1 (answer 4), pop 3 (answer 4), push → [4]
x=1: push                  → [4,1]
x=5: pop 1 (ans 5), pop 4 (ans 5), push → [5]
answers: 3→4, 1→4, 4→5, 1→5, 5→None

কেন linear? প্রতিটা element ঠিক একবার push + ঠিক একবার pop — গত লেসনের multipop-stack aggregate argument হুবহু: total ops ≤ 2n। Amortized thinking আবার ফিরে এল — এবার algorithm design-এর tool হয়ে।

deque.popleft() — একটা O(1)-এর ভেতরটা
  1. Method dispatchC-implemented method — Python-level loop নেই, interpreter tax একটাই
  2. Block checkleftmost block-এ element আছে কি না — lenfield decrement, কোনো shift নেই
  3. Slot readblock-এর current-leftmost pointer read + advance — contiguous block, cache-friendly
  4. Block retireblock খালি হলে chain থেকে unlink — free list-এ; এটাই মাঝে মাঝের দামি মুহূর্ত

উদাহরণ

Balanced parentheses — stack-এর ছোট্ট কিন্তু পূর্ণ গল্প

{[()()]} valid? ([)] invalid? Rule: প্রতিটা opener-এর matching closer ঠিক উল্টো order-এ আসে — LIFO-র হাতের লেখা definition!

def balanced(s):
    pairs = {')': '(', ']': '[', '}': '{'}
    openers = set(pairs.values())
    stack = []
    for ch in s:
        if ch in openers:
            stack.append(ch)
        elif ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False
    return not stack

# balanced('{[()()]}') -> True
# balanced('([)]')     -> False   (pop '(' পায় ']' এর জন্য '[' চেয়েছিল)

Trace ([)]: push (, push [, ] আসে — top ( ≠ [ → fail। Stack discipline mismatch-কে ধরে ফেলে ঠিক যেখানে হয়। Compiler, linter, JSON parser — সবার ভেতরে এই ছোট function-এর বংশধর।

ছবিতে

তিন discipline, এক ছবি

STACK (LIFO):            QUEUE (FIFO):           DEQUE:

      push ↓                enq ↓    ↑ deq          ↓↑      ↓↑
   ┌─────────┐           ┌─────────────┐        ┌────────────┐
   │  ▓ new  │           │ ▓ oldest    │        │ ▓      ▓   │
   │  ▒      │           │ ░           │        │ ▒      ▒   │
   │  ░ old  │           │ ░           │        │ ░      ░   │
   └─────────┘           └─────────────┘        └────────────┘
   same end                opposite ends           both ends
capacity=8 circular buffer, কয়েক step পর:

idx:    0    1    2    3    4    5    6    7
      [ C ][ D ][ E ][ _ ][ _ ][ X ][ Y ][ Z ]
             ▲tail                ▲head

dequeue -> X (head: 5→6)
enqueue F -> idx3 (tail: 3→4)      ← wraparound! 7-এর পরে 0-তে না,
                                      এখানে ফাঁকা 3-এ
full condition: (tail+1) mod cap == head
empty condition: tail == head
Circular buffer — fixed memory-তে infinite queue illusion; head আর tail pointer ঘুরতে থাকে, slots recycle হয়, কোনো reallocation নেই।

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

EXPERIMENT

popleft বনাম pop(0) — FIFO discipline-এর দুই দাম

Linux/macOS, Python 3.8+· ১০ মিনিট
# fifo_lab.py — same contract, different invoices
# চালান: python3 fifo_lab.py
import time
from collections import deque

N = 100_000

def bench(fn):
    t0 = time.perf_counter()
    fn()
    return time.perf_counter() - t0

def drain_list():
    q = list(range(N))
    while q:
        q.pop(0)               # প্রতিবার বাকিদের shift — O(size)

def drain_deque():
    q = deque(range(N))
    while q:
        q.popleft()            # প্রান্ত-পয়েন্টার advance — O(1)

t_list = bench(drain_list)
t_dq = bench(drain_deque)
print(f'draining {N:,} elements FIFO-style:')
print(f'  list.pop(0)  : {t_list*1000:9.1f} ms')
print(f'  dq.popleft() : {t_dq*1000:9.1f} ms')
print(f'  ratio        : {t_list/t_dq:9.1f}x')

# scaling proof: doubling-এ deque flat, list quadratic-ish
def drain_list_at(n):
    q = list(range(n))
    while q:
        q.pop(0)

def drain_deque_at(n):
    q = deque(range(n))
    while q:
        q.popleft()

print('\ndoubling check:')
for n in (25_000, 50_000, 100_000):
    td = bench(lambda: drain_deque_at(n))
    tl = bench(lambda: drain_list_at(n))
    print(f'  n={n:>6,}: list {tl*1000:8.1f} ms | deque {td*1000:6.1f} ms '
          f'| ratio {tl/max(td, 1e-9):6.0f}x')
python3 fifo_lab.py

Typical ফলাফল:

draining 100,000 elements FIFO-style:
  list.pop(0)  :   812.4 ms
  dq.popleft() :     4.1 ms
  ratio        :   198.1x

doubling check:
  n= 25,000: list    51.2 ms | deque    1.0 ms | ratio     51x
  n= 50,000: list   203.8 ms | deque    2.0 ms | ratio    102x
  n=100,000: list   812.4 ms | deque    4.1 ms | ratio    198x

Reading: list side প্রতি doubling-এ ×4 — quadratic signature (n বার pop, প্রতি pop গড়ে n/2 shift); deque side ×2 — linear total, per-op constant। Ratio-ও প্রতি doubling-এ ×2 — গত লেসনের ratio-of-ratios method আবার কাজে। যা প্রমাণিত: “queue ব্যবহার করেছি” বলাই যথেষ্ট না — implementation-টা আপনার algorithm-এর complexity class নিজেই বদলে দিতে পারে।

এটা কী প্রমাণ করে

একই FIFO discipline-এর দুই implementation-এ per-operation cost রাতদিন: deque.popleft constant থাকে n বাড়লেও, list.pop(0) linearly বাড়ে — implementation choice discipline-এর সঙ্গে যুক্ত না হলে asymptotic class-ই বদলে যায়।

নিজে বানান

BUILD IT

Queue + Stack + CircularBuffer + monotonic solver — চার-এক kit

Python · ●●○○○
  1. Array-backed Queue লিখুন head-index sliding approach-এ — dequeue-এ shift নেই, শুধু head++
  2. Wasted-space দেখুন — head reset/compaction কখন লাগে তা মাপুন
  3. CircularBuffer লিখুন fixed capacity, modulo arithmetic, full/empty conditions আলাদা করে
  4. MonotonicStack-based next_greater(xs) লিখুন — প্রতিটা element push+pop exactly once
  5. তিনটা structure-কে একই workload দিয়ে cross-benchmark করুন

লক্ষ্য: discipline-গুলো নিজের হাতে concrete করা, আর monotonic pattern- এর amortized claim runtime counter দিয়ে verify করা।

# discipline_kit.py — Queue, CircularBuffer, monotonic stack
# চালান: python3 discipline_kit.py


class ArrayQueue:
    """Sliding-head queue — dequeue O(1), কিন্তু space ফেলে যায়।"""
    def __init__(self):
        self.buf = []
        self.head = 0

    def enqueue(self, v):
        self.buf.append(v)

    def dequeue(self):
        if self.head >= len(self.buf):
            raise IndexError('empty queue')
        v = self.buf[self.head]
        self.head += 1
        return v

    def __len__(self):
        return len(self.buf) - self.head


class CircularBuffer:
    """Fixed-capacity queue — zero allocation after init."""
    def __init__(self, capacity):
        self.cap = capacity
        self.buf = [None] * capacity
        self.head = 0
        self.count = 0

    def enqueue(self, v):
        if self.count == self.cap:
            raise IndexError('buffer full')
        self.buf[(self.head + self.count) % self.cap] = v
        self.count += 1

    def dequeue(self):
        if self.count == 0:
            raise IndexError('buffer empty')
        v = self.buf[self.head]
        self.head = (self.head + 1) % self.cap
        self.count -= 1
        return v

    def __len__(self):
        return self.count


def next_greater(xs):
    """Monotonic stack — returns answers[i] = first greater to the right."""
    answers = [None] * len(xs)
    stack = []                       # indices; values strictly decreasing
    pushes = pops = 0
    for i, x in enumerate(xs):
        while stack and x > xs[stack[-1]]:
            answers[stack.pop()] = x
            pops += 1
        stack.append(i)
        pushes += 1
    print(f'  internal ledger: pushes={pushes} pops={pops} '
          f'total={pushes + pops} (bound: 2n = {2 * len(xs)})')
    return answers


if __name__ == '__main__':
    print('next-greater-element:')
    xs = [3, 1, 4, 1, 5, 9, 2, 6]
    print(f'  input : {xs}')
    print(f'  answer: {next_greater(xs)}')

    import random, time
    big = [random.randint(0, 10**6) for _ in range(200_000)]
    t0 = time.perf_counter()
    next_greater(big)
    dt = time.perf_counter() - t0
    print(f'  200k elements: {dt*1000:.1f} ms — linear confirmed')

    print('circular buffer wraparound:')
    cb = CircularBuffer(capacity=4)
    for v in (10, 20, 30, 40):
        cb.enqueue(v)
    print('  dequeued:', cb.dequeue(), cb.dequeue())
    cb.enqueue(50); cb.enqueue(60)
    drained = []
    while len(cb):
        drained.append(cb.dequeue())
    print('  after wrap:', drained, '(order preserved)')

প্রত্যাশিত output:

next-greater-element:
  input : [3, 1, 4, 1, 5, 9, 2, 6]
  answer: [4, 4, 5, 5, 9, None, 6, None]
  internal ledger: pushes=8 pops=6 total=14 (bound: 2n = 16)
  200k elements: 118.3 ms — linear confirmed
circular buffer wraparound:
  dequeued: 10 20
  after wrap: [30, 40, 50, 60] (order preserved)

Ledger line-টাই আসল প্রমাণ: total operations কখনো 2n পার করে না — amortized O(1) per element, measurement আর theory একই সংখ্যায় মিলছে।

নিজে বাড়ান:

  1. ArrayQueue-তে compaction যোগ করুন — head অর্ধেক buffer পার হলে slice-shift; এটা amortized analysis-এর আরেক বাস্তব কেস
  2. CircularBuffer-এ overwrite-on-full mode যোগ করুন (telemetry ring buffer semantics) — পুরনো data silently ফেলে দেওয়া
  3. Sliding-window maximum বানান — deque দিয়ে (indices decreasing monotonic রেখে) — monotonic pattern-এর sibling

বাস্তব সিস্টেমে

Discipline-গুলো যেখানে পৃথিবী চালায়

Call stack আর exception unwinding। প্রতিটা language runtime-এর function-call machinery stack-disciplined — OS module-এর stack-and-heap লেসনের সরাসরি ধারাবাহিকতা। Exception ছোঁড়া মানে frames pop হয়ে যাওয়া until handler — LIFO-ই cleanup ordering নির্ধারণ করে (finally blocks, destructors)।

Producer-consumer queues। Message broker (Kafka partition log, RabbitMQ queue), thread-pool task queue, Go channels — FIFO discipline fairness-এর সবচেয়ে সস্তা approximation। Scheduler starvation-এর বিরুদ্ধে round-robin = circular queue + time slice।

BFS আর level-order। Graph shortest path (unweighted), web crawler frontier, tree serialization — queue discipline-ই “nearest first” guarantee দেয়। Worklist abstraction ছাড়া graph algorithms-এর অর্ধেক লিখা কঠিন।

Undo/redo আর command patterns। Editor, design tools, database transaction logs — প্রতিটা reversible operation system-এ দুই stack- এর নাচ। Command object + stack = time travel।

Rate limiters আর sliding windows। Fixed-memory circular buffer timestamp-দের — “শেষ এক মিনিটে কত request” — O(1) update, O(window) query না করেই। Ring buffer-এর সবচেয়ে practical modern use।

যে ভুলগুলো সবাই করে

“Stack আর queue আলাদা data structure — নতুন layout শিখতে হবে।”

নতুন layout কিছুই না — দুটোই array বা linked nodes-এর উপর thin contract। নতুনটা হলো discipline: কোন প্রান্ত খোলা। এজন্যই একই underlying buffer উপর আলাদা API wrapper আলাদা structure “হয়ে” যায়। Interface সরু হওয়াটা দুর্বলতা না — এটাই property: implementation swap করা যায় (array → deque → linked) caller-এর অজান্তে, যতক্ষণ contract মানা হয়।

“Python list-ই universal — queue দরকার হলে append+pop(0) চলবে।”

Contract মিলে, invoice মেলে না। pop(0) প্রতি কলে সব shift — গোটা algorithm-এর complexity class বদলে যায় (আজকের experiment-এ ~২০০×)। Production rule: FIFO লাগলে deque — standard library-র এই একটা habit সবচেয়ে সস্তা performance win। একই কারণে threading.Queue ভেতরে deque-ই ব্যবহার করে।

“Monotonic stack একটা advanced competitive-programming trick — production-এ লাগে না।”

Trick না — এটা amortized argument-এর সরাসরি ফল, আর production ভরা: stock span analysis, temperature/histogram processing, compiler register-allocation heuristics, UI layout (largest rectangle in histogram — rendering dirty-region computation), rate-limit windowing। Pattern- টা চেনার skill: “প্রতিটা element-এর জন্য nearest greater/smaller” — এই phrase শুনলেই monotonic stack মনে করুন।

“Circular buffer-এর full আর empty condition একই হয়ে যায় — তাই design flawed।”

head == tail সত্যিই দুই অর্থে পড়া যায় — classic ambiguity। তিনটা standard resolution আছে, সবই production-proven: (১) এক slot সবসময় খালি রাখা (full: (tail+1)%cap == head); (২) count field রাখা (আমাদের build এটা করে); (৩) monotonic timestamp/sequence number রাখা। Flawed design না — documented convention-এর প্রশ্ন; kernel kfifo count-free variant ব্যবহার করে power-of-two mask দিয়ে।

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

1

DFS-এ stack আর BFS-এ queue — কোড প্রায় এক, ফল রাতদিন। ঠিক কোন একটা সিদ্ধান্তের পার্থক্য এটা তৈরি করে?

স্মরণ

Worklist-এর remove প্রান্ত: DFS same-end (stack top) — সদ্য-আবিষ্কৃত path-এর গভীরে আগে ডোবে; BFS opposite-end (queue front) — আবিষ্কারের ক্রমেই process হয়, মানে distance-order। Push logic identical; extraction side-এর choice-ই exploration semantics নির্ধারণ করে। Discipline-এর শক্তির সবচেয়ে ছোট উদাহরণ।

2

Input stream: a b c d — stack-এ push করে মাঝে মাঝে pop করে কোন কোন permutation output হতে পারে? d c b a আর a b c d দুটোই কি possible?

প্রয়োগ

d c b a possible: চারটা push, তারপর চারটা pop। a b c d-ও possible: push a, pop a, push b, pop b… প্রতিটার পরেই pop। যে permutations অসম্ভব: যেখানে বড় element pop হয়ে গেছে কিন্তু তার চেয়ে ছোট কোনো আগের element এখনো stack-এ আটকে উল্টো order চাইছে — classic example: c আগে, তারপর a, তারপর b (a,b উল্টে আটকে থাকে)। Stack-sortable permutations-এর এই সেট Catalan number দিয়ে গোনা যায়।

3

ArrayQueue-তে (sliding head) ১০ লক্ষ enqueue, তারপর ৫ লক্ষ dequeue, তারপর আবার ১০ লক্ষ enqueue — memory-তে কী ঘটছে? কোন খরচ কোথায় লুকিয়ে?

যুক্তি

Dequeue গুলো শুধু head++ — কিন্তু প্রথম ৫ লক্ষ slot মৃত জঞ্জাল হয়ে থেকে যায়, buffer-এর logical size বাড়তেই থাকে। দ্বিতীয় enqueue phase-এ append পিছনেই হয় — জঞ্জাল কখনো reclaim হয় না। Peak memory ≈ সব-mortal-ই সব-enqueues-এর যোগফল, live items মাত্র ১০ লক্ষ। Fix: compaction (periodic slice) — কিন্তু সেটা O(live) pause; অথবা circular buffer — জঞ্জাল-ই recycle। Space leak এখানে discipline violation না, implementation-এর হিসাবের ভুল।

4

Telemetry system: sensor প্রতি ms-এ reading পাঠায়, analyzer মাঝে মাঝে “শেষ ৫ সেকেন্ডের stats” চায়। Fixed RAM budget। কোন structure, capacity কত, কোন conditions?

ডিজাইন

Circular buffer, capacity ৫,০০০ (৫ সেকেন্ড × ১০০০/ms)। Sensor enqueue; buffer full হলে overwrite mode — পুরনোটা নিজে থেকেই বেরিয়ে যায় (head advance), কোনো error না — telemetry-তে stale data-র মূল্য শূন্য। Analyzer query = head থেকে count পর্যন্ত sequential walk — ৫,০০০ slot, cache-friendly, microsecond-scale। Memory: ঠিক ৫,০০০ × record size, lifetime-এ একটাও allocation না — deterministic budget। Producer consumer speed mismatch হলে overwrite policy document করুন — silent data loss-এর চেয়ে explicit policy।

5

Monotonic stack-এ প্রতিটা element ঠিক কতবার push আর কতবার pop হয়? Total operations-এর bound কত, আর এটা কোন প্রমাণ-কৌশলের পুনর্ব্যবহার?

স্মরণ

Push ঠিক একবার (প্রতি element), pop-ও ঠিক একবার (একবার popped হলে আর ফেরে না)। Total ≤ 2n → প্রতি element amortized O(1)। কৌশল: গত-গত লেসনের multipop-stack aggregate method হুবহু — দামি-মনে- হওয়া inner while loop-এর মোট কাজ আসলে মোট push-দ্বারা bounded। Double-counting এড়ানোই পুরো প্রমাণ।

এরপর কী

এরপর কী

এতক্ষণের structures সব identity-বান্ধব: array-র index, node-এর position — ক্রমই সব। পরের লেসনে ক্রমকে পুরোপুরি ছেঁড়া দেব: hash table — key থেকে সরাসরি bucket-এ লাফ, O(1) average lookup-এর জাদু। কিন্তু pigeonhole principle বলে collision অনিবার্য — তাহলে O(1) কীভাবে সম্ভব? Chaining বনাম open addressing, load factor, resizing, clustering — আর শেষে adversarial keys দিয়ে hash DoS attack, যেখানে “average case O(1)”- এর distribution assumption প্রথমবার প্রকাশ্যে ভাঙব। আগের পাঁচ লেসনের সব হাতিয়ার — amortized analysis, locality accounting, adversarial thinking — এক লেসনে মিলব।

পরের লেসন: Hash Table — O(1)-এর জাদু আর তার দাম।

আরও পড়ুন

  • Introduction to Algorithms (3rd edition), Chapter 10.1 — Stacks and Queues — Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein · Array-ভিত্তিক stack/queue implementation-এর canonical treatment
  • The Art of Computer Programming, Volume 1, Section 2.2 — Linear Lists — Donald E. Knuth · Stack/queue/deque-এর classical taxonomy — pushdown list terminology-র উৎস
  • Algorithms (4th edition), Section 1.3 — Bags, Queues, and Stacks — Robert Sedgewick, Kevin Wayne · API design আর generic implementation-এর পাঠ্যপুস্তক মডেল