এই পর্বে যা শিখবেন
- যেকোনো divide and conquer অ্যালগরিদম থেকে
a,b,f(n)বের করা - Recursion tree দেখে বোঝা — কেন উত্তরটি মূল, সব তলা, না পাতা ঠিক করে
- CLRS-এর তিনটি case নির্ভুলভাবে প্রয়োগ, regularity শর্তসহ
- Binary search, merge sort, Strassen, Karatsuba-র complexity এক লাইনে
- কোথায় master theorem খাটে না, আর তখন কোন পদ্ধতি ধরবেন
১ · এক ছাঁচে অনেক অ্যালগরিদম — T(n) = aT(n/b) + f(n)
পর্ব ০৮-এ আপনি recurrence সমাধান করেছেন দুইভাবে — অনুমান করে প্রমাণ (substitution method) আর recursion tree এঁকে তলায় তলায় যোগ। দুটোই শক্তিশালী, কিন্তু প্রতিবার গাছ আঁকা, ধারা যোগ করা, তারপর induction — পরীক্ষার হলে বা ইন্টারভিউর টেবিলে এত সময় থাকে না। এই পর্বে সেই পুরো খাটুনি এক লাইনে নামিয়ে আনব।
একটু খেয়াল করলে দেখবেন, divide and conquer অ্যালগরিদমগুলোর recurrence প্রায় সবসময় একই চেহারার:
a— একটি সমস্যা থেকে কয়টি ছোট সমস্যা (subproblem) তৈরি হয়, অর্থাৎ ফাংশন নিজেকে কয়বার ডাকে।a ≥ 1একটি ধ্রুবক।b— প্রতিটি ছোট সমস্যা মূলের কত ভাগের এক।b > 1একটি ধ্রুবক।f(n)— রিকার্শনের বাইরের কাজ: সমস্যা ভাঙা আর ছোট উত্তরগুলো জোড়া দেওয়ার মোট খরচ।
বোর্ড পরীক্ষার খাতা দেখার কথা ভাবুন। প্রধান পরীক্ষকের কাছে nটি খাতা এলো। তিনি খাতাগুলো সমান b ভাগে ভাগ করে a জন সহকারীকে দিলেন (সাধারণত a = b, তবে সবসময় নয়)। প্রত্যেক সহকারী আবার একই নিয়মে নিজের ভাগ নিচের জনদের ভাগ করে দেন — যতক্ষণ না একজনের হাতে একটিমাত্র খাতা থাকে। প্রতিটি স্তরে বান্ডিল ভাঙা আর নম্বর-তালিকা জোড়া দেওয়ার যে সময়, সেটিই f(n). প্রশ্ন হলো — সব মিলিয়ে মোট খাটুনি কত? Master theorem এর উত্তর দেয় কয়েক সেকেন্ডে।
পরিচিত কয়েকটি অ্যালগরিদম এই ছাঁচে বসিয়ে দেখুন:
| অ্যালগরিদম | কী করে | Recurrence | a, b, f(n) |
|---|---|---|---|
| Binary search (পর্ব ১৮) | মাঝের সাথে তুলনা, এক অর্ধেকে যায় | T(n) = T(n/2) + Θ(1) | 1, 2, 1 |
| Merge sort (পর্ব ২১) | দুই অর্ধেক সাজায়, তারপর merge | T(n) = 2T(n/2) + Θ(n) | 2, 2, n |
| Karatsuba (পর্ব ৪৯) | বড় সংখ্যার গুণ, ৪টির বদলে ৩টি অর্ধেক-গুণ | T(n) = 3T(n/2) + Θ(n) | 3, 2, n |
| Strassen (পর্ব ৪৯) | Matrix গুণ, ৮টির বদলে ৭টি অর্ধেক-গুণ | T(n) = 7T(n/2) + Θ(n²) | 7, 2, n² |
| সাধারণ divide-and-conquer matrix গুণ | ৮টি অর্ধেক-গুণ আর যোগ | T(n) = 8T(n/2) + Θ(n²) | 8, 2, n² |
দুটি ছোট কথা মনে রাখুন। প্রথমত, n/b সবসময় পূর্ণসংখ্যা হয় না — আসলে ⌊n/b⌋ বা ⌈n/b⌉. Master theorem-এর উত্তর এতে বদলায় না, তাই আমরা নিশ্চিন্তে n/b লিখি। দ্বিতীয়ত, ছোট n-এর জন্য T(n) = Θ(1) — এই base case ধরে নেওয়া থাকে, আলাদা করে লেখা হয় না।
কোড থেকে a, b আর f(n) পড়া
পরীক্ষায় প্রায়ই recurrence দেওয়া থাকে না — দেওয়া থাকে একটি ফাংশন। তখন তিনটি প্রশ্ন করুন: নিজেকে কয়বার ডাকছে (a)? প্রতিটি ডাকে ইনপুট কত ছোট হচ্ছে (b)? আর ডাকগুলোর বাইরে কত কাজ হচ্ছে (f)? নিচের ফাংশনটি একটি range-এর সব সংখ্যার কিছু হিসাব করে:
long long solve(int lo, int hi) { // n = hi - lo
if (hi - lo <= 1) return w[lo]; // base case: Θ(1)
int mid = (lo + hi) / 2;
long long x = solve(lo, mid); // T(n/2)
long long y = solve(mid, hi); // T(n/2)
long long s = 0;
for (int i = lo; i < hi; ++i) s += w[i]; // Θ(n) outside the calls
return x + y + s;
}দুটি ডাক, প্রতিটি অর্ধেক আকারে, আর একটি Θ(n) লুপ — তাই T(n) = 2T(n/2) + Θ(n). তিনটি ফাঁদ থেকে সাবধান থাকুন। এক: লুপের ভেতরে রিকার্সিভ ডাক থাকলে a হয় লুপ যতবার ঘোরে ততটি। দুই: একই আর্গুমেন্টে দুবার ডাকলেও (memoization না থাকলে) দুটি ডাকই গুনতে হবে। তিন: লুকানো খরচ। Python-এ a[:mid] লিখলে তালিকার একটি নতুন কপি তৈরি হয়, যার খরচ Θ(n). তাই slicing দিয়ে লেখা “binary search” আসলে T(n) = T(n/2) + Θ(n) — case 3, Θ(n)! ইনডেক্স (lo, hi) পাঠালে সেটি আবার Θ(log n) হয়ে যায়। একটি অদৃশ্য কপি পুরো complexity বদলে দিতে পারে।
২ · Recursion tree থেকে অন্তর্দৃষ্টি — কোন তলায় খরচ বেশি
Master theorem কোনো জাদুর সূত্র নয়। এটি আসলে recursion tree-র যোগফলকে আগে থেকেই একবার সাধারণভাবে করে রাখা। তাই সূত্র মুখস্থ করার আগে গাছটি মাথায় বসিয়ে নিন। নিচের ডায়াগ্রামে প্রথমে সাধারণ চিত্র, তারপর n = 16 নিয়ে তিনটি আলাদা recurrence-এর গাছ — প্রতিটি তলার মোট খরচ খেয়াল করুন।
level 0 : 1 node size n cost f(n)
level 1 : a nodes size n/b cost a·f(n/b)
level 2 : a^2 nodes size n/b^2 cost a^2·f(n/b^2)
...
level j : a^j nodes size n/b^j cost a^j·f(n/b^j)
...
leaves : a^(log_b n) nodes, each Θ(1)
size at level j = n / b^j = 1 -> j = log_b n
leaves = a^(log_b n) = n^(log_b a)
example a = 4, b = 2: 4^(log_2 n) = n^2
example a = 3, b = 2: 3^(log_2 n) = n^1.585
example a = 1, b = 2: 1^(log_2 n) = 1
level 0: [16] = 16
level 1: [8] = 8
level 2: [4] = 4
level 3: [2] = 2
leaves : [1] = 1
total = 31 (= 2n - 1)
level 0: [16] = 16
level 1: [8][8] = 16
level 2: [4] x 4 = 16
level 3: [2] x 8 = 16
leaves : [1] x 16 = 16
total = 80 (= n(lg n + 1))
level 0: [16] = 16
level 1: [8] x 4 = 32
level 2: [4] x 16 = 64
level 3: [2] x 64 = 128
leaves : [1] x 256 = 256
total = 496 (= 2n^2 - n)
level costs shrink (T(n/2)+n) -> root wins -> Θ(f(n)) level costs equal (2T(n/2)+n) -> every level -> Θ(f(n)·log n) level costs grow (4T(n/2)+n) -> leaves win -> Θ(n^(log_b a))
- সাধারণ চিত্র। তলা
j-তে আছে ajটি নোড, প্রতিটির আকার n/bj, প্রতিটির বাইরের কাজ f(n/bj). তাই পুরো তলার খরচ aj·f(n/bj). মোট খরচ = সব তলার যোগফল + পাতাগুলোর খরচ। - গভীরতা logb n, পাতা nlogb a. আকার যখন ১-এ নামে, তখন n/bj = 1, অর্থাৎ j = logb n. পাতার সংখ্যা alogb n — আর পর্ব ০৬-এর log-এর নিয়মে এটি nlogb a. যেমন a = 4, b = 2 হলে পাতা n²টি। এই nlogb a-ই পুরো পর্বের নায়ক।
- T(n/2) + n — খরচ প্রতি তলায় অর্ধেক। ১৬, ৮, ৪, ২, ১ — যোগফল ৩১, যা 2n − 1. প্রথম তলাতেই প্রায় অর্ধেক খরচ। কমতে থাকা জ্যামিতিক ধারার যোগফল তার প্রথম পদের ধ্রুবক গুণ — তাই উত্তর Θ(n) = Θ(f(n)). মূল জেতে।
- 2T(n/2) + n — প্রতিটি তলা সমান। প্রতিটি তলায় ঠিক ১৬, আর তলা আছে lg 16 + 1 = ৫টি। মোট ৮০ = n(lg n + 1). অর্থাৎ এক তলার খরচ × তলার সংখ্যা = Θ(n log n). কেউ একা জেতে না — log n বাড়তি গুণক আসে।
- 4T(n/2) + n — খরচ প্রতি তলায় দ্বিগুণ। ১৬, ৩২, ৬৪, ১২৮, আর শেষে ২৫৬টি পাতা। মোট ৪৯৬ = 2n² − n. শেষ তলাই প্রায় অর্ধেক — বাড়তে থাকা জ্যামিতিক ধারার যোগফল তার শেষ পদের ধ্রুবক গুণ। উত্তর Θ(n²) = Θ(nlog2 4). পাতা জেতে।
- তিনটি ভাগ্যই master theorem-এর তিন case. তলার খরচ কমে, সমান থাকে, না বাড়ে — সেটি ঠিক হয়
f(n)আর পাতার সংখ্যা nlogb a-এর তুলনায়। তুলনাটুকু করতে পারলেই আর গাছ আঁকতে হয় না।
এখান থেকেই আসে watershed (জলবিভাজিকা) কথাটি। পাহাড়ের যে চূড়া ঠিক করে দেয় বৃষ্টির পানি কোন দিকে গড়াবে, তাকে বলে watershed. nlogb a ঠিক তেমনি সীমারেখা: f(n) এর নিচে থাকলে খরচ পাতার দিকে গড়ায়, ওপরে থাকলে মূলের দিকে, আর সমান হলে সব তলায় সমানভাবে ছড়িয়ে থাকে। CLRS বইয়ের চতুর্থ সংস্করণ এই nlogb a-কে বলে watershed function.
Master theorem একটিমাত্র প্রশ্ন করে: f(n) কি nlogb a-এর চেয়ে ছোট, সমান, না বড়? উত্তর তিনটি সম্ভাব্য গাছের একটিকে বেছে নেয়, আর গাছের যোগফল আগে থেকেই জানা।
৩ · তিনটি case — CLRS-এর ভাষায় নির্ভুলভাবে
অন্তর্দৃষ্টি পেলেন, এবার নিয়মটি নির্ভুলভাবে লিখি — কারণ পরীক্ষায় শর্তগুলো ঠিকঠাক না লিখলে নম্বর কাটে, আর শর্ত না মিললে ভুল উত্তরও আসে। নিচের রূপটি Cormen, Leiserson, Rivest ও Stein-এর Introduction to Algorithms (সংক্ষেপে CLRS) বইয়ের।
ধরুন a ≥ 1 আর b > 1 ধ্রুবক, f(n) এমন ফাংশন যা বড় n-এ ধনাত্মক, আর T(n) = aT(n/b) + f(n). তাহলে:
- Case 1: যদি কোনো ধ্রুবক ε > 0-এর জন্য f(n) = O(nlogb a − ε) হয়, তাহলে T(n) = Θ(nlogb a).
- Case 2: যদি কোনো ধ্রুবক k ≥ 0-এর জন্য f(n) = Θ(nlogb a logk n) হয়, তাহলে T(n) = Θ(nlogb a logk+1 n).
- Case 3: যদি কোনো ধ্রুবক ε > 0-এর জন্য f(n) = Ω(nlogb a + ε) হয়, এবং কোনো ধ্রুবক c < 1 ও যথেষ্ট বড় সব n-এর জন্য a·f(n/b) ≤ c·f(n) হয়, তাহলে T(n) = Θ(f(n)).
প্রতিটি শর্তের পেছনের কারণ বুঝে নিন — তাহলে মুখস্থ করতে হবে না।
Case 1 — “polynomially ছোট” মানে কী
শুধু “f ছোট” বললে চলবে না; f-কে nlogb a-এর চেয়ে অন্তত nε গুণক ছোট হতে হবে — যেখানে ε যত ছোটই হোক, একটি ধনাত্মক ধ্রুবক। কেন? কারণ তখনই তলার খরচগুলো জ্যামিতিক হারে বাড়ে (প্রতি তলায় bε গুণ), আর বাড়তে থাকা জ্যামিতিক ধারায় শেষ পদ — পাতাগুলো — জেতে। Strassen-এ f = n² আর watershed n2.807: এখানে ε ≈ ০.৮, শর্ত পরিষ্কারভাবে মেলে।
Case 2 — সমান, log-এর গুণক পর্যন্ত
সবচেয়ে সাধারণ রূপে (k = 0): f(n) = Θ(nlogb a) হলে প্রতিটি তলার খরচ সমান, তলা logb n টি, তাই উত্তর Θ(nlogb a log n). বর্ধিত রূপে f-এর সাথে logk n গুণক থাকলেও চলে — তখন উত্তরে log-এর ঘাত এক বাড়ে। যেমন 2T(n/2) + n log n-এ k = 1, উত্তর Θ(n log² n). CLRS-এর তৃতীয় সংস্করণ পর্যন্ত মূল উপপাদ্যে শুধু k = 0 ছিল (বর্ধিত রূপটি অনুশীলনী হিসেবে ছিল); চতুর্থ সংস্করণ k ≥ 0 সরাসরি উপপাদ্যে এনেছে। তাই কোনো বই বা শিক্ষক “2T(n/2) + n log n-এ master theorem খাটে না” বললে বুঝবেন তিনি সংকীর্ণ রূপটির কথা বলছেন।
Case 3 — বড়, আর regularity শর্ত
f polynomially বড় হলে তলার খরচ সাধারণত কমতে থাকে, আর মূল জেতে। কিন্তু “সাধারণত” যথেষ্ট নয় — খরচ যেন নিশ্চিতভাবে জ্যামিতিক হারে কমে, সেটাই regularity শর্ত: a·f(n/b) ≤ c·f(n), c < 1. বাঁ দিক হলো তলা ১-এর মোট খরচ, ডান দিক মূলের খরচের c গুণ। তাহলে তলা j-এর খরচ ≤ cj·f(n), আর সব তলার যোগফল ≤ f(n)/(1 − c) = O(f(n)). অন্যদিকে মূল একাই f(n), তাই T(n) = Θ(f(n)).
ভালো খবর: f(n) = nd logk n ধরনের সাধারণ ফাংশনে, d > logb a হলে regularity নিজে থেকেই মেলে, কারণ a·f(n/b) ≈ (a/bd)·f(n) আর a/bd < 1. শর্তটি ভাঙে কেবল অদ্ভুতভাবে ওঠানামা করা f-এ (যেমন sine-cosine মেশানো ফাংশন)। তবু পরীক্ষার খাতায় এক লাইনে c-এর মানটি দেখিয়ে দেওয়াই নিয়ম।
কেন সত্য — প্রমাণের রূপরেখা
অনেক পরীক্ষায় শুধু প্রয়োগ নয়, প্রমাণের মূল ভাবটিও চাওয়া হয়। সরলতার জন্য ধরি n হলো b-এর একটি ঘাত। অংশ ২-এর গাছ থেকে সরাসরি লেখা যায়:
প্রথম পদটি পাতাগুলোর খরচ, দ্বিতীয়টি ভেতরের সব তলার যোগফল (ধরুন g(n)). এখন তিন case আলাদা করে দেখি।
- Case 1: f(n/bj) ≤ c(n/bj)logb a − ε বসালে প্রতিটি পদ হয় c·nlogb a − ε·(bε)j — অনুপাত bε > 1 এর একটি বাড়তে থাকা জ্যামিতিক ধারা। তার যোগফল শেষ পদের ধ্রুবক গুণ, অর্থাৎ O(nlogb a − ε·nε) = O(nlogb a). পাতার পদের সাথে মিলিয়ে T(n) = Θ(nlogb a).
- Case 2 (k = 0): প্রতিটি পদ aj·(n/bj)logb a = nlogb a, কারণ aj আর bj·logb a = aj কেটে যায়। logb n টি সমান পদ, তাই g(n) = Θ(nlogb a log n). k > 0 হলে পদগুলো (log n − j)k-এর মতো, আর Σ (log n − j)k = Θ(logk+1 n) — তাই বাড়তি একটি log.
- Case 3: regularity বারবার প্রয়োগ করলে ajf(n/bj) ≤ cjf(n), তাই g(n) ≤ f(n)·(1 + c + c² + …) = f(n)/(1 − c). আর g(n) ≥ f(n) (প্রথম পদ)। পাতার পদ nlogb a তো f-এর চেয়ে polynomially ছোট, তাই T(n) = Θ(f(n)).
n যখন b-এর ঘাত নয়, তখন ⌊n/b⌋ বা ⌈n/b⌉ নিয়ে একই যুক্তি একটু বেশি হিসাবসহ চলে — CLRS-এ সেই অংশটি আলাদা করে দেখানো আছে, আর ফল বদলায় না।
পুরো সিদ্ধান্ত-প্রক্রিয়াটি f(n) = Θ(nd logk n) ধরনের ফাংশনের জন্য ছোট একটি pseudocode-এ লেখা যায়:
MASTER(a, b, d, k) // T(n) = a T(n/b) + Θ(n^d log^k n), a ≥ 1, b > 1
e = log_b(a) // watershed exponent
if d < e: return Θ(n^e) // case 1
if d == e:
if k ≥ 0: return Θ(n^e · log^(k+1) n) // case 2
else: return "gap: master theorem does not apply"
if d > e: return Θ(n^d · log^k n) // case 3 (regularity holds)সবচেয়ে সাধারণ ভুল: “f(n) = n log n আর watershed n, f বড়, তাই case 3 → Θ(n log n).” ভুল! n log n, n-এর চেয়ে বড় ঠিকই, কিন্তু polynomially বড় নয় — কোনো ε > 0-এর জন্য n log n = Ω(n1+ε) সত্য নয়, কারণ log n যেকোনো nε-এর চেয়ে ধীরে বাড়ে। এটি case 2 (k = 1), উত্তর Θ(n log² n).
৪ · ধাপে ধাপে সিদ্ধান্ত — আটটি recurrence
নিয়ম জানা আর প্রয়োগে দক্ষ হওয়া আলাদা জিনিস। প্রতিটি recurrence-এ একই চার ধাপ: (১) a, b, f চিনুন, (২) watershed nlogb a হিসাব করুন, (৩) f-এর সাথে তুলনা করুন, (৪) case-এর শর্ত মিলিয়ে উত্তর লিখুন। নিচে আটটি recurrence-এ এই চার ধাপ চালানো হলো — শেষটি ইচ্ছা করে একটি ফাঁদ।
a = 1, b = 2, f(n) = 1
watershed : n^(log_2 1) = n^0 = 1
compare : f(n) = Θ(1) = Θ(n^0 · log^0 n) -> equal, k = 0
case 2 : T(n) = Θ(log n)
a = 2, b = 2, f(n) = n
watershed : n^(log_2 2) = n^1
compare : f(n) = Θ(n) -> equal, k = 0
case 2 : T(n) = Θ(n log n)
a = 7, b = 2, f(n) = n^2
watershed : n^(log_2 7) ≈ n^2.807
compare : n^2 = O(n^(2.807 - 0.8)) -> smaller, ε ≈ 0.8
case 1 : T(n) = Θ(n^(log_2 7)) ≈ Θ(n^2.81)
a = 3, b = 2, f(n) = n
watershed : n^(log_2 3) ≈ n^1.585
compare : n = O(n^(1.585 - 0.5)) -> smaller, ε ≈ 0.5
case 1 : T(n) = Θ(n^(log_2 3)) ≈ Θ(n^1.585)
a = 2, b = 2, f(n) = n log n
watershed : n^1
compare : n log n = Θ(n · log^1 n) -> equal up to log, k = 1
(NOT polynomially larger: no ε works)
case 2 : T(n) = Θ(n log^2 n)
a = 4, b = 2, f(n) = n^2
watershed : n^(log_2 4) = n^2
compare : f(n) = Θ(n^2) -> equal, k = 0
case 2 : T(n) = Θ(n^2 log n)
a = 1, b = 2, f(n) = n
watershed : n^0 = 1
compare : n = Ω(n^(0 + 1)) -> larger, ε = 1
regularity: 1 · f(n/2) = n/2 <= (1/2) · n, c = 1/2 < 1 OK
case 3 : T(n) = Θ(n)
a = 2, b = 2, f(n) = n / log n
watershed : n^1
case 1? : n / log n = O(n^(1 - ε)) ? NO (n^ε / log n grows)
case 2? : n / log n = n · log^(-1) n, k = -1 < 0 NO
case 3? : f is smaller, not larger NO
no case : use recursion tree -> Θ(n log log n)
- Binary search. একবার তুলনা করে অর্ধেকে যায়, তাই a = 1, b = 2, f = 1. Watershed n0 = 1, আর f-ও ধ্রুবক — সমান, k = 0. Case 2 বলে Θ(1 · log n) = Θ(log n). গাছটি আসলে একটি সরলরেখা: log n তলা, প্রতিটিতে ১ ইউনিট কাজ।
- Merge sort. দুই অর্ধেক, merge করতে Θ(n). Watershed n1, f = n — সমান। Case 2: Θ(n log n). ধাপ ৪-এর গাছের প্রতিটি তলা ১৬ — মনে আছে?
- Strassen. ৭টি অর্ধেক-আকারের matrix গুণ, আর matrix যোগ-বিয়োগে Θ(n²). log2 7 ≈ ২.৮০৭, তাই watershed n2.807. f = n² এর চেয়ে n0.8-এর মতো গুণক ছোট — case 1. উত্তর Θ(nlog2 7) ≈ Θ(n2.81), যা সাধারণ পদ্ধতির Θ(n³)-এর চেয়ে কম।
- Karatsuba. ৩টি অর্ধেক-আকারের গুণ আর Θ(n) যোগ-সরানো। Watershed nlog2 3 ≈ n1.585; f = n polynomially ছোট (ε ≈ ০.৫৮)। Case 1: Θ(n1.585) — স্কুলের গুণের Θ(n²)-এর চেয়ে দ্রুত।
- 2T(n/2) + n log n. Watershed n, আর f = n log n — n-এর চেয়ে বড়, কিন্তু শুধু log-এর গুণকে। Polynomially বড় নয়, তাই case 3 নয়। বর্ধিত case 2-তে k = 1: Θ(n log² n). প্রতিটি তলার খরচ প্রায় সমান (n log(n/2j)), আর log n তলা।
- 4T(n/2) + n². Watershed nlog2 4 = n², f = n² — সমান। Case 2: Θ(n² log n). খেয়াল করুন, ধাপ ৫-এর 4T(n/2) + n-এ f = n ছিল, তাই সেখানে উত্তর Θ(n²) (case 1). শুধু f বদলালেই case বদলায়।
- T(n/2) + n. Watershed ১, f = n — polynomially বড় (ε = 1). Regularity: 1·(n/2) ≤ ½·n, c = ½ < 1 — মিলল। Case 3: Θ(n). Quickselect-এর ভাগ্যবান ক্ষেত্রেও (পর্ব ২২) প্রায় এই recurrence-ই আসে।
- ফাঁদ: 2T(n/2) + n/log n. Watershed n. f কি polynomially ছোট? না — n/log n আর n1−ε-এর অনুপাত nε/log n, যা অসীমে যায়, তাই f কোনো n1−ε-এর নিচে থাকে না। Case 2-তে k = −1, কিন্তু k ≥ 0 লাগে। Case 3 তো নয়ই। কোনো case খাটে না — এই ফাঁকের নাম “gap”. অংশ ৬-এ গাছ এঁকে উত্তর বের করব: Θ(n log log n).
| Recurrence | Watershed | Case | উত্তর |
|---|---|---|---|
| T(n/2) + 1 | 1 | 2 (k = 0) | Θ(log n) |
| 2T(n/2) + n | n | 2 (k = 0) | Θ(n log n) |
| 7T(n/2) + n² | n2.807 | 1 | Θ(nlog2 7) |
| 3T(n/2) + n | n1.585 | 1 | Θ(nlog2 3) |
| 2T(n/2) + n log n | n | 2 (k = 1) | Θ(n log² n) |
| 4T(n/2) + n² | n² | 2 (k = 0) | Θ(n² log n) |
| T(n/2) + n | 1 | 3 | Θ(n) |
| 2T(n/2) + n/log n | n | কোনোটিই নয় | Θ(n log log n) — গাছ দিয়ে |
পরীক্ষার হলের শর্টকাট: a বনাম bd
f(n) = Θ(nd) হলে log হিসাব না করেই case বলা যায়। d আর logb a-র তুলনা আসলে bd আর a-র তুলনা, কারণ b > 1 হলে bx বাড়ন্ত ফাংশন। তাই:
- a > bd → case 1, Θ(nlogb a). যেমন 7T(n/2) + n²: 7 > 2² = 4.
- a = bd → case 2, Θ(nd log n). যেমন 4T(n/2) + n²: 4 = 2².
- a < bd → case 3, Θ(nd). যেমন 2T(n/2) + n²: 2 < 4.
ব্যাখ্যাটাও সুন্দর: a/bd হলো পাশাপাশি দুই তলার খরচের অনুপাত। প্রতি তলায় নোড a গুণ বাড়ে, আর প্রতিটি নোডের কাজ bd গুণ কমে। অনুপাত ১-এর বেশি হলে খরচ নিচে গড়ায়, কম হলে ওপরে জমে, সমান হলে সব তলা এক। অংশ ২-এর গাছগুলোতে এই অনুপাত ছিল ½, ১ আর ২।
৫ · আরও উদাহরণ — b = 2 ছাড়াও, আর একটু চালাকি
সব recurrence-এ b = 2 থাকে না, আর f সবসময় পরিচ্ছন্ন nd হয় না। কয়েকটি ক্লাসিক উদাহরণ হাতে করে দেখি — এগুলো বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় বারবার আসে।
উদাহরণ ১: T(n) = 9T(n/3) + n
a = 9, b = 3, watershed nlog3 9 = n². f = n = O(n2−1), ε = 1. Case 1: T(n) = Θ(n²). গাছে প্রতি তলায় খরচ ৩ গুণ বাড়ে (n, 3n, 9n, …), আর শেষে n²টি পাতা।
উদাহরণ ২: T(n) = T(2n/3) + 1
এখানে b একটি ভগ্নাংশ — n/b = 2n/3 মানে b = 3/2 (যা ১-এর বেশি, তাই চলবে)। a = 1, watershed nlog3/2 1 = n0 = 1. f = 1 — সমান, k = 0. Case 2: T(n) = Θ(log n). প্রতিবার এক-তৃতীয়াংশ ফেলে দিলেও log-ই থাকে — শুধু log-এর ভিত্তি ৩/২, যা Θ-তে কোনো পার্থক্য করে না।
উদাহরণ ৩: T(n) = 3T(n/4) + n log n
a = 3, b = 4, watershed nlog4 3 ≈ n0.793. f = n log n = Ω(n0.793 + 0.2) — polynomially বড়, ε ≈ ০.২. Regularity: 3·(n/4)·log(n/4) ≤ (3/4)·n log n, কারণ log(n/4) < log n. তাই c = 3/4 < 1. Case 3: T(n) = Θ(n log n).
উদাহরণ ৪: T(n) = 2T(n/4) + √n
a = 2, b = 4, watershed nlog4 2 = n1/2 = √n. f = √n — সমান। Case 2: T(n) = Θ(√n log n). ভগ্নাংশ ঘাত দেখে ঘাবড়ানোর কিছু নেই — নিয়ম একই।
উদাহরণ ৫: T(n) = 8T(n/2) + n²
Matrix গুণের সরল divide and conquer: ৮টি অর্ধেক-গুণ। Watershed nlog2 8 = n³, f = n² — case 1, Θ(n³). সাধারণ তিন-লুপের গুণের চেয়ে কোনো লাভ নেই! Strassen ৮-কে ৭ বানিয়ে watershed-টাই নামিয়ে দেন — এখানেই master theorem একটি নকশা-শিক্ষা দেয়: case 1-এ উত্তর ঠিক করে a আর b, f নয়। তাই উন্নতি চাইলে subproblem-এর সংখ্যা কমাতে হবে, জোড়া দেওয়ার কাজ কমিয়ে লাভ নেই।
উদাহরণ ৬: T(n) = 4T(n/2) + n log n
Watershed n², আর f = n log n. log n থাকায় ঘাবড়াবেন না — n log n = O(n1.5), অর্থাৎ n²-এর চেয়ে n0.5 গুণক ছোট (log n যেকোনো nε-এর চেয়ে ধীরে বাড়ে)। তাই এটি পরিষ্কার case 1: T(n) = Θ(n²). মনে রাখুন, log-এর গুণক কেবল তখনই গুরুত্বপূর্ণ, যখন f আর watershed-এর ঘাত হুবহু সমান — তখন সেটি case 2-এর k ঠিক করে। ঘাত আলাদা হলে log-এর গুণক কোনো case বদলাতে পারে না।
Case 1-এ লাভ কোথায়
a কমান (Karatsuba ৪→৩, Strassen ৮→৭)। f কমিয়ে কিছু হয় না, কারণ খরচ পাতায়।
পাতা-প্রধানCase 2-এ লাভ কোথায়
দুটোই সমান গুরুত্বপূর্ণ। f-এর একটি log কমাতে পারলে উত্তর থেকেও একটি log কমে।
ভারসাম্যCase 3-এ লাভ কোথায়
শুধু f কমান — ভাগ-জোড়ার কাজ দ্রুত করা। রিকার্শনের অংশ প্রায় বিনা খরচে।
মূল-প্রধানএই সাধারণ পদ্ধতির উৎস ১৯৮০ সালের একটি প্রবন্ধ — Jon Bentley, Dorothea Haken ও James Saxe-এর “A general method for solving divide-and-conquer recurrences”. পরে ১৯৯০ সালে প্রকাশিত CLRS বই (তখন Cormen, Leiserson ও Rivest) একে “master method” আর “master theorem” নামে জনপ্রিয় করে, আর এখন বিশ্বের প্রায় সব অ্যালগরিদম কোর্সে এটি পড়ানো হয়।
৬ · কখন খাটে না — আর তখন কী করবেন
Master theorem একটি ধারালো ছুরি, কিন্তু সব কিছু কাটে না। প্রথমে দেখি সেই ফাঁদটি — 2T(n/2) + n/log n — গাছ এঁকে নিজেই উত্তর বের করি। ধরি n = 216 = 65,536, আর গাছ থামে আকার ২-এ।
nodes at level j : 2^j
size of each : n / 2^j
cost of each : (n/2^j) / lg(n/2^j) = (n/2^j) / (16 - j)
level total : 2^j · (n/2^j) / (16 - j) = n / (16 - j)
level 0 : 65536 / 16 = 4096
level 1 : 65536 / 15 ≈ 4369
level 2 : 65536 / 14 ≈ 4681
level 3 : 65536 / 13 ≈ 5041
... costs grow, but slowly
level 12 : 65536 / 4 = 16384
level 13 : 65536 / 3 ≈ 21845
level 14 : 65536 / 2 = 32768
level 15 : 65536 / 1 = 65536
total = n · (1/16 + 1/15 + ... + 1/2 + 1/1)
= n · H_16
≈ 65536 · 3.3807
≈ 221559
n = 65536
n · H_16 ≈ 221559 <- our recurrence
n · lg n = 1048576
H_m ≈ ln m, m = lg n -> T(n) = Θ(n log log n)
- এক তলার খরচ। তলা j-তে 2jটি নোড, প্রতিটির আকার n/2j. প্রতিটির কাজ (n/2j)/lg(n/2j), আর lg(n/2j) = 16 − j. গুণ করলে 2j কেটে যায় — তলার মোট খরচ n/(16 − j). তলাগুলো সমানও নয়, জ্যামিতিক হারেও বদলায় না।
- শুরুর তলাগুলো। ৪০৯৬, প্রায় ৪৩৬৯, ৪৬৮১, ৫০৪১ — খরচ বাড়ছে, কিন্তু প্রতি তলায় মাত্র কয়েক শতাংশ। Case 1-এর মতো দ্বিগুণ-তিনগুণ হারে নয়, আবার case 2-এর মতো একদম সমানও নয়।
- শেষের তলাগুলো। ১৬৩৮৪, প্রায় ২১৮৪৫, ৩২৭৬৮, ৬৫৫৩৬ — শেষ তলাটি একাই n. তবু সবগুলো মিলেও “পাতা জেতে” বলা যায় না, কারণ ওপরের অনেক তলার যোগফলও উপেক্ষা করার মতো নয়।
- যোগফল একটি harmonic ধারা। n দিয়ে বের করে নিলে বাকি থাকে ১/১৬ + ১/১৫ + … + ১/১ = H16 ≈ ৩.৩৮০৭. মোট প্রায় ২,২১,৫৫৯. পর্ব ০৬-এ দেখেছেন, Hm ≈ ln m.
- উত্তর। এখানে m = lg n, তাই T(n) = Θ(n · Hlg n) = Θ(n log log n). সংখ্যায়ও তাই: ২,২১,৫৫৯ ঠিক n (৬৫,৫৩৬) আর n lg n (১০,৪৮,৫৭৬)-এর মাঝখানে। Master theorem-এর তিন case যে তিন রকম ধারা ধরে, এটি তার কোনোটি নয় — তাই উপপাদ্য চুপ।
Gap ছাড়াও আরও কয়েকটি পরিস্থিতিতে master theorem সরাসরি প্রয়োগ করা যায় না। পরিস্থিতি দেখেই চিনে ফেলুন:
| পরিস্থিতি | উদাহরণ | কেন খাটে না | বিকল্প পথ আর উত্তর |
|---|---|---|---|
| বিয়োগ করে ছোট করা | T(n) = T(n−1) + n | আকার ভাগ হয় না, বিয়োগ হয় — b নেই | যোগফল খুলে: n + (n−1) + … + 1 = Θ(n²) |
| বিয়োগ, একাধিক ডাক | T(n) = 2T(n−1) + 1 (Hanoi টাওয়ার) | একই কারণ | গাছে প্রতি তলায় দ্বিগুণ, n তলা: Θ(2n) |
| অ-ধ্রুবক a | T(n) = 2nT(n/2) + nn | a অবশ্যই ধ্রুবক হতে হবে | সাধারণত গাছ বা substitution |
| a < 1 | T(n) = ½T(n/2) + n | “অর্ধেকটি subproblem” অর্থহীন | শর্তেই a ≥ 1 |
| অসমান ভাগ | T(n) = T(n/3) + T(2n/3) + n | দুই ডাকের আকার আলাদা, এক b নেই | Recursion tree: প্রতি তলা ≤ n, গভীরতা log3/2 n → Θ(n log n); অথবা Akra–Bazzi |
| Gap | T(n) = 2T(n/2) + n/log n | কোনো case-এর শর্ত মেলে না | Recursion tree: Θ(n log log n) |
| অদ্ভুত আকারের ভাগ | T(n) = 2T(√n) + log n | n/b নয়, √n | চলক বদল: m = lg n, S(m) = 2S(m/2) + m → Θ(m log m) = Θ(log n · log log n) |
Regularity শর্ত ভাঙার উদাহরণ দৈনন্দিন অ্যালগরিদমে প্রায় পাওয়াই যায় না, কিন্তু তাত্ত্বিকভাবে আছে: f(n) যদি ওঠানামা করে — কোনো n-এ বিশাল, ঠিক তার অর্ধেকে প্রায় শূন্যের কাছে — তাহলে a·f(n/b) কোনো কোনো n-এ f(n)-কে ছাড়িয়ে যেতে পারে, যদিও গড়ে f “বড়”। তখন তলার খরচ নিয়মিত হারে কমে না, আর case 3-এর যুক্তি দাঁড়ায় না। এজন্যই শর্তটি উপপাদ্যে আলাদা করে লেখা থাকে, যদিও সাধারণ বহুপদী f-এ এটি আপনা-আপনি মেলে।
বিয়োগের recurrence-গুলো দেখতে নিরীহ, কিন্তু ফল একেবারে আলাদা। T(n) = 2T(n/2) + 1 মাত্র Θ(n), অথচ T(n) = 2T(n−1) + 1 হলো Θ(2n) — Hanoi টাওয়ারের চাল গোনার recurrence (পর্ব ০৭ ও ০৮-এ এটি দেখেছেন)। পার্থক্য গভীরতায়: ভাগ করলে গাছ log n তলা নামে, বিয়োগ করলে n তলা। দুই ক্ষেত্রেই প্রতি তলায় নোড দ্বিগুণ হয়, কিন্তু log n তলায় সেটা মাত্র n টি পাতা, আর n তলায় 2nটি। তাই কোডে f(n-1) দুবার ডাকা দেখলেই সতর্ক হোন — সাধারণত সেটি exponential, আর সমাধান প্রায়ই memoization (পর্ব ৫২)।
শেষ সারির কৌশলটির নাম চলক বদল (change of variables): n = 2m ধরলে √n = 2m/2, আর S(m) = T(2m) লিখলে recurrence হয়ে যায় S(m) = 2S(m/2) + m — যা merge sort-এর চেনা রূপ। সমাধান করে m-এর জায়গায় lg n ফেরত বসালেই কাজ শেষ। অচেনা recurrence-কে চেনা ছাঁচে টেনে আনার এই চিন্তাটা master theorem-এর চেয়েও বেশি কাজের।
আরও গভীরে: অসমান ভাগের জন্য সাধারণ উত্তর দেয় Akra–Bazzi পদ্ধতি (Mohamad Akra ও Louay Bazzi, ১৯৯৮)। T(n) = Σ aiT(bin) + g(n) হলে প্রথমে এমন p বের করুন যাতে Σ aibip = 1; তখন T(n) = Θ(np(1 + ∫1n g(u)/up+1 du)). T(n/3) + T(2n/3) + n-এ p = 1 (কারণ ১/৩ + ২/৩ = ১), আর integral থেকে আসে ln n — উত্তর Θ(n log n). একই সূত্রে 2T(n/2) + n/log n-এ p = 1, integral ∫ du/(u ln u) = ln ln n — gap-এর Θ(n log log n) আবার মিলল। Master theorem আসলে এর একটি বিশেষ রূপ।
৭ · নিজে হিসাব করুন — Master theorem calculator
নিচের ছোট টুলটিতে a, b আর f(n) = nd logk n-এর d ও k বসান — সাথে সাথে case আর উত্তর দেখাবে। “চালান” চাপলে ওপরে ঘরগুলো আসবে, আর নিচের আউটপুটে অংশ ৪-এর আটটি recurrence-এর ফল ছাপা হবে। ঘরের মান বদলে নিজের recurrence পরীক্ষা করুন — যেমন Strassen-এর জন্য a = 7, b = 2, d = 2, k = 0.
// T(n) = a*T(n/b) + f(n), f(n) = n^d * (log n)^k
function master(a, b, d, k) {
if (!(a >= 1 && b > 1)) return 'not applicable: need a >= 1 and b > 1';
const e = Math.log(a) / Math.log(b); // watershed exponent log_b a
const pw = (x) => (x === 0 ? '' : x === 1 ? 'n' : `n^${+x.toFixed(3)}`);
const lg = (p) => (p === 0 ? '' : p === 1 ? 'log n' : `log^${p} n`);
const th = (...t) => `Theta(${t.filter(Boolean).join(' ') || '1'})`;
const E = Math.abs(e - Math.round(e)) < 1e-9 ? Math.round(e) : e;
if (Math.abs(d - e) < 1e-9) { // f equals the watershed (up to logs)
if (k >= 0) return `case 2 -> ${th(pw(E), lg(k + 1))}`;
return `gap, k < 0 (use recursion tree) -> ${th(pw(E), k === -1 ? 'log log n' : '')}`;
}
if (d < e) return `case 1, smaller by n^${+(e - d).toFixed(3)} -> ${th(pw(E))}`;
return `case 3, a/b^d = ${+(a / b ** d).toFixed(3)} < 1 -> ${th(pw(d), lg(k))}`;
}
const ids = ['a', 'b', 'd', 'k'];
function show() {
const [a, b, d, k] = ids.map((x) => Number(document.getElementById(x).value));
document.getElementById('res').textContent = master(a, b, d, k);
}
ids.forEach((x) => { document.getElementById(x).oninput = show; });
show();
for (const [a, b, d, k] of [[1, 2, 0, 0], [2, 2, 1, 0], [7, 2, 2, 0], [3, 2, 1, 0],
[2, 2, 1, 1], [4, 2, 2, 0], [1, 2, 1, 0], [2, 2, 1, -1]])
console.log(`a=${a} b=${b} d=${d} k=${k}: ${master(a, b, d, k)}`);আউটপুটের আটটি লাইন অংশ ৪-এর সারণির সাথে হুবহু মেলে: a=1 b=2 d=0 k=0: case 2 -> Theta(log n), a=2 b=2 d=1 k=0: case 2 -> Theta(n log n), a=7 b=2 d=2 k=0: case 1, smaller by n^0.807 -> Theta(n^2.807), a=3 b=2 d=1 k=0: case 1, smaller by n^0.585 -> Theta(n^1.585), a=2 b=2 d=1 k=1: case 2 -> Theta(n log^2 n), a=4 b=2 d=2 k=0: case 2 -> Theta(n^2 log n), a=1 b=2 d=1 k=0: case 3, a/b^d = 0.5 < 1 -> Theta(n) আর শেষে a=2 b=2 d=1 k=-1: gap, k < 0 (use recursion tree) -> Theta(n log log n). কোডের হৃদয় তিনটি তুলনা: e = log(a)/log(b) হলো watershed-এর ঘাত, তারপর d-কে e-এর সাথে মেলানো। দশমিক সংখ্যা কখনো হুবহু সমান হয় না (log 4/log 2 হয়তো 1.9999999…), তাই “সমান” মানে পার্থক্য 1e-9-এর কম। Case 3-এ regularity আলাদা পরীক্ষা করা হয়নি, কারণ nd logk n ধরনের f-এ d > e হলে a/bd < 1 নিজে থেকেই সত্য — আউটপুটে সেই অনুপাতটিই দেখানো হয়। Gap-এ k = −1 হলে log log n আর k < −1 হলে শুধু watershed — এই দুটি ফল master theorem-এর নয়, গাছ বা Akra–Bazzi থেকে আসে।
টুলটি দিয়ে কয়েকটি পরীক্ষা নিজে করে দেখুন। (১) a = 1, b = 1.5, d = 0, k = 0 বসান — এটি অংশ ৫-এর T(2n/3) + 1, উত্তর আসবে Θ(log n). (২) a = 3, b = 4, d = 1, k = 1 — উদাহরণ ৩-এর case 3, সাথে অনুপাত a/bd = 0.75, যা regularity-র c. (৩) a = 2, b = 4, d = 0.5 — √n-এর উদাহরণ, case 2. (৪) a = 0.5 বসালে টুলটি নিজেই জানাবে যে উপপাদ্য প্রযোজ্য নয়। (৫) শেষে a = 8, b = 2, d = 2 আর a = 7, b = 2, d = 2 পাশাপাশি চালান — Strassen-এর একটি গুণ বাঁচানো ঘাতকে ৩ থেকে ২.৮০৭-এ নামায়। টুলটি শুধু হিসাব মেলানোর সহায়ক; পরীক্ষার খাতায় ধাপগুলো নিজের হাতে লিখতে হবে।
৮ · সংখ্যায় যাচাই — ভবিষ্যদ্বাণী কি সত্যি মেলে?
সূত্র যা বলছে, কম্পিউটার কি তা মানে? যাচাইয়ের সহজ উপায়: n = 2i নিয়ে recurrence-টি সরাসরি হিসাব করুন (T(1) = 1), তারপর ভাগ করুন ভবিষ্যদ্বাণী করা ফাংশন দিয়ে। Θ সঠিক হলে n বাড়ার সাথে অনুপাতটি একটি ধ্রুবকের কাছে থিতু হবে — শূন্যের দিকেও যাবে না, অসীমের দিকেও না। নিচে তিনটি recurrence, তিন case থেকে একটি করে:
- merge: 2T(n/2) + n, ভবিষ্যদ্বাণী Θ(n lg n) — case 2
- karatsuba: 3T(n/2) + n, ভবিষ্যদ্বাণী Θ(nlg 3) = Θ(3i) — case 1
- halving: T(n/2) + n, ভবিষ্যদ্বাণী Θ(n) — case 3
#include <cstdio>
#include <initializer_list>
// T(1) = 1, T(n) = a*T(n/2) + n^p, n = 2^i (exact integers)
long long solve(long long a, int p, int i) {
long long t = 1;
for (int j = 1; j <= i; ++j) {
long long f = 1;
for (int e = 0; e < p; ++e) f *= (1LL << j);
t = a * t + f;
}
return t;
}
int main() {
std::printf("%4s %10s %10s %10s\n", "i", "merge", "karatsuba", "halving");
for (int i : {4, 8, 12, 16, 20}) {
double n = double(1LL << i), p3 = 1;
for (int j = 0; j < i; ++j) p3 *= 3; // n^(log2 3) = 3^i
double m = solve(2, 1, i) / (n * i); // vs n log2 n
double k = solve(3, 1, i) / p3; // vs n^1.585
double h = solve(1, 1, i) / n; // vs n
std::printf("%4d %10.4f %10.4f %10.4f\n", i, m, k, h);
}
}শেষ সারি: 20 1.0500 2.9994 2.0000 — তিনটি অনুপাতই থিতু হয়েছে। পুরো ব্যাখ্যা নিচে।
# T(1) = 1, T(n) = a*T(n/2) + n^p, n = 2^i (exact integers)
def solve(a, p, i):
t = 1
for j in range(1, i + 1):
t = a * t + (2 ** j) ** p
return t
print(f"{'i':>4} {'merge':>10} {'karatsuba':>10} {'halving':>10}")
for i in (4, 8, 12, 16, 20):
n = 2 ** i
m = solve(2, 1, i) / (n * i) # vs n log2 n
k = solve(3, 1, i) / 3 ** i # vs n^(log2 3) = 3^i
h = solve(1, 1, i) / n # vs n
print(f"{i:4d} {m:10.4f} {k:10.4f} {h:10.4f}")আউটপুট C++ সংস্করণের সাথে অক্ষরে অক্ষরে এক। Python-এর int যত বড় দরকার তত বড় হয়, তাই overflow-এর চিন্তা নেই।
দুই ভাষাতেই আউটপুট:
i merge karatsuba halving 4 1.2500 2.6049 1.9375 8 1.1250 2.9220 1.9961 12 1.0833 2.9846 1.9998 16 1.0625 2.9970 2.0000 20 1.0500 2.9994 2.0000
merge কলাম ১-এর দিকে নামছে — আসলে সঠিক মান T(2i) = 2i(i + 1), তাই অনুপাত (i + 1)/i. Θ(n log n) নিশ্চিত, শুধু নিচু ক্রমের “+n” পদটি ধীরে মিলিয়ে যায়। karatsuba কলাম ৩-এর দিকে যাচ্ছে: সঠিক মান 3·3i − 2·2i, অনুপাত 3 − 2(2/3)i — পাতারা (3iটি) জিতছে, ঠিক case 1-এর মতো। halving ২-এ থিতু: T(n) = 2n − 1, অংশ ২-এর ৩১ = 2·16 − 1 মনে আছে? প্রতিটি অনুপাত একটি ধ্রুবকে থামছে, তাই তিনটি Θ-ই সংখ্যায় প্রমাণিত। কোডে solve নিচ থেকে ওপরে হিসাব করে (T(1) থেকে T(2i)) — রিকার্শন ছাড়াই, পূর্ণসংখ্যায়, তাই কোনো rounding ভুল ঢোকে না। সময় O(i·p), জায়গা O(1). 320 প্রায় ৩৪৮ কোটি — long long-এ অনায়াসে ধরে। ভুল Θ দিয়ে ভাগ করলে কী হয় দেখতে চাইলে karatsuba-কে n * i দিয়ে ভাগ করে দেখুন — অনুপাত থামবে না, বাড়তেই থাকবে।
সময় তো হলো — জায়গা কত?
Master theorem শুধু সময় বলে। রিকার্সিভ অ্যালগরিদমের জায়গা (space) আলাদা করে ভাবতে হয়, আর এখানে মূল প্রশ্ন দুটি: একসাথে call stack-এ সর্বোচ্চ কয়টি ডাক থাকে (গভীরতা), আর প্রতিটি ডাক কত বাড়তি মেমরি নেয়। Merge sort-এ গভীরতা Θ(log n), কিন্তু merge-এর জন্য Θ(n) আকারের অস্থায়ী array লাগে — মোট বাড়তি জায়গা Θ(n). রিকার্সিভ binary search-এ প্রতিটি ডাক Θ(1) জায়গা নেয়, গভীরতা Θ(log n), তাই জায়গা Θ(log n); লুপ দিয়ে লিখলে Θ(1). Karatsuba-তে প্রতিটি তলায় অস্থায়ী সংখ্যা রাখতে হয়, যার যোগফল n + n/2 + n/4 + … = Θ(n). লক্ষ করুন — সময়ের গাছে সব নোড যোগ হয়, কিন্তু জায়গার হিসাবে শুধু মূল থেকে একটি পাতা পর্যন্ত একটি পথ, কারণ একই সময়ে কেবল সেই পথের ডাকগুলোই জীবিত থাকে।
পরীক্ষা ও ইন্টারভিউতে
- “T(n) = 2T(n/2) + n সমাধান করুন, পদ্ধতি দেখিয়ে।” — a = 2, b = 2, nlog2 2 = n = f(n), case 2 → Θ(n log n). তিন লাইনে a, b, watershed, case লিখুন।
- “Binary search-এর recurrence আর complexity?” — T(n) = T(n/2) + Θ(1), case 2 (k = 0) → Θ(log n).
- “Master theorem কখন প্রযোজ্য নয়?” — a ধ্রুবক নয় বা a < 1, ভাগ নয় বিয়োগ (T(n−1)), অসমান ভাগ, আর f যখন watershed-এর সাথে polynomially তুলনীয় নয় (gap), বা case 3-এ regularity ভাঙে।
- “Strassen কেন n³-এর চেয়ে দ্রুত?” — ৮-এর বদলে ৭টি subproblem; case 1-এ উত্তর a-তেই নির্ভর করে, তাই Θ(nlog2 7) ≈ Θ(n2.81).
- “একটি recursive ফাংশন দুবার n/2 আকারে নিজেকে ডাকে আর একটি O(1) কাজ করে — সময় কত?” — 2T(n/2) + 1, case 1 → Θ(n). অনেকে ভুল করে log n বলেন।
৯ · AI-এর যুগে master theorem
AI-কে “এই recurrence-এর complexity কত?” জিজ্ঞেস করলে প্রায়ই সঠিক উত্তর আসে — কিন্তু সবসময় নয়, আর ভুলগুলো খুব আত্মবিশ্বাসী সুরে আসে। সবচেয়ে বেশি দেখা যায় এই ভুলগুলো:
| AI-এর ভুল | উদাহরণ | কীভাবে ধরবেন |
|---|---|---|
| “বড়” মানেই case 3 ধরে নেওয়া | 2T(n/2) + n log n → “Θ(n log n)” | Polynomial ব্যবধান আছে কি না নিজে দেখুন — nε গুণক লাগে |
| Gap-এ জোর করে একটি case বসানো | 2T(n/2) + n/log n → “Θ(n)” | Recursion tree-র তলার খরচ লিখে যোগ করুন |
| T(n−1)-এ master theorem প্রয়োগ | T(n−1) + n → “Θ(n log n)” | b আছে কি না দেখুন; না থাকলে যোগফল খুলুন |
| কোড থেকে ভুল recurrence লেখা | দুটি recursive call, কিন্তু লেখে T(n/2) | কোডে কয়টি ডাক আর কত আকার — নিজে গুনুন |
| logb a-র হিসাবে ভুল | log4 2-কে 2 ধরা | 4x = 2 → x = ½; ক্যালকুলেটরে যাচাই |
AI-কে শুধু উত্তর নয়, প্রতিটি ধাপ দেখাতে বলুন — তাহলে কোন ধাপে ভুল, আপনি নিজেই ধরতে পারবেন:
AI-এর দেওয়া complexity হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Master theorem কোথা থেকে এলো, আর একে “master” বলা হয় কেন?
১৯৬০ ও ৭০-এর দশকে divide and conquer অ্যালগরিদম একের পর এক আসছিল — merge sort তো আগে থেকেই ছিল, তারপর Karatsuba-র গুণ, Strassen-এর matrix গুণ, closest pair, FFT-ভিত্তিক নানা অ্যালগরিদম। প্রতিটি নতুন অ্যালগরিদমের সাথে গবেষকেরা প্রায় একই ধরনের recurrence আলাদা আলাদা করে সমাধান করতেন — গাছ আঁকা, ধারা যোগ, induction দিয়ে প্রমাণ।
১৯৮০ সালে Jon Bentley, Dorothea Haken ও James Saxe ACM-এর SIGACT News-এ একটি ছোট প্রবন্ধে দেখান যে T(n) = aT(n/b) + f(n) ধরনের পুরো পরিবারকে একবারে সমাধান করা যায় — f আর nlogb a-এর তুলনা করেই। Bentley পরে “Programming Pearls” কলাম ও বইয়ের জন্য প্রোগ্রামারদের কাছে বিখ্যাত হন; সেখানেও তাঁর মূল সুর একই — সুন্দর গাণিতিক চিন্তা দিয়ে বাস্তব প্রোগ্রামের সময় বোঝা।
“Master” নামটি জনপ্রিয় করে ১৯৯০ সালে প্রকাশিত Cormen, Leiserson ও Rivest-এর Introduction to Algorithms বই (পরে Stein যোগ দিলে CLRS). নামটির ভাবটা “মাস্টার চাবি”-র মতো — এক চাবিতে অনেক দরজা খোলে। বইটির প্রতিটি সংস্করণে উপপাদ্যটি একটু একটু বদলেছে: তৃতীয় সংস্করণে case 2 ছিল শুধু f(n) = Θ(nlogb a), আর চতুর্থ সংস্করণ (২০২২) logk n গুণক সরাসরি উপপাদ্যে এনেছে এবং nlogb a-কে “watershed function” নাম দিয়েছে।
এখান থেকে একটি গুরুত্বপূর্ণ শিক্ষা: একই নামে ভিন্ন বইয়ে সামান্য ভিন্ন রূপ থাকতে পারে। পরীক্ষার খাতায় যে রূপ ব্যবহার করছেন, তার শর্তগুলো স্পষ্ট লিখে দিন — তাহলে পরীক্ষক বুঝবেন আপনি জেনে-বুঝে প্রয়োগ করছেন।
মূল উপলব্ধি: বড় তত্ত্ব প্রায়ই জন্মায় একই কাজ বারবার করার বিরক্তি থেকে। যখন দেখবেন একই ধরনের হিসাব বারবার করছেন, থামুন আর জিজ্ঞেস করুন — এর সাধারণ রূপ কী?
প্র ০২Strassen-এর n2.81 তো n³-এর চেয়ে ভালো — তাহলে সব লাইব্রেরি কেন সবসময় Strassen ব্যবহার করে না?
১৯৬৯ সালে জার্মান গণিতবিদ Volker Strassen দেখান যে দুটি 2×2 matrix গুণ করতে ৮টি নয়, ৭টি গুণই যথেষ্ট — বিনিময়ে বেশ কিছু বাড়তি যোগ-বিয়োগ লাগে। এই কৌশল রিকার্শনে বসালে T(n) = 7T(n/2) + Θ(n²), আর master theorem-এর case 1 বলে Θ(nlog2 7) ≈ Θ(n2.807). তখন পর্যন্ত অনেকেই ধরে নিয়েছিলেন n³-এর নিচে নামা অসম্ভব — Strassen সেই ধারণা ভেঙে দেন, আর শুরু হয় matrix গুণের ঘাত (ω) কমানোর দীর্ঘ প্রতিযোগিতা। Coppersmith ও Winograd ১৯৯০ সালে প্রায় ২.৩৭৬-এ নামান; সাম্প্রতিক গবেষণায় ঘাত ২.৩৭-এর সামান্য নিচে।
কিন্তু বাস্তব লাইব্রেরি (যেমন BLAS-এর আধুনিক রূপগুলো) সাধারণত সাধারণ Θ(n³) পদ্ধতিই খুব যত্ন করে চালায়। কারণ কয়েকটি:
- ধ্রুবক গুণক: Θ লুকিয়ে রাখে যোগ-বিয়োগের বাড়তি খরচ। ছোট matrix-এ ৭টি গুণের লাভ ১৮টি matrix যোগ-বিয়োগে খেয়ে ফেলে।
- মেমরির আচরণ: আধুনিক CPU-তে গণনার চেয়ে মেমরি থেকে ডেটা আনাই ধীর। সাধারণ পদ্ধতিকে block করে cache-বান্ধব করা সহজ; Strassen বাড়তি অস্থায়ী matrix বানায়।
- সংখ্যাগত স্থিতিশীলতা: দশমিক সংখ্যায় বিয়োগ বেশি হলে rounding ভুল জমে — বৈজ্ঞানিক গণনায় এটা গুরুতর।
তাই বাস্তবে Strassen ব্যবহার হলে হয় “hybrid” রূপে: বড় matrix-এ কয়েক ধাপ Strassen, আকার একটি সীমার নিচে নামলেই সাধারণ পদ্ধতি। আর ২.৩৭-ঘাতের অ্যালগরিদমগুলোর ধ্রুবক এত বিশাল যে কোনো বাস্তব আকারে সেগুলো লাভজনক নয় — এদের বলা হয় galactic algorithm.
মূল উপলব্ধি: Master theorem বলে n অসীমের দিকে গেলে কে জিতবে। আপনার ডেটা কত বড়, ধ্রুবক কত, মেমরি কেমন আচরণ করে — সেটা মাপতে হয় আলাদাভাবে।
প্র ০৩Karatsuba কীভাবে প্রমাণ করলেন যে স্কুলের গুণই শেষ কথা নয়? আর Python-এর বড় সংখ্যার সাথে এর সম্পর্ক কী?
স্কুলে শেখা গুণে দুটি n-অঙ্কের সংখ্যা গুণ করতে প্রায় n²টি অঙ্ক-গুণ লাগে। ১৯৬০ সালে মস্কোয় বিখ্যাত গণিতবিদ Andrey Kolmogorov একটি সেমিনারে ধারণা দেন যে এর চেয়ে কম সম্ভব নয়। সেই সেমিনারের তরুণ ছাত্র Anatoly Karatsuba কয়েক দিনের মধ্যেই উল্টোটা দেখান। কৌশলটি: সংখ্যাকে দুই অর্ধেকে ভাঙুন, x = x₁·B + x₀ আর y = y₁·B + y₀. সাধারণভাবে চারটি গুণ লাগে (x₁y₁, x₁y₀, x₀y₁, x₀y₀), কিন্তু মাঝের দুটির যোগফল (x₁ + x₀)(y₁ + y₀) − x₁y₁ − x₀y₀ দিয়ে বের করা যায় — মোট তিনটি গুণ। Recurrence 3T(n/2) + Θ(n), master theorem-এর case 1, উত্তর Θ(nlog2 3) ≈ Θ(n1.585). ফলাফল ১৯৬২ সালে Ofman-এর সাথে যৌথ নামে প্রকাশিত হয়।
এরপর পথ খুলে যায়। Toom–Cook পদ্ধতি সংখ্যাকে ৩ বা আরও বেশি ভাগে ভেঙে ঘাত আরও কমায় (৩ ভাগে ৫টি গুণ: log3 5 ≈ ১.৪৬৫)। ১৯৭১ সালে Schönhage ও Strassen FFT ব্যবহার করে O(n log n log log n)-এ পৌঁছান, আর ২০১৯ সালে David Harvey ও Joris van der Hoeven O(n log n) অ্যালগরিদম দেখান — যদিও সেটি শুধু অকল্পনীয় বড় সংখ্যায় লাভজনক।
দৈনন্দিন সম্পর্ক: CPython-এর int খুব বড় হলে গুণের জন্য Karatsuba ব্যবহার করে — একটি নির্দিষ্ট আকারের নিচে স্কুলের পদ্ধতি, ওপরে Karatsuba. GMP-এর মতো বড়-সংখ্যার লাইব্রেরি আকার অনুযায়ী স্কুলের পদ্ধতি, Karatsuba, Toom আর FFT-ভিত্তিক পদ্ধতির মধ্যে বদল করে। RSA-র মতো cryptography-তে শত শত অঙ্কের সংখ্যা প্রতিনিয়ত গুণ হয় — সেখানে এই পার্থক্য সত্যিই টের পাওয়া যায়।
মূল উপলব্ধি: Case 1-এ subproblem একটি কমালেই ঘাত বদলে যায়। “এভাবেই সবাই করে” কখনো প্রমাণ নয় — একজন ছাত্র একটি গুণ বাঁচিয়ে একজন কিংবদন্তির অনুমান ভুল প্রমাণ করেছিলেন।
প্র ০৪Floor, ceiling আর base case উপেক্ষা করা কি ঠিক? আর অসমান ভাগে Akra–Bazzi কীভাবে সাহায্য করে?
Merge sort আসলে ⌈n/2⌉ আর ⌊n/2⌋ আকারের দুই ভাগে ভাঙে — সমান n/2 নয়, যদি না n জোড় হয়। তবু আমরা লিখি 2T(n/2) + n. এটা কি ফাঁকি? না — CLRS বইয়ে আলাদা করে দেখানো আছে যে floor বা ceiling নিলে recursion tree-র গভীরতা ও প্রতিটি তলার খরচ বড়জোর ধ্রুবক গুণে বদলায়, তাই Θ একই থাকে। Base case-ও তাই: T(1) = 1 না 5 না 100 — এটা পাতার খরচ ধ্রুবক গুণে বদলায় মাত্র। এজন্যই master theorem-এ এগুলো লেখা হয় না।
কিন্তু “প্রায় সমান” আর “সত্যিই অসমান” আলাদা জিনিস। Quicksort-এ pivot যদি সবসময় ১০%-৯০% ভাগ করে, তাহলে T(n) = T(n/10) + T(9n/10) + n — এখানে কোনো একক b নেই। ১৯৯৮ সালে Mohamad Akra ও Louay Bazzi এমন recurrence-এর জন্য সাধারণ পদ্ধতি দেন: T(n) = Σ aiT(bin) + g(n) হলে এমন p খুঁজুন যাতে Σ aibip = 1. তারপর T(n) = Θ(np(1 + ∫1n g(u)/up+1 du)).
১০%-৯০% উদাহরণে (0.1)p + (0.9)p = 1 হয় p = 1-এ। g(u) = u হলে integral ∫ du/u = ln n, তাই T(n) = Θ(n log n) — অর্থাৎ ধারাবাহিক অসমান ভাগেও quicksort n log n থাকে (পর্ব ২২-এ এর অর্থ আরও দেখবেন)। ১৯৯৬ সালে Tom Leighton-এর একটি নোট দেখায় যে floor/ceiling-এর মতো ছোট বিচ্যুতি এই পদ্ধতিতেও সমস্যা করে না।
Master theorem আসলে Akra–Bazzi-র বিশেষ রূপ: একটিমাত্র পদ a·T(n/b) হলে a·b−p = 1 থেকে p = logb a — পরিচিত watershed-এর ঘাত! Integral-টিই ঠিক করে তিন case-এর কোনটি।
মূল উপলব্ধি: Θ-এর জগতে ধ্রুবক গুণের পার্থক্য উপেক্ষা করা যায়, কিন্তু কাঠামোর পার্থক্য (একটি b না অনেকগুলো) উপেক্ষা করা যায় না। কোনটা কোন ধরনের, চিনতে পারাই বিশ্লেষণের দক্ষতা।
প্র ০৫Θ একই হলেও বাস্তবে কেন একটি divide and conquer অন্যটির চেয়ে দ্রুত চলে? “hybrid” অ্যালগরিদম কী?
Merge sort আর heapsort দুটোই Θ(n log n), অথচ বাস্তবে গতিতে পার্থক্য চোখে পড়ার মতো। Master theorem শুধু বৃদ্ধির হার বলে — ধ্রুবক গুণক, মেমরি-অ্যাক্সেসের ধরন আর রিকার্শনের বাড়তি খরচ (ফাংশন ডাকা, stack-এ জায়গা) সে দেখে না।
Recursion tree-তে ফিরে যান। Case 1-এ খরচের বড় অংশ পাতায়, আর case 2-তেও পাতার তলা মোট খরচের একটি সমান অংশ। কিন্তু খুব ছোট সমস্যার জন্য রিকার্শন চালানো অপচয় — ৮টি সংখ্যা সাজাতে আরও ৮ বার ফাংশন ডাকার চেয়ে সরল একটি insertion sort অনেক দ্রুত। তাই বাস্তবের লাইব্রেরি “hybrid” কৌশল নেয়: আকার একটি সীমার (cutoff) নিচে নামলেই সরল অ্যালগরিদমে চলে যায়।
- Introsort (David Musser, ১৯৯৭): quicksort দিয়ে শুরু, রিকার্শন বেশি গভীর হলে heapsort-এ বদল, আর ছোট অংশে insertion sort. অনেক C++ লাইব্রেরির
std::sortএই ধাঁচে লেখা। - Timsort (Tim Peters, ২০০২, Python-এর জন্য): merge sort-এর আত্মীয়, কিন্তু ছোট অংশ (run) binary insertion sort দিয়ে সাজায় আর আগে থেকে সাজানো অংশ খুঁজে কাজে লাগায়। পরে Java-ও বস্তুর তালিকা সাজাতে এটি গ্রহণ করে।
- Karatsuba/Strassen: আগের প্রশ্নগুলোতে দেখেছেন — নির্দিষ্ট আকারের নিচে স্কুলের পদ্ধতি।
Master theorem-এর দৃষ্টিতে cutoff কিছুই বদলায় না: base case Θ(1)-এর বদলে Θ(c²) হলো, যেখানে c একটি ধ্রুবক — Θ একই। কিন্তু ঘড়ির সময় অনেক সময় অর্ধেক হয়ে যায়। Cutoff-এর সঠিক মান তত্ত্ব দিয়ে নয়, মেপে (benchmark করে) ঠিক করা হয়, কারণ সেটি CPU, cache আর compiler-এর ওপর নির্ভর করে।
মূল উপলব্ধি: তত্ত্ব বলে কোন অ্যালগরিদম বড় n-এ টিকে থাকবে; প্রকৌশল বলে সেটিকে কীভাবে দ্রুত চালাতে হয়। ভালো প্রোগ্রামার দুটোই জানেন — আগে Θ দিয়ে ঠিক পরিবার বাছাই, তারপর মেপে মেপে ধ্রুবক কমানো।
অনুশীলন
খাতা-কলম নিয়ে বসুন। প্রথমটি হাতে গাছ আঁকা, শেষটি কোড লেখা।
-
হাতে trace করুন: T(n) = 3T(n/3) + n, T(1) = 1, n = 27. Recursion tree-র প্রতিটি তলায় কয়টি নোড, প্রতিটির আকার আর তলার মোট খরচ লিখুন। মোট T(27) কত? Master theorem-এর উত্তরের সাথে মেলান।
তলা ০: ১টি নোড, আকার ২৭, খরচ ২৭. তলা ১: ৩টি নোড, আকার ৯, খরচ ৩ × ৯ = ২৭. তলা ২: ৯টি নোড, আকার ৩, খরচ ২৭. পাতা: ২৭টি নোড, প্রতিটি T(1) = 1, খরচ ২৭. মোট = ২৭ × ৪ = ১০৮ = n(log3 n + 1). প্রতিটি তলা সমান — case 2 এর চিহ্ন। Master theorem: a = 3, b = 3, watershed nlog3 3 = n = f(n), case 2 → Θ(n log n). মিলে গেল।
-
এক লাইনে সমাধান: (ক) 8T(n/2) + n² (খ) 16T(n/4) + n (গ) 2T(n/4) + √n (ঘ) 3T(n/4) + n log n (ঙ) T(9n/10) + n
(ক) watershed nlog2 8 = n³, f = n² ছোট → case 1, Θ(n³).
(খ) watershed nlog4 16 = n², f = n ছোট → case 1, Θ(n²).
(গ) watershed nlog4 2 = n1/2, f = √n সমান → case 2, Θ(√n log n).
(ঘ) watershed n0.793, f = n log n polynomially বড়; regularity 3(n/4)log(n/4) ≤ (3/4)n log n, c = 3/4 → case 3, Θ(n log n).
(ঙ) a = 1, b = 10/9, watershed ১; f = n বড়; regularity (9/10)n ≤ (9/10)n, c = 9/10 → case 3, Θ(n).
-
খাটে কি খাটে না: নিচের প্রতিটিতে master theorem সরাসরি প্রয়োগ করা যায় কি না বলুন; না গেলে কারণ আর উত্তর দিন। (ক) T(n) = T(n−1) + 1 (খ) T(n) = 0.5T(n/2) + n (গ) T(n) = T(n/4) + T(3n/4) + n (ঘ) T(n) = 4T(n/2) + n²/log n
(ক) না — আকার বিয়োগে কমে। যোগফল খুললে ১ + ১ + … (n বার) = Θ(n).
(খ) না — a = ০.৫ < 1. (Subproblem-এর সংখ্যা ভগ্নাংশ হতে পারে না।)
(গ) না — অসমান ভাগ। গাছের প্রতিটি পূর্ণ তলার খরচ n, গভীরতা Θ(log n) (দীর্ঘতম পথ log4/3 n) → Θ(n log n); Akra–Bazzi-তে p = 1 থেকেও একই।
(ঘ) না — gap. Watershed n², f = n²/log n, অর্থাৎ k = −1. তলা j-এর খরচ n²/(lg n − j), যোগফল n²·Hlg n → Θ(n² log log n).
-
কোড লিখুন: অংশ ৮-এর যাচাই-প্রোগ্রাম বদলে দুটি নতুন recurrence পরীক্ষা করুন — 4T(n/2) + n² (ভবিষ্যদ্বাণী Θ(n² log n)) আর 7T(n/2) + n² (ভবিষ্যদ্বাণী Θ(nlog2 7) = Θ(7i)). অনুপাত কোন সংখ্যার দিকে যাবে, আগে অনুমান করুন।
শুধু
main-এর লুপ বদলালেই চলবে (solveএকই থাকবে):for (int i : {4, 8, 12, 16, 20}) { double n = double(1LL << i), p7 = 1; for (int j = 0; j < i; ++j) p7 *= 7; // 7^i = n^(log2 7) double q = solve(4, 2, i) / (n * n * i); // vs n^2 log2 n double s = solve(7, 2, i) / p7; // vs n^2.807 std::printf("%4d %10.4f %10.4f\n", i, q, s); }সঠিক মান: 4T(n/2) + n²-এ T(2i) = 4i(i + 1), তাই প্রথম অনুপাত (i + 1)/i → ১ (i = 20-এ 1.0500). 7T(n/2) + n²-এ T(2i) = (7·7i − 4·4i)/3, তাই দ্বিতীয় অনুপাত 7/3 − (4/3)(4/7)i → ২.৩৩৩৩. দুটিই ধ্রুবকে থামে — Θ নিশ্চিত। i = 20-এ T প্রায় ১.৯ × ১০১৭, যা
long long-এর সীমার (প্রায় ৯.২ × ১০১৮) ভেতরে, কিন্তু i = 23-এ পৌঁছালেই overflow হবে — Python-এ এই চিন্তা নেই।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি recurrence-এ master theorem প্রয়োগের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Recurrence-কে T(n) = aT(n/b) + f(n) রূপে লেখা
- a ≥ 1 ধ্রুবক আর b > 1 ধ্রুবক কি না নিশ্চিত করা
- Watershed nlogb a হিসাব করা
- f(n)-কে watershed-এর সাথে তুলনা করে case বাছাই
- Case-এর শর্ত (ε, k বা regularity) যাচাই করে Θ লেখা
T(n) = 9T(n/3) + n-এর সমাধান কোনটি?
- Θ(n)
- Θ(n log n)
- Θ(n²)
- Θ(n² log n)
T(n) = 2T(n/2) + n log n-এর সমাধান কী?
- Θ(n log n)
- Θ(n log² n)
- Θ(n²)
- Master theorem দিয়ে কোনোভাবেই সমাধান করা যায় না
নিচের কোন recurrence-এ master theorem সরাসরি প্রয়োগ করা যায় না?
- T(n) = 4T(n/2) + n
- T(n) = T(n − 1) + n
- T(n) = T(n/2) + 1
- T(n) = 3T(n/4) + n log n
T(n) = 8T(n/2) + n²-এ watershed ফাংশন nlogb a কত?
- n²
- n³
- n⁸
- n⁴
Case 3-এর regularity শর্ত a·f(n/b) ≤ c·f(n) (কোনো c < 1-এর জন্য) আসলে কী নিশ্চিত করে?
- Recursion tree-র তলার খরচ জ্যামিতিক হারে কমে, তাই মূল জেতে
- পাতার সংখ্যা f(n)-এর সমান হয়
- Recursion tree-র গভীরতা ধ্রুবক থাকে
- n/b সবসময় পূর্ণসংখ্যা হয়
Karatsuba-র গুণে T(n) = 3T(n/2) + Θ(n). সময়-জটিলতা কত?
- Θ(n²)
- Θ(nlog2 3) ≈ Θ(n1.585)
- Θ(n log n)
- Θ(n1.5)
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ছাঁচ: T(n) = aT(n/b) + f(n), a ≥ 1 আর b > 1 ধ্রুবক।
- সবকিছু একটি তুলনায়: f(n) বনাম watershed nlogb a — recursion tree-র পাতার সংখ্যা।
- ছোট (polynomially) → Θ(nlogb a); সমান (logk পর্যন্ত) → Θ(nlogb a logk+1 n); বড় + regularity → Θ(f(n)).
- “বড়” বা “ছোট” মানে nε গুণকের ব্যবধান; n/log n-এর মতো ফাঁকে উপপাদ্য চুপ — recursion tree বা Akra–Bazzi.
- T(n−1), অসমান ভাগ, অ-ধ্রুবক a-তে সরাসরি খাটে না; সন্দেহ হলে T(n)/ভবিষ্যদ্বাণী অনুপাত চালিয়ে দেখুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১০ · Array আর dynamic array — মেমরিতে পাশাপাশিপরবর্তী পর্বমডিউল ২-এর শুরু: প্রথম ডেটা স্ট্রাকচার।
- পর্ব ০৮ · Recurrence সমাধান — substitution আর recursion treeআগের ভিত্তিযেখানে master theorem খাটে না, সেখানে এই দুই পদ্ধতিই ভরসা।
- ডেটা স্ট্রাকচার ও অ্যালগরিদম · কোর্স হোম৭২টি পর্বপুরো কোর্সের রোডম্যাপ — ৯টি মডিউল।
- C++ প্রোগ্রামিংপাশের ট্র্যাকএই কোর্সের মূল ভাষা — STL, রিকার্শন আর আধুনিক C++.
- Python প্রোগ্রামিংপাশের ট্র্যাকএকই অ্যালগরিদম Python-এ লিখতে আর দ্রুত যাচাই করতে।