এই পর্বে যা শিখবেন
- একটি recursive algorithm থেকে T(n) = aT(n/b) + f(n) লেখা, আর O, Ω, Θ-র নিখুঁত সংজ্ঞা দিয়ে উত্তর বলা
- কেন n = bk ধরে নেওয়া যায়, তার সৎ প্রমাণ: monotonicity আর smoothness দুটোই লাগে
- Recursion tree-র স্তর-যোগ থেকে master theorem-এর তিন case-এর পূর্ণ প্রমাণ
- Karatsuba, Strassen, closest pair-এর recurrence সমাধান, আর ছাঁচের বাইরের recurrence-এ বর্ধিত রূপ ও Akra–Bazzi
- Substitution method-এ ব্যর্থ অনুমান শক্ত করা, আর প্রতিটি উত্তর Python-এ নির্ভুল পূর্ণসংখ্যায় মেলানো
১ · ভেঙে সমাধান — T(n) = aT(n/b) + f(n)
এই মডিউলের আগের তিন পর্বে recurrence-এর সূচক এক বা দুই করে কমেছে: পর্ব ৪৪ “Recurrence দিয়ে মডেল — Fibonacci, Hanoi আর bit string”, পর্ব ৪৫ “Linear recurrence সমাধান — characteristic equation” আর পর্ব ৪৬ “Non-homogeneous recurrence আর particular solution”. সেখানে an নির্ভর করত an−1, an−2-এর ওপর। এই পর্বে আকার কমে ভাগ হয়ে: n থেকে সরাসরি n/2 বা n/3. এমন recurrence আসে divide and conquer algorithm থেকে, আর এদের সমাধানের পদ্ধতিও আলাদা।
ঈদের আগে বাজারঘরের প্রধান অফিসে ৬৪টি জেলার অর্ডার খাতা মেলাতে হবে। ব্যবস্থাপক নিজে সব খাতা দেখেন না। তিনি খাতাগুলো দুই সহকারীর মধ্যে ভাগ করে দেন, প্রত্যেকে আবার নিজের অংশ দুজনকে দেন, এভাবে শেষে একেকজনের হাতে একটি করে খাতা। তারপর ফল ওপরের দিকে জোড়া লাগে: দুজনের সারাংশ মিলিয়ে একটি, সেগুলো মিলিয়ে আরেকটি। মোট কাজ কত? নির্ভর করে তিনটি জিনিসের ওপর: প্রতিবার কয়জনকে ভাগ দেওয়া হয় (a), প্রতিটি ভাগ কত ছোট (b), আর মেলানোর খরচ কত (f). এই পর্বের পুরো গণিত এই তিন সংখ্যার খেলা।
ধ্রুবক a ≥ 1, b > 1 আর একটি ধনাত্মক ফাংশন f-এর জন্য
T(n) = a · T(n/b) + f(n), T(1) = c0 > 0
এখানে a হলো উপ-সমস্যার সংখ্যা, n/b প্রতিটির আকার, আর f(n) হলো ভাঙা আর জোড়ার মোট খরচ, যাকে চালিকা-খরচ (driving function) বলে। n/b পূর্ণসংখ্যা না হলে বাস্তবে ⌊n/b⌋ বা ⌈n/b⌉ বসে; §২-এ দেখব কেন তা উত্তরের বৃদ্ধির হার বদলায় না।
দুটি পরিচিত উদাহরণ। Binary search সাজানো তালিকার মাঝের উপাদানটি দেখে, তারপর এক অর্ধেকে নামে: a = 1, b = 2, f(n) = 1, তাই T(n) = T(n/2) + 1. বাজারঘরের দশ লাখ invoice-এর সাজানো তালিকায় একটি নম্বর খুঁজতে বড়জোর ২০টি তুলনা লাগে। Merge sort তালিকাকে দুই অর্ধেক করে, দুটিকেই সাজায়, তারপর n-এর কাছাকাছি তুলনায় জোড়া দেয়: T(n) = 2T(n/2) + n. দুটির সঠিকতা আর floor-সহ নির্ভুল সমাধান strong induction দিয়ে পর্ব ১১ “Strong induction আর well-ordering”-এ প্রমাণিত; এখানে আমরা সাধারণ ছাঁচটির গণিত চাই।
একটি নিজে মডেল করার উদাহরণ। বাজারঘরের n-টি দিনের বিক্রির মধ্যে সবচেয়ে বড়টি খুঁজতে একজন প্রোগ্রামার “ভেঙে খোঁজা” লিখলেন: তালিকা দুই অর্ধেক করে প্রতিটি অর্ধেকের সর্বোচ্চ আলাদাভাবে বের করা, তারপর একটি তুলনায় দুটির বড়টি নেওয়া। তুলনার সংখ্যা M(1) = 0, M(n) = 2M(n/2) + 1. n = 2k-এ খুলে লিখলে M = 1 + 2 + 4 + … + 2k−1 = n − 1, ঠিক একটি সাধারণ loop-এর সমান। শিক্ষা: ভেঙে সমাধান করলেই কাজ কমে না; কমে শুধু যখন জোড়ার খরচ আর উপ-সমস্যার সংখ্যা মিলে লাভ দেয়। কখন দেয়, সেটিই এই পর্বের প্রশ্ন।
উত্তর সাধারণত নির্ভুল সূত্রে নয়, বৃদ্ধির হারে বলা হয়। তার জন্য তিনটি চিহ্ন লাগে। এদের algorithm-এর দিক DSA কোর্সের পর্ব ০৪-এ আছে; এখানে নিখুঁত সংজ্ঞা দিচ্ছি, কারণ প্রমাণে এগুলোই ব্যবহার হবে।
f, g ধনাত্মক মানের ফাংশন হলে:
- f(n) = O(g(n)) মানে এমন ধ্রুবক c > 0 আর n0 আছে যে সব n ≥ n0-এ f(n) ≤ c · g(n).
- f(n) = Ω(g(n)) মানে এমন c > 0 আর n0 আছে যে সব n ≥ n0-এ f(n) ≥ c · g(n).
- f(n) = Θ(g(n)) মানে দুটোই সত্য: কোনো c1, c2 > 0-এর জন্য বড় n-এ c1g(n) ≤ f(n) ≤ c2g(n).
এই পর্বে lg n মানে log2 n. Θ-এর ভেতরে logarithm-এর ভিত্তি গুরুত্বহীন, কারণ logb n = lg n / lg b, অর্থাৎ শুধু একটি ধ্রুবক গুণক। কিন্তু সূচকে ভিত্তি খুব গুরুত্বপূর্ণ: nlog2 3 ≈ n1.585 আর nlog4 3 ≈ n0.792 একেবারে আলাদা হারে বাড়ে। পরীক্ষায় এই দুটি গুলিয়ে ফেলা খুব সাধারণ ভুল।
২ · Floor, ceiling আর n = bk ধরে নেওয়ার সৎ যুক্তি
বাস্তবের merge sort বিজোড় n-এ দুই সমান অর্ধেক পায় না: এক ভাগে ⌈n/2⌉, অন্যটিতে ⌊n/2⌋. পর্ব ১১-এ দেখেছেন, সবচেয়ে খারাপ ক্ষেত্রে merge sort-এর তুলনার নির্ভুল সংখ্যা M(n) = n⌈lg n⌉ − 2⌈lg n⌉ + 1. এই সূত্র ঢেউয়ের মতো ওঠানামা করে, n = 2k-এ এক রকম, মাঝখানে আরেক রকম। তবু বৃদ্ধির হার Θ(n lg n) সব n-এ একই। পাঠ্যবইগুলো তাই প্রায় সবসময় বলে, “ধরি n = 2k”. প্রশ্ন হলো, এটি কেন বৈধ, আর কখন অবৈধ। উত্তরটি দুটি শর্তে।
g একটি ধনাত্মক ফাংশন, যা বড় n-এ কমে না (non-decreasing). b ≥ 2 পূর্ণসংখ্যার জন্য g(bn) = O(g(n)) হলে g-কে b-মসৃণ (smooth) বলে। যেমন nd মসৃণ, কারণ (bn)d = bdnd; lg n আর nd lg n-ও মসৃণ। কিন্তু 2n মসৃণ নয়: 22n / 2n = 2n, কোনো ধ্রুবকে আটকায় না।
T বড় n-এ non-decreasing, g b-মসৃণ, আর n = bk আকারের সব n-এ T(n) = Θ(g(n)). তাহলে সব n-এ T(n) = Θ(g(n)).
- যেকোনো বড় n-এর জন্য একটি k আছে যাতে bk ≤ n < bk+1. — ঘাতগুলো সব ধনাত্মক সংখ্যাকে ফাঁক ছাড়া ঢাকে
- ঘাতগুলোতে ধ্রুবক c1, c2 আছে: c1g(bj) ≤ T(bj) ≤ c2g(bj). আর মসৃণতা থেকে একটি ধ্রুবক s আছে: g(bm) ≤ s · g(m). — অনুমান
- ওপরের সীমা: T(n) ≤ T(bk+1) ≤ c2g(bk+1) ≤ c2s · g(bk) ≤ c2s · g(n). — T আর g non-decreasing, মসৃণতা
- নিচের সীমা: T(n) ≥ T(bk) ≥ c1g(bk) ≥ (c1/s) · g(bk+1) ≥ (c1/s) · g(n). — একই যুক্তি উল্টো দিকে
- ধ্রুবক c2s আর c1/s n-এর ওপর নির্ভর করে না, তাই T(n) = Θ(g(n)). ∎ — সংজ্ঞা ১.২
দুটি শর্তই সত্যিই লাগে। মসৃণতা ছাড়া ধাপ ৩ ভাঙে: bk আর bk+1-এর মাঝে g অনেক গুণ বাড়তে পারে। Monotonicity ছাড়া ঘাতগুলো আমাদের ঠকাতে পারে: ধরুন T(n) = n যখন n দুইয়ের ঘাত, নইলে n². ঘাতগুলোতে দেখলে T = Θ(n) মনে হয়, অথচ সব n-এ তা মিথ্যা। তাই “ধরি n = 2k” বলার পর T non-decreasing তা দেখানো সৎ প্রমাণের অংশ। Merge sort-এর ধাঁচে সেটা সহজ।
T(1) = c0 ≥ 0, n ≥ 2-এ T(n) = T(⌈n/2⌉) + T(⌊n/2⌋) + f(n), আর f ≥ 0 non-decreasing হলে সব n ≥ 1-এ T(n) ≤ T(n + 1).
- ভিত্তি n = 1: T(2) = 2T(1) + f(2) ≥ T(1), কারণ T(1) ≥ 0 আর f(2) ≥ 0. — সরাসরি
- অনুমান: n ≥ 2, আর সব m < n-এ T(m) ≤ T(m + 1); ফলে 1 থেকে n পর্যন্ত T non-decreasing. — strong induction (পর্ব ১১)
- ⌈n/2⌉ ≤ ⌈(n+1)/2⌉ আর ⌊n/2⌋ ≤ ⌊(n+1)/2⌋, আর n ≥ 2-এ দুটি নতুন আকারই ≤ n. — floor-ceiling-এর ধর্ম
- তাই অনুমান থেকে T(⌈n/2⌉) ≤ T(⌈(n+1)/2⌉), T(⌊n/2⌋) ≤ T(⌊(n+1)/2⌋), আর f(n) ≤ f(n + 1). তিনটি যোগ করলে T(n) ≤ T(n + 1). ∎ — ধাপ ২, ৩
আরও সাধারণ ক্ষেত্রে, যেমন T(⌈n/b⌉), পাঠ্যবইগুলো আরেকটি পথ নেয়: ceiling-এর পর আকার n/bj + b/(b − 1)-এর বেশি হয় না, আর এই ছোট বাড়তি অংশ Θ বদলায় না (CLRS-এর master theorem অধ্যায়ের floor-ceiling অংশ)। আমরা এখন থেকে n = bk ধরে কাজ করব, আর উপপাদ্য ২.২ মনে রাখব।
৩ · Recursion tree — স্তর ধরে খরচের যোগ
T(n) = aT(n/b) + f(n) একটি গাছ আঁকে। মূলে আকার n-এর একটি সমস্যা, নিজস্ব খরচ f(n). তার a-টি সন্তান, প্রতিটি আকার n/b, প্রতিটির নিজস্ব খরচ f(n/b). এভাবে আকার 1-এ পৌঁছানো পর্যন্ত। এই গাছই recursion tree. মোট খরচ হলো সব node-এর নিজস্ব খরচের যোগ, আর সবচেয়ে সুবিধাজনক উপায় হলো স্তর ধরে যোগ করা। বারবার নিজের ভেতরে বসিয়ে খুলে লেখার এই পদ্ধতিকে iteration বা unrolling method-ও বলে।
T(n) = aT(n/b) + f(n)-এর recursion tree (ছবিতে a = 3)
বাঁয়ে গাছ, ডানে প্রতিটি স্তরের যোগ। স্তর j-তে ajটি node, প্রতিটির খরচ f(n/bj). উচ্চতা k = logb n, আর পাতা ak = nlogb aটি।
n = bk (b ≥ 2 পূর্ণসংখ্যা) হলে
T(n) = ak T(1) + ∑j=0k−1 aj f(n/bj)
আর পাতার সংখ্যা ak = alogb n = nlogb a.
- ভিত্তি k = 0: n = 1, যোগফলটি ফাঁকা, দুই পাশেই T(1). — সরাসরি
- অনুমান: k-এর জন্য সূত্রটি সত্য। — induction (পর্ব ১০)
- ধাপ: m = bk+1 হলে T(m) = aT(bk) + f(m) = ak+1T(1) + ∑j=0k−1 aj+1 f(bk−j) + f(m). সূচক i = j + 1 বসালে যোগফলটি ∑i=1k ai f(m/bi), আর বাড়তি f(m) হলো i = 0-এর পদ। ফলে সূত্রটি k + 1-এ সত্য। — recurrence, সূচক বদল
- পাতার সংখ্যা: alogb n আর nlogb a-এর logb নিলে দুটিই (logb n)(logb a), আর logb এক-এক ফাংশন, তাই সংখ্যা দুটি সমান। ∎ — logarithm-এর নিয়ম
ধাপ ৪-এর অভেদটি পরীক্ষায় খুব কাজের: T(n) = 9T(n/3) + … আর n = 81 হলে পাতা 94 = 6561 = 812. ফাংশন nlogb a পাতার মোট খরচ মাপে; DSA কোর্স একে watershed ফাংশন বলে। নিচের stepper সবচেয়ে চেনা গাছটি, T(n) = 2T(n/2) + n, n = 16-এ স্তর ধরে যোগ করে।
1স্তর ০: 1 × 16 = 162স্তর ১: 2 × 8 = 163স্তর ২: 4 × 4 = 164স্তর ৩: 8 × 2 = 165পাতা: 16 × T(1) = 166মোট 4 · 16 + 16 = 80
| node | |
| আকার | |
| স্তরের যোগ | |
| জমা যোগ |
- মূল। আকার 16-এর সমস্যা নিজে 16 খরচ করে: দুই ভাগে ভাঙা আর শেষে জোড়া দেওয়া। বাকি কাজ দুই সন্তানের।
- স্তর ১। দুটি node, প্রতিটি আকার 8, প্রতিটির নিজস্ব খরচ 8. স্তরের যোগ 2 × 8 = 16: মূলের সমান।
- স্তর ২। চারটি node, আকার 4. node দ্বিগুণ হলো, প্রতিটির খরচ অর্ধেক হলো, তাই যোগ আবার 16. এখানে q = a/bd = 2/2 = 1.
- স্তর ৩। আটটি node, আকার 2, যোগ 16. মোট lg 16 = 4টি অ-পাতা স্তর, প্রতিটির যোগ n.
- পাতা। 24 = 16টি পাতা, প্রতিটি T(1) = 1. পাতার সংখ্যা nlog2 2 = n, তাই এই স্তরেও n.
- মোট। 4 · 16 + 16 = 80 = n lg n + n. সব স্তর সমান, তাই উত্তর “এক স্তরের খরচ × স্তরের সংখ্যা” = Θ(n lg n). Recurrence সরাসরি চালালেও T(16) = 80 আসে।
নিচের ব্লক যেকোনো a, b, f-এর জন্য এই স্তর-টেবিল ছাপে, আর স্তরের যোগকে recurrence সরাসরি চালানোর ফলের সঙ্গে মেলায়। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।
def T(a, b, f, n): # direct recursion, n a power of b
return 1 if n == 1 else a * T(a, b, f, n // b) + f(n)
def tree(a, b, f, n, name): # recursion tree, one row per level
print(name, " n =", n)
total, j, size = 0, 0, n
while size > 1:
s = a ** j * f(size) # a^j nodes, each costing f(n/b^j)
print(f" level {j}: {a ** j:4} nodes x f({size:2}) = {s:5}")
total += s
j, size = j + 1, size // b
print(f" leaves : {a ** j:4} x T(1) = {a ** j:5}")
total += a ** j
print(" sum of levels =", total, " direct T(n) =", T(a, b, f, n))
tree(2, 2, lambda m: m, 32, "T(n) = 2T(n/2) + n")
tree(3, 2, lambda m: m, 32, "T(n) = 3T(n/2) + n")
tree(2, 2, lambda m: m * m, 32, "T(n) = 2T(n/2) + n^2")আসল আউটপুট (ব্রাউজারের Python):
T(n) = 2T(n/2) + n n = 32
level 0: 1 nodes x f(32) = 32
level 1: 2 nodes x f(16) = 32
level 2: 4 nodes x f( 8) = 32
level 3: 8 nodes x f( 4) = 32
level 4: 16 nodes x f( 2) = 32
leaves : 32 x T(1) = 32
sum of levels = 192 direct T(n) = 192
T(n) = 3T(n/2) + n n = 32
level 0: 1 nodes x f(32) = 32
level 1: 3 nodes x f(16) = 48
level 2: 9 nodes x f( 8) = 72
level 3: 27 nodes x f( 4) = 108
level 4: 81 nodes x f( 2) = 162
leaves : 243 x T(1) = 243
sum of levels = 665 direct T(n) = 665
T(n) = 2T(n/2) + n^2 n = 32
level 0: 1 nodes x f(32) = 1024
level 1: 2 nodes x f(16) = 512
level 2: 4 nodes x f( 8) = 256
level 3: 8 nodes x f( 4) = 128
level 4: 16 nodes x f( 2) = 64
leaves : 32 x T(1) = 32
sum of levels = 2016 direct T(n) = 2016
তিনটি গাছের তিনটি চরিত্র। প্রথমটিতে প্রতিটি স্তর 32: সুষম গাছ, মোট = স্তর × স্তরের সংখ্যা। দ্বিতীয়টিতে প্রতিটি স্তর আগেরটির 1.5 গুণ, আর সবচেয়ে বড় অংশ পাতায় (243): পাতা-ভারী গাছ। তৃতীয়টিতে প্রতিটি স্তর আগেরটির অর্ধেক, আর মূল একাই মোটের অর্ধেকের বেশি (1024 / 2016): মূল-ভারী গাছ। তিন ক্ষেত্রেই স্তর-যোগ সরাসরি চালানো recurrence-এর সঙ্গে হুবহু মেলে, যা লেমা ৩.১-এর সংখ্যাগত যাচাই। গুণক 1, 1.5 আর 0.5 আসলে q = a/bd; পরের অংশে এটাই প্রমাণের চাবি।
৪ · Master theorem আর তার পূর্ণ প্রমাণ
f(n) = nd হলে স্তর j-এর যোগ aj(n/bj)d = nd · (a/bd)j. অর্থাৎ স্তরগুলো একটি geometric series, যার অনুপাত q = a/bd. Geometric series-এর যোগ পর্ব ১৭ “Sequence আর summation — closed form আর বিখ্যাত যোগফল”-এ প্রমাণিত: q ≠ 1 হলে ∑j=0k−1 qj = (qk − 1)/(q − 1). বাকিটা শুধু q-কে 1-এর সঙ্গে তুলনা। এই ফলটির নাম master theorem.
a ≥ 1, b ≥ 2 পূর্ণসংখ্যা, d ≥ 0. n = bk-এ T(1) = c0 > 0 আর T(n) = aT(n/b) + f(n), যেখানে কোনো ধ্রুবক 0 < c1 ≤ c2-এর জন্য সব n ≥ 1-এ c1nd ≤ f(n) ≤ c2nd. তাহলে
- Case 1 (d < logb a, অর্থাৎ q > 1): T(n) = Θ(nlogb a)
- Case 2 (d = logb a, অর্থাৎ q = 1): T(n) = Θ(nd log n)
- Case 3 (d > logb a, অর্থাৎ q < 1): T(n) = Θ(nd)
Monotone T-এর জন্য উপপাদ্য ২.২ এই উত্তর সব n-এ ছড়িয়ে দেয়, কারণ তিনটি উত্তরই মসৃণ।
তিন case-এর শর্ত আসলে এক কথা: d < logb a ⇔ bd < a ⇔ q > 1. Case-এর নম্বর DSA কোর্স আর বেশিরভাগ বইয়ের মতো রাখা হলো: case 1-এ পাতা জেতে, case 3-এ মূল জেতে।
- স্তর-যোগ: লেমা ৩.১ থেকে T(n) = c0ak + S, যেখানে S = ∑j=0k−1 ajf(n/bj). — লেমা ৩.১
- চাপা দেওয়া: f-এর সীমা থেকে c1ndQ ≤ S ≤ c2ndQ, যেখানে Q = ∑j=0k−1 qj, কারণ aj(n/bj)d = ndqj. আর ak = nlogb a. — বীজগণিত, লেমা ৩.১-এর ধাপ ৪
- Case 3 (q < 1): 1 ≤ Q ≤ 1/(1 − q), কারণ প্রথম পদ 1 আর অসীম যোগফল 1/(1 − q). তাই S = Θ(nd). আর logb a < d বলে পাতা c0nlogb a ≤ c0nd. যোগ করলে c1nd ≤ T(n) ≤ (c2/(1 − q) + c0)nd. — geometric series
- Case 2 (q = 1): Q = k = logb n, আর ak = nd. তাই c1nd logb n ≤ T(n) ≤ c2nd logb n + c0nd, অর্থাৎ Θ(nd log n). — k-টি সমান পদ
- Case 1 (q > 1): Q = (qk − 1)/(q − 1) < qk/(q − 1), আর qk = ak/bdk = nlogb a/nd. তাই S < c2nlogb a/(q − 1). নিচের দিকে T(n) ≥ c0ak = c0nlogb a. ফলে c0nlogb a ≤ T(n) ≤ (c0 + c2/(q − 1))nlogb a. — geometric series, পাতার স্তর
- তিন ক্ষেত্রেই ধ্রুবকগুলো n-এর ওপর নির্ভর করে না, তাই দাবি করা Θ-সীমা প্রমাণিত। ∎ — সংজ্ঞা ১.২
প্রমাণটি একটি ছবিতে ধরা যায়। Geometric series-এ q < 1 হলে প্রথম পদই প্রধান (মূল), q > 1 হলে শেষ পদ প্রধান (পাতা), আর q = 1 হলে সব পদ সমান, তাই পদের সংখ্যা log n গুণক হয়ে আসে। f(n) ঠিক nd আর T(1) = 1 হলে নির্ভুল সূত্রও বেরিয়ে আসে: q ≠ 1-এ T(n) = nlogb a + nd(qk − 1)/(q − 1). যেমন T(n) = 3T(n/2) + n-এ q = 3/2, আর হিসাব করলে T(n) = 3nlg 3 − 2n. নিচের ব্লক চারটি recurrence-এর নির্ভুল মান বের করে, বন্ধ সূত্রের সঙ্গে assert দিয়ে মেলায়, তারপর Θ-অনুমানের সঙ্গে অনুপাত দেখায়।
from fractions import Fraction
def T(a, f, k): # exact T(2^k) with T(1) = 1, b = 2
t = 1
for i in range(1, k + 1): # bottom-up: T(2^i) = a*T(2^(i-1)) + f
t = a * t + f(2 ** i, i)
return t
cases = [ # recurrence, Theta guess, a, f(n, lg n), g(n, lg n), closed form
("T(n/2)+1", "lg n", 1, lambda n, k: 1, lambda n, k: k,
lambda n, k: k + 1),
("2T(n/2)+n", "n lg n", 2, lambda n, k: n, lambda n, k: n * k,
lambda n, k: n * k + n),
("3T(n/2)+n", "n^lg3", 3, lambda n, k: n, lambda n, k: 3 ** k,
lambda n, k: 3 ** (k + 1) - 2 * n),
("2T(n/2)+n^2", "n^2", 2, lambda n, k: n * n, lambda n, k: n * n,
lambda n, k: 2 * n * n - n),
]
print("T(n)/g(n) at n = 2^4, 2^8, 2^16, 2^24")
for name, label, a, f, g, exact in cases:
ratios = []
for k in (4, 8, 16, 24):
n, t = 2 ** k, T(a, f, k)
assert t == exact(n, k) # closed form checked exactly
ratios.append(f"{float(Fraction(t, g(n, k))):.4f}")
print(f"{name:12} g = {label:7}", " ".join(ratios))আসল আউটপুট (ব্রাউজারের Python):
T(n)/g(n) at n = 2^4, 2^8, 2^16, 2^24
T(n/2)+1 g = lg n 1.2500 1.1250 1.0625 1.0417
2T(n/2)+n g = n lg n 1.2500 1.1250 1.0625 1.0417
3T(n/2)+n g = n^lg3 2.6049 2.9220 2.9970 2.9999
2T(n/2)+n^2 g = n^2 1.9375 1.9961 2.0000 2.0000
কোনো assert ব্যর্থ হয়নি, তাই চারটি বন্ধ সূত্রই (lg n + 1, n lg n + n, 3nlg 3 − 2n, 2n2 − n) প্রতিটি পরীক্ষিত n-এ নির্ভুল। nlg 3-কে কোডে 3k লেখা হয়েছে, কারণ n = 2k-এ দুটি সমান, আর এতে float এড়ানো গেল। অনুপাতগুলো একটি ধনাত্মক ধ্রুবকের দিকে যায় (1, 1, 3, 2): ঠিক এটাই Θ-এর অর্থ। প্রথম দুই সারিতে অনুপাত 1 + 1/lg n, তাই ধীরে নামে; তৃতীয় সারিতে 3 − 2n1 − lg 3, যা দ্রুত 3-এ পৌঁছায়। ধ্রুবকটি master theorem বলে না; সেটি আসে নির্ভুল সূত্র থেকে।
Algorithm নকশায় এর অর্থ। তিন case তিন রকম পরামর্শ দেয়। Case 1-এ (পাতা-ভারী) উত্তর nlogb a, যেখানে f একেবারেই নেই: জোড়ার খরচ কমিয়ে লাভ নেই, লাভ শুধু উপ-সমস্যার সংখ্যা a কমালে, যা Karatsuba আর Strassen করেছেন। Case 3-এ (মূল-ভারী) উত্তর f(n): এখানে উল্টো, recursion প্রায় বিনা খরচের, তাই মূলের ভাঙা-জোড়ার কাজ দ্রুত করাই একমাত্র পথ। Case 2-এ (সুষম) দুই দিকই সমান ভারী, আর বাড়তি log n গুণকটি আসে স্তরের সংখ্যা থেকে; merge sort এই ভারসাম্যের সবচেয়ে চেনা উদাহরণ। তাই কোনো নতুন পদ্ধতির খরচ কমাতে চাইলে আগে দেখুন গাছটি কোন দিকে ভারী।
শর্তগুলো ভুলবেন না। উপপাদ্য ৪.১-এর প্রতিটি অনুমান প্রমাণে কোথাও ব্যবহৃত হয়েছে। (১) a আর b ধ্রুবক: T(n) = nT(n/2) + 1-এ a = n বদলায়, তাই উপপাদ্য খাটে না। (২) a ≥ 1: T(n) = 0.5T(n/2) + n-এ “অর্ধেক উপ-সমস্যা” কোনো algorithm-এর অর্থ বহন করে না। (৩) b > 1: আকার সত্যিই ভাগ হয়ে কমতে হবে; T(n) = 2T(n − 1) + 1 এই ছাঁচে পড়ে না, তার উত্তর সূচকীয় (পর্ব ৪৪-এর Hanoi). (৪) f-এর দুই দিকের সীমা: শুধু f(n) ≤ c2nd জানা থাকলে প্রমাণের ওপরের অর্ধেকই চলে, ফলে উত্তর O(…), Θ নয়। আর T(1)-এর মান বা f-এর ধ্রুবক গুণক উত্তরের Θ বদলায় না, শুধু নির্ভুল সূত্রের ধ্রুবক বদলায়।
৫ · Case বাছাই ধাপে ধাপে, আর পরিচিত algorithm-এর হিসাব
পরীক্ষায় master theorem প্রয়োগ একটি নির্দিষ্ট ক্রমে করলে ভুল কম হয়: a, b, d পড়া; logb a বের করা; d-এর সঙ্গে তুলনা (বা q = a/bd-কে 1-এর সঙ্গে); case থেকে উত্তর। নিচের stepper এই ক্রম T(n) = 3T(n/2) + n-এ দেখায়।
1a = 3, b = 2, f(n) = n2log₂ 3 ≈ 1.5853d = 1 < 1.585 ⇒ case 14স্তর: 16, 24, 36, 54, 815T(n) = Θ(n^1.585)6n = 1024: অনুপাত ≈ 2.97
| d | |
| logb a | |
| q = a/bd | |
| উত্তর |
- পড়া। ৩টি উপ-সমস্যা (a = 3), প্রতিটির আকার n/2 (b = 2), আর ভাঙা-জোড়ার খরচ n, অর্থাৎ f(n) = n1, d = 1. এটিই Karatsuba-র recurrence (§৬)।
- পাতা। log2 3 ≈ 1.585, তাই পাতার সংখ্যা nlog2 3 ≈ n1.585. এটি সেই ফাংশন, যার সঙ্গে f(n)-এর তুলনা হবে।
- তুলনা। d = 1 < 1.585, অর্থাৎ q = 3/21 = 1.5 > 1. এটি case 1: প্রতিটি নিচের স্তর ওপরেরটির চেয়ে ভারী।
- স্তরগুলো। n = 16-এ স্তরের যোগ 16, 24, 36, 54, আর পাতায় 34 = 81: প্রতিবার 1.5 গুণ। মোট 211, যার প্রায় ৩৮% শুধু পাতায়।
- সিদ্ধান্ত। T(n) = Θ(nlg 3) ≈ Θ(n1.585). T(1) = 1 হলে নির্ভুল সূত্র T(n) = 3nlg 3 − 2n, অর্থাৎ 3k+1 − 2k+1.
- সংখ্যায় যাচাই। n = 1024 = 210: T = 311 − 211 = 177147 − 2048 = 175099, আর nlg 3 = 310 = 59049. অনুপাত ≈ 2.965, যা n বাড়লে 3-এর দিকে যায়।
একই তিন ধাপে পরিচিত algorithm-গুলোর হিসাব। এদের code আর বিস্তারিত বিশ্লেষণ DSA কোর্সের পর্ব ০৯ আর পর্ব ৪৯-এ; এখানে শুধু গণিত।
| Algorithm | Recurrence | logb a বনাম d | উত্তর |
|---|---|---|---|
| Binary search | T(n/2) + 1 | 0 = 0 (case 2) | Θ(log n) |
| Merge sort | 2T(n/2) + n | 1 = 1 (case 2) | Θ(n log n) |
| Closest pair (আগে থেকে সাজানো) | 2T(n/2) + n | 1 = 1 (case 2) | Θ(n log n) |
| Karatsuba গুণ | 3T(n/2) + n | 1.585 > 1 (case 1) | Θ(n1.585) |
| Matrix গুণ, সরল ভাগ | 8T(n/2) + n2 | 3 > 2 (case 1) | Θ(n3) |
| Strassen | 7T(n/2) + n2 | 2.807 > 2 (case 1) | Θ(n2.807) |
| এক ভাগে নামা, linear কাজ | T(n/2) + n | 0 < 1 (case 3) | Θ(n) |
টেবিলের একটি শিক্ষা: সরল ভাগে matrix গুণ 8T(n/2) + n2 = Θ(n3), সাধারণ পদ্ধতির চেয়ে একটুও ভালো নয়। Divide and conquer নিজে কিছু বাঁচায় না; বাঁচায় a কমানো। Case 1-এ উত্তর শুধু a আর b-এর ওপর নির্ভর করে, f-এর ওপর নয়, তাই উপ-সমস্যা একটি কমালেই সূচক কমে। Karatsuba আর Strassen-এর পুরো কৌশল এটিই।
৬ · Karatsuba আর Strassen — একটি গুণ কম, সূচক কম
দুটি n-অঙ্কের সংখ্যা স্কুলের নিয়মে গুণ করলে প্রতিটি অঙ্ককে প্রতিটি অঙ্কের সঙ্গে গুণ করতে হয়: n2টি এক-অঙ্কের গুণ। একে schoolbook multiplication বলে। ধরুন n জোড়, m = n/2, আর x = a · 10m + b, y = c · 10m + d, যেখানে a, b, c, d প্রতিটি m-অঙ্কের। তখন
xy = ac · 102m + (ad + bc) · 10m + bd
সরলভাবে এখানে চারটি অর্ধেক-আকারের গুণ (ac, ad, bc, bd), তাই T(n) = 4T(n/2) + Θ(n) = Θ(n2): কোনো লাভ নেই। Karatsuba-র পর্যবেক্ষণ হলো মাঝের পদটি আলাদা দুটি গুণ ছাড়াই পাওয়া যায়।
যেকোনো সংখ্যা a, b, c, d-এর জন্য ad + bc = (a + b)(c + d) − ac − bd. তাই xy বের করতে তিনটি গুণ যথেষ্ট: ac, bd আর (a + b)(c + d).
- (a + b)(c + d) = ac + ad + bc + bd. — বণ্টন নিয়ম
- দুই পাশ থেকে ac + bd বিয়োগ করলে (a + b)(c + d) − ac − bd = ad + bc. ∎ — বীজগণিত
উদাহরণ 1234 × 5678: a = 12, b = 34, c = 56, d = 78. ac = 672, bd = 2652, (12 + 34)(56 + 78) = 46 × 134 = 6164, তাই মাঝের পদ 6164 − 672 − 2652 = 2840. ফল 672 · 104 + 2840 · 102 + 2652 = 7006652, যা 1234 × 5678-এর সঙ্গে মেলে। যোগ-বিয়োগ আর 10-এর ঘাতে সরানো Θ(n) খরচের, তাই T(n) = 3T(n/2) + Θ(n), যা §৫-এর stepper অনুযায়ী Θ(nlg 3). নিচের ব্লক ছোট (প্রায় এক-অঙ্কের) গুণ গুনে দেখে, সময় মাপে না।
import random
count = {"mul": 0, "big": 0}
def karatsuba(x, y, n): # x, y below 10^n, n a power of 2
if n == 1:
count["mul"] += 1 # one small multiplication
count["big"] = max(count["big"], x, y)
return x * y
m = n // 2
a, b = divmod(x, 10 ** m) # x = a*10^m + b
c, d = divmod(y, 10 ** m)
ac = karatsuba(a, c, m)
bd = karatsuba(b, d, m)
s = karatsuba(a + b, c + d, m) - ac - bd # = ad + bc
return ac * 10 ** (2 * m) + s * 10 ** m + bd
random.seed(47)
print(" digits Karatsuba schoolbook ratio 3^k = n^lg3")
for k in range(9):
n = 2 ** k
x = random.randrange(10 ** (n - 1), 10 ** n)
y = random.randrange(10 ** (n - 1), 10 ** n)
count["mul"] = 0
assert karatsuba(x, y, n) == x * y
print(f"{n:6} {count['mul']:10} {n * n:11} {count['mul'] / n**2:6.3f}"
f" {3 ** k:9}")
print("largest operand in a small multiplication:", count["big"])আসল আউটপুট (ব্রাউজারের Python):
digits Karatsuba schoolbook ratio 3^k = n^lg3
1 1 1 1.000 1
2 3 4 0.750 3
4 9 16 0.562 9
8 27 64 0.422 27
16 81 256 0.316 81
32 243 1024 0.237 243
64 729 4096 0.178 729
128 2187 16384 0.133 2187
256 6561 65536 0.100 6561
largest operand in a small multiplication: 56
ছোট গুণের সংখ্যা ঠিক 3k = nlg 3, কারণ M(n) = 3M(n/2), M(1) = 1-এর সমাধান এটাই; schoolbook-এ n2 = 4k. ২৫৬ অঙ্কে অনুপাত 0.100, অর্থাৎ দশ ভাগের এক ভাগ গুণ, আর অনুপাত (3/4)k হারে কমতেই থাকে। প্রতিটি গুণফল assert দিয়ে Python-এর নিজস্ব গুণের সঙ্গে মেলানো। শেষ লাইনটি একটি সৎ স্বীকারোক্তি: a + b-এর carry-র কারণে “এক-অঙ্কের” গুণে কখনো কখনো একটু বড় সংখ্যা আসে (এই run-এ সর্বোচ্চ 56). সেটি এখনো একটি ধ্রুবক-আকারের গুণ, তাই গণনা Θ-কে বদলায় না। এখানে যোগ-বিয়োগ গোনা হয়নি; সেগুলো f(n) = Θ(n) অংশ, আর case 1-এ উত্তর তাতে বদলায় না।
Strassen-এর matrix গুণ একই ভাবনা। দুটি n × n matrix-কে চারটি করে n/2 × n/2 খণ্ডে ভাগ করলে সরল পদ্ধতিতে খণ্ডের ৮টি গুণ লাগে। Strassen ১৯৬৯ সালে এমন ৭টি গুণফল দেখান, যাদের যোগ-বিয়োগে (প্রায় ১৮টি খণ্ড-যোগ) চারটি ফল-খণ্ডই পাওয়া যায়। ফলে T(n) = 7T(n/2) + Θ(n2), log2 7 ≈ 2.807 > 2, case 1: Θ(n2.807). n = 210 = 1024 হলে পাতার স্তরে 710 = 282,475,249টি সংখ্যা-গুণ, সরল ভাগে 810 = 1,073,741,824টি: প্রায় ২৬%. Boolean matrix আর matrix-এর discrete ব্যবহার পর্ব ১৯ “Discrete math-এ matrix — 0-1 matrix আর Boolean product”-এ আছে।
Closest pair কেন n log n. সমতলের n-টি বিন্দু (ধরুন বাজারঘরের ডেলিভারি পয়েন্ট) x-স্থানাঙ্কে সাজিয়ে একটি উল্লম্ব রেখায় দুই অর্ধেক করা হয়, প্রতিটি অর্ধেকে সবচেয়ে কাছের জোড়ার দূরত্ব δL, δR recursion দিয়ে আসে। δ = min(δL, δR). বাকি থাকে রেখার দুই পাশের জোড়া, আর সেগুলোর শুধু রেখার δ দূরত্বের ফালিতে থাকা বিন্দু দেখলেই চলে। একটি জ্যামিতিক pigeonhole যুক্তি দেখায়, ফালিতে y-অনুযায়ী সাজানো ক্রমে প্রতিটি বিন্দুকে শুধু পরের কয়েকটি (একটি ছোট ধ্রুবক সংখ্যক) বিন্দুর সঙ্গে মেলাতে হয়। তাই জোড়ার খরচ Θ(n), আর T(n) = 2T(n/2) + Θ(n) = Θ(n log n), যদি y-ক্রম আগে থেকে রাখা যায়। প্রতি ধাপে নতুন করে সাজালে খরচ Θ(n log n) হয়ে যায়, যার ফল §৭-এ দেখব। ধ্রুবকটির সঠিক মান আর code DSA কোর্সের পর্ব ৪৯-এ।
৭ · ছাঁচের বাইরে — n log n চালিকা, ফাঁক, regularity আর Akra–Bazzi
উপপাদ্য ৪.১ শুধু f(n) = Θ(nd)-এর জন্য। বাস্তবে অন্য রূপও আসে। ধরুন closest pair algorithm প্রতিটি ধাপে বিন্দুগুলো নতুন করে সাজায়: তখন জোড়ার খরচ Θ(n log n), আর T(n) = 2T(n/2) + n lg n. এখানে d = 1 = log2 2, কিন্তু f ঠিক n1 নয়। Recursion tree সরাসরি কাজ করে।
n = 2k, T(1) = 1 হলে স্তর j-এর যোগ 2j · (n/2j) · lg(n/2j) = n(k − j). তাই
T(n) = n(k + (k − 1) + … + 1) + n = n · k(k + 1)/2 + n = Θ(n lg2 n)
লক্ষ করুন, এই ফল case 2-এর মতো (n lg n) নয়, এক lg গুণক বেশি। সাধারণভাবে, d = logb a আর f(n) = Θ(nd lgp n) হলে, p ≥ 0-এ T(n) = Θ(nd lgp+1 n). অনেক বই case 2-কে এই বর্ধিত রূপে লেখে। এর প্রমাণ উপরের মতোই: স্তরের যোগ nd(k − j)p-এর গুণিতক, আর ∑(k − j)p = Θ(kp+1).
দুই case-এর মাঝের ফাঁক। T(n) = 2T(n/2) + n/lg n নিন। n/lg n কি n-এর চেয়ে ছোট? হ্যাঁ। তাহলে কি case 1, উত্তর Θ(n)? না। Case 1-এর আসল শর্ত হলো f(n) = O(nlogb a − ε) কোনো ধ্রুবক ε > 0-এর জন্য, অর্থাৎ f-কে polynomially ছোট হতে হবে। n/lg n যেকোনো n1−ε-এর চেয়ে বড়, তাই কোনো case খাটে না। Recursion tree আবার উত্তর দেয়: স্তর j-এর যোগ 2j · (n/2j)/(k − j) = n/(k − j), তাই অ-পাতা স্তরের মোট n(1/k + … + 1/1) = n · Hk, যেখানে Hk একটি harmonic number (পর্ব ৪০). Hk ≈ ln k, তাই T(n) = Θ(n log log n): n-এর চেয়ে সামান্য বেশি, n log n-এর চেয়ে অনেক কম।
a ≥ 1, b > 1, f বড় n-এ ধনাত্মক। T(n) = aT(n/b) + f(n) (n/b-এর জায়গায় floor বা ceiling চলবে) হলে:
- কোনো ε > 0-এ f(n) = O(nlogb a − ε) হলে T(n) = Θ(nlogb a).
- কোনো p ≥ 0-এ f(n) = Θ(nlogb a lgp n) হলে T(n) = Θ(nlogb a lgp+1 n).
- কোনো ε > 0-এ f(n) = Ω(nlogb a + ε), আর কোনো ধ্রুবক c < 1 ও বড় n-এ a · f(n/b) ≤ c · f(n) হলে T(n) = Θ(f(n)).
তৃতীয় case-এর শেষ শর্তটির নাম regularity condition: প্রতিটি নিচের স্তরের মোট খরচ ওপরেরটির একটি নির্দিষ্ট ভগ্নাংশের বেশি নয়, অর্থাৎ স্তরগুলো অন্তত geometric হারে কমে। f(n) = nd হলে a(n/b)d = q · nd, তাই শর্তটি ঠিক q < 1, যা উপপাদ্য ৪.১-এ আপনিই মেটে। অদ্ভুত, দোলায়মান f-এ শর্তটি ভাঙতে পারে, তাই বক্তব্যে এটি রাখতে হয়। প্রমাণ উপপাদ্য ৪.১-এর মতোই স্তর-যোগে, শুধু অসমতাগুলো একটু জটিল; এখানে বাদ রাখছি।
চলক বদল। কিছু recurrence প্রথমে ছাঁচে পড়ে না, কিন্তু চলক বদলালে পড়ে। T(n) = 2T(√n) + lg n নিন, যা কিছু গাছ-ভিত্তিক তথ্য-কাঠামোয় আসে। m = lg n আর S(m) = T(2m) বসালে √n = 2m/2, তাই S(m) = 2S(m/2) + m: চেনা merge sort-এর ছাঁচ, S(m) = Θ(m lg m). ফিরে গেলে T(n) = Θ(lg n · lg lg n). T(2) = 1 নিলে n = 216-এ নির্ভুল মান S(16) = 16 · 4 + 16 = 80, যা recurrence সরাসরি চালিয়ে scratch-এ মেলানো। চলক বদলের algorithm-এর দিক DSA কোর্সের পর্ব ০৮-এ আছে।
অসমান ভাগ। T(n) = T(n/3) + T(2n/3) + n-এ দুটি উপ-সমস্যা দুই আকারের, তাই a আর b নেই। Median of medians-এর T(n) = T(n/5) + T(7n/10) + n-ও এমন। এদের জন্য Mohamad Akra আর Louay Bazzi ১৯৯৮ সালে প্রকাশিত একটি সাধারণ ফল ব্যবহার হয়, যার একটি পরিমার্জিত রূপ Tom Leighton-এর নোটে আছে বলে জানা যায়।
T(x) = g(x) + ∑i=1m aiT(bix), যেখানে ai > 0, 0 < bi < 1 ধ্রুবক, আর g বহুপদী হারে সীমিত (মোটামুটি: ধ্রুবক গুণে x বদলালে g-ও ধ্রুবক গুণের বেশি বদলায় না)। p হলো ∑ aibip = 1-এর একমাত্র বাস্তব সমাধান। তাহলে
T(x) = Θ( xp (1 + ∫1x g(u) / up+1 du) )
Floor-ceiling-এর মতো ছোট বিচ্যুতিও শর্তসাপেক্ষে চলে; পূর্ণ শর্ত মূল প্রবন্ধে।
প্রয়োগ। (১) T(n/3) + T(2n/3) + n: p = 1-এ 1/3 + 2/3 = 1, আর ∫ u/u2 du = ln x, তাই Θ(n log n). (২) T(n/5) + T(7n/10) + n: p = 1-এ যোগ 0.9 < 1, তাই p < 1 (হিসাবে p ≈ 0.84); ∫ u−p du ≈ x1−p/(1 − p), গুণ করলে Θ(n). Master theorem এর বিশেষ ক্ষেত্র: m = 1, a1 = a, b1 = 1/b হলে a · b−p = 1 থেকে p = logb a. নিচের ব্লক floor-সহ তিনটি recurrence নির্ভুল পূর্ণসংখ্যায় চালিয়ে অনুপাত দেখায়।
from functools import lru_cache
from math import log2
@lru_cache(maxsize=None)
def split(n): # T(n) = T(n//3) + T(2n//3) + n
return 1 if n < 3 else split(n // 3) + split(2 * n // 3) + n
@lru_cache(maxsize=None)
def mom(n): # T(n) = T(n//5) + T(7n//10) + n
return 1 if n < 5 else mom(n // 5) + mom(7 * n // 10) + n
@lru_cache(maxsize=None)
def nlogn(n): # T(n) = 2T(n/2) + n lg n, n = 2^k
return 1 if n == 1 else 2 * nlogn(n // 2) + n * (n.bit_length() - 1)
H = -(1/3) * log2(1/3) - (2/3) * log2(2/3) # entropy of the split
print("n split/(n lg n) mom/n nlogn/(n lg^2 n)")
for k in (10, 16, 22, 30, 40):
n = 2 ** k
print(f"2^{k:<7} {split(n) / (n * k):14.4f} {mom(n) / n:6.3f}"
f" {nlogn(n) / (n * k * k):10.4f}")
print(f"limits: 1/H = {1 / H:.4f}, 10, 1/2")আসল আউটপুট (ব্রাউজারের Python):
n split/(n lg n) mom/n nlogn/(n lg^2 n)
2^10 0.9688 6.019 0.5600
2^16 1.0157 7.944 0.5352
2^22 1.0359 8.941 0.5248
2^30 1.0530 9.565 0.5178
2^40 1.0614 9.856 0.5131
limits: 1/H = 1.0890, 10, 1/2
তিনটি অনুপাতই একটি ধ্রুবকের দিকে যাচ্ছে, তাই তিনটি Θ-উত্তর (n log n, n, n lg2 n) সংখ্যায় সমর্থিত। median of medians-এর ধ্রুবক 10 সহজে বোঝা যায়: স্তরগুলোর যোগ n, 0.9n, 0.81n, …, যার অসীম যোগফল n/(1 − 0.9) = 10n. শেষ কলামের নির্ভুল সূত্র (k + 1)/(2k) + 1/k2, যা উদাহরণ ৭.১ থেকে আসে। প্রথম কলামের সীমা 1/H ≈ 1.089, যেখানে H হলো ভাগ (1/3, 2/3)-এর entropy; এটি জানা একটি ফল, এখানে প্রমাণ করছি না। অভিসরণ ধীর: 240-এও 1.06. মনে রাখুন, সংখ্যা Θ প্রমাণ করে না; প্রমাণ আসে Akra–Bazzi বা recursion tree থেকে, সংখ্যা শুধু ভুল ধরতে সাহায্য করে।
৮ · Substitution method — ব্যর্থ অনুমান, শক্ত অনুমান আর পরীক্ষার অঙ্ক
Recursion tree উত্তর খুঁজে দেয়; সবচেয়ে কড়া প্রমাণ প্রায়ই আসে substitution method থেকে: একটি সীমা অনুমান করে induction দিয়ে প্রমাণ। এর একটি বিখ্যাত ফাঁদ আছে। অনুমান সঠিক Θ হলেও induction আটকে যেতে পারে, আর তখন সমাধান হলো অনুমানকে শক্ত করা: একটি ছোট মাত্রার পদ বিয়োগ করা। শুনতে উল্টো লাগে, কিন্তু শক্ত দাবি মানে induction-এ শক্ত অনুমানও পাওয়া যায়।
1অনুমান: T(n) ≤ c·n²24c(n/2)² + n = cn² + n3cn² + n ≤ cn²? কখনো না4নতুন: T(n) ≤ cn² − n54(cn²/4 − n/2) + n = cn² − n ✓6ভিত্তি: 1 ≤ c − 1 ⇒ c = 2
| অনুমান | |
| ধাপে পাওয়া | |
| ফল |
- অনুমান। a = 4, b = 2, d = 1 < log2 4 = 2, তাই case 1: T(n) = Θ(n2). ওপরের সীমা প্রমাণ করতে অনুমান করি T(m) ≤ cm2 সব ছোট ঘাত m-এ।
- Induction ধাপ। T(n) = 4T(n/2) + n ≤ 4c(n/2)2 + n = cn2 + n.
- আটকে গেল। আমাদের দরকার T(n) ≤ cn2, পেলাম cn2 + n. n ≥ 1-এ কোনো c-তেই cn2 + n ≤ cn2 নয়। “cn2 + n = O(n2), তাই হয়ে গেল” বলা চলবে না: induction-এ একই c ফেরত পেতে হয়।
- শক্ত অনুমান। সমস্যা শুধু একটি ছোট মাত্রার বাড়তি n. তাই দাবিটি শক্ত করি: T(m) ≤ cm2 − m. এটি আগের দাবির চেয়ে কড়া, কিন্তু induction-এ অনুমানও কড়া হয়।
- আবার ধাপ। T(n) ≤ 4(c(n/2)2 − n/2) + n = cn2 − 2n + n = cn2 − n. ঠিক যা চাই। বিয়োগ করা পদটি বাড়তি n-কে খেয়ে ফেলল।
- ভিত্তি। T(1) = 1 ≤ c · 1 − 1 মানে c ≥ 2. c = 2 নিলে T(n) ≤ 2n2 − n, আর আসলে সমতা: T(16) = 496 = 2 · 256 − 16. নিচের সীমা T(n) ≥ n2 একই ভাবে সহজে আসে, তাই Θ(n2).
যুক্তির ভুল ধরুন: “2T(n/2) + n = O(n)”
একজন লিখলেন: “দাবি: T(n) = 2T(n/2) + n, T(1) = 1 হলে T(n) = O(n). প্রমাণ: অনুমান T(m) ≤ cm. তাহলে T(n) ≤ 2c(n/2) + n = cn + n = (c + 1)n = O(n). ∎”
ভুল কোথায়: Induction-এ প্রমাণ করতে হয় একই c দিয়ে T(n) ≤ cn. পাওয়া গেল (c + 1)n: প্রতিটি স্তরে ধ্রুবক 1 করে বাড়ছে, আর lg n স্তরে তা c + lg n, যা ধ্রুবক নয়। আসল উত্তর T(n) = n lg n + n (§৩-এর stepper), যা O(n) নয়। এখানে শক্ত অনুমানেও লাভ নেই, কারণ দাবিটিই মিথ্যা। Substitution-এ “= O(…)” লিখে থামবেন না; সবসময় হুবহু অনুমানের রূপটি ফেরত আনুন।
অঙ্ক ১: নির্ভুল সূত্র আর Θ
T(1) = 1, T(n) = 8T(n/2) + n2, n = 2k. T(n)-এর বন্ধ সূত্র আর Θ বের করুন।
- q = 8/22 = 2 > 1, case 1: Θ(nlog2 8) = Θ(n3).
- নির্ভুল: T(n) = n3 + n2(2k − 1)/(2 − 1) = n3 + n3 − n2 = 2n3 − n2.
- যাচাই: T(2) = 8 + 4 = 12 = 16 − 4, T(16) = 7936 = 2 · 4096 − 256 (scratch-এ k ≤ 5 পর্যন্ত মেলানো)।
অঙ্ক ২: case 2 আর case 3 একসঙ্গে
(ক) T(n) = 4T(n/2) + n2; (খ) T(n) = T(n/2) + n; দুটিতেই T(1) = 1, n = 2k.
- (ক) q = 4/4 = 1, case 2: Θ(n2 lg n). প্রতিটি স্তরে n2, k স্তর আর পাতায় n2: T(n) = n2(lg n + 1); T(16) = 256 · 5 = 1280.
- (খ) q = 1/2, case 3: Θ(n). নির্ভুল: 1 + (n + n/2 + … + 2) = 2n − 1; T(32) = 63.
অঙ্ক ৩: ভিত্তি 3 আর 4
(ক) T(n) = 9T(n/3) + n; (খ) T(n) = 2T(n/4) + √n.
- (ক) log3 9 = 2 > 1, case 1: Θ(n2). T(1) = 1 হলে নির্ভুল (3n2 − n)/2; T(27) = 1080.
- (খ) log4 2 = 1/2 = d, case 2: Θ(√n log n). T(1) = 1 হলে নির্ভুল √n(log4 n + 1); T(256) = 16 · 5 = 80.
অঙ্ক ৪: দুই algorithm-এর তুলনা
বাজারঘরের গুদাম-দূরত্বের matrix গুণের জন্য একজন একটি নতুন পদ্ধতি প্রস্তাব করলেন: T'(n) = a · T'(n/4) + n2. সবচেয়ে বড় কোন পূর্ণসংখ্যা a-তে এটি Strassen-এর Θ(nlog2 7)-এর চেয়ে asymptotically দ্রুত হবে?
- a > 16 হলে log4 a > 2, case 1: T'(n) = Θ(nlog4 a). (a ≤ 16 হলে উত্তর Θ(n2) বা Θ(n2 log n), যা এমনিতেই দ্রুত।)
- দ্রুত হওয়ার শর্ত log4 a < log2 7. কিন্তু log4 a = (lg a)/2, তাই শর্তটি lg a < 2 lg 7 = lg 49, অর্থাৎ a < 49.
- উত্তর a = 48. a = 49-এ log4 49 = log2 7 হুবহু, তাই সমান হারে বাড়ে, দ্রুত নয়। এখানে ভিত্তি বদলের নিয়মটিই অঙ্কের চাবি।
আরও গভীরে: Θ-এর চেয়ে সূক্ষ্ম প্রশ্নও আছে: T(n)/(n lg n)-এর মতো অনুপাত কি একটি সীমায় যায়, নাকি চিরকাল দোলে? Floor-সহ merge sort-এ উত্তর হলো দোলে, আর সেই দোলন একটি পর্যায়ক্রমিক ফাংশন দিয়ে বর্ণনা করা যায়; Flajolet আর Golin-এর মতো গবেষকরা ১৯৯০-এর দশকে Mellin transform দিয়ে এমন নির্ভুল বিশ্লেষণ করেন বলে জানা যায়। আরেক দিকে, Akra–Bazzi-এর বিচ্যুতি-শর্ত (floor, ceiling, ছোট সরণ) Leighton পরিষ্কার করেন, আর এই ধারার ফলই আজ algorithm বিশ্লেষণের মানক হাতিয়ার।
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:
- T(n) = 2T(n/2) + n? Case 2, Θ(n log n); n = 2k, T(1) = 1-এ নির্ভুল n lg n + n.
- T(n) = 7T(n/2) + n2? log2 7 ≈ 2.81 > 2, case 1, Θ(n2.81): Strassen.
- T(n) = 2T(n/2) + n/log n-এ master theorem খাটে? না, n/log n polynomially ছোট নয়; উত্তর Θ(n log log n).
- Recursion tree-তে পাতা কয়টি? alogb n = nlogb a.
- Substitution-এ cn2 + n এলে কী করবেন? অনুমান থেকে ছোট পদ বিয়োগ করুন: cn2 − n.
৯ · AI-এর যুগে — master theorem-এর উত্তর যাচাই
“T(n) = …-এর সমাধান চাই” লিখলে AI সহকারী সাধারণত এক লাইনে উত্তর দেয়, আর বেশিরভাগ সময় ঠিক। ভুলগুলো লুকিয়ে থাকে ঠিক সেই জায়গায়, যেখানে এই পর্বের সূক্ষ্মতা: সূচকে log-এর ভিত্তি, polynomially ছোট-বড়, regularity, অসমান ভাগ। এই বিষয়ে কয়েকটি ভুল নিয়মিত দেখা যায়:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| logb a-র জায়গায় loga b বা lg a বসানো (যেমন 9T(n/3)-এ nlog2 9) | লেমা ৩.১: পাতা ak, k = logb n; n = 81-এ হাতে গুনুন |
| n/log n বা n log n চালিকায় সরল তিন case জোর করে খাটানো | “polynomially ছোট” শর্তটি জিজ্ঞেস করুন; স্তর-যোগ নিজে লিখুন |
| T(n/2) + T(n/4) + n-কে a = 2, b = 2 ধরে Θ(n log n) বলা | অসমান ভাগে Akra–Bazzi; স্তরগুলো n, 3n/4, 9n/16 … কমে, উত্তর Θ(n) |
| Substitution-এ “cn + n = O(n)” লিখে induction শেষ ঘোষণা | একই ধ্রুবক c কি ফেরত এল? না এলে প্রমাণ অসম্পূর্ণ |
| Case 3-এ regularity শর্ত বাদ দেওয়া, বা master theorem-কে T(n − 1)-এর recurrence-এ খাটানো | উপপাদ্যের সব শর্ত লিখিয়ে নিন; T(n) = 2T(n − 1) + 1, T(1) = 1-এর উত্তর 2n − 1, কোনো case নয় |
| Karatsuba বা Strassen-এর সাল, বা matrix গুণের সূচকের সর্বশেষ মান বানিয়ে বলা | বিশ্বস্ত বই বা মূল প্রবন্ধে মেলান; অনিশ্চিত তথ্য “প্রায়” বলা হয়েছে কি না দেখুন |
AI-এর কাছে recurrence-এর সমাধান চাইলে এমন কাঠামো দিন, যাতে প্রতিটি ধাপ আলাদা করে যাচাই করা যায়:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
এই পর্বের প্রায় সব উত্তর একটি প্রশ্নে নেমে আসে: recursion tree-র স্তরগুলো বাড়ে, সমান থাকে, না কমে? AI-এর উত্তর পড়ার সময় নিজে প্রথম তিন স্তরের যোগ লিখে দেখুন। অনুপাত q চোখে পড়লেই case-টি নিজে বলতে পারবেন, আর ভুল উত্তর সাধারণত সেখানেই ধরা পড়ে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Karatsuba কীভাবে “গুণে n² লাগবেই” ধারণাটি ভাঙলেন? তারপর বড় সংখ্যার গুণ কতদূর এগিয়েছে?
সাধারণভাবে প্রচলিত বিবরণ অনুযায়ী, ১৯৬০ সালে মস্কো বিশ্ববিদ্যালয়ে Andrey Kolmogorov গণনার জটিলতা নিয়ে একটি সেমিনার চালাতেন, আর সেখানে অনুমান করেন যে দুটি n-অঙ্কের সংখ্যা গুণে মোটামুটি n2 ধাপ লাগবেই। সেমিনারের তরুণ ছাত্র Anatoly Karatsuba অল্প কয়েক দিনের মধ্যে এই পর্বের লেমা ৬.১-এর অভেদ ব্যবহার করে তিনটি অর্ধেক-আকারের গুণের পদ্ধতি দেখান, যার recurrence T(n) = 3T(n/2) + Θ(n), সমাধান Θ(nlog2 3) ≈ Θ(n1.585). Karatsuba-র পরবর্তী নিজের বিবরণ অনুযায়ী, Kolmogorov নিজে ফলটি লিখে ১৯৬২ সালে Karatsuba আর Yuri Ofman-এর নামে প্রকাশ করান, আর Karatsuba প্রকাশের কথা পরে জানতে পারেন। খুঁটিনাটি নিয়ে বিভিন্ন উৎসে সামান্য ভিন্নতা আছে, তবে মূল ঘটনাটি ব্যাপকভাবে স্বীকৃত।
এই ফলের গুরুত্ব শুধু গতিতে নয়, মনোভাবে। একটি স্বাভাবিক মনে হওয়া নিচের সীমা মিথ্যা প্রমাণ হলো, আর তা হলো একটি সরল বীজগাণিতিক কৌশলে। এরপর একের পর এক উন্নতি আসে। Toom আর Cook-এর পদ্ধতি সংখ্যাকে আরও বেশি খণ্ডে ভেঙে সূচক আরও কমায়; দুই খণ্ডে ভাঙলে সেটিই Karatsuba-র পদ্ধতি। Schönhage আর Strassen ১৯৭১ সালে Fast Fourier Transform ব্যবহার করে প্রায় n log n log log n সময় দেখান। Martin Fürer ২০০৭ সালে log log n গুণকটি আরও কমান। শেষে David Harvey আর Joris van der Hoeven ২০১৯ সালে একটি O(n log n) পদ্ধতি দেন বলে জানা যায়, যা অনেকের ধারণায় সম্ভাব্য সেরা; তবে এর ধ্রুবক এত বিশাল যে বাস্তবে এটি ব্যবহারযোগ্য নয়।
বাস্তবের বড়-সংখ্যার লাইব্রেরি তাই আকার দেখে পদ্ধতি বদলায়: ছোট সংখ্যায় schoolbook, মাঝারিতে Karatsuba বা Toom-Cook, খুব বড়তে FFT-ভিত্তিক পদ্ধতি। এই পর্বের ব্লকে দেখেছেন, Karatsuba-র লাভ (3/4)k হারে বাড়ে; কিন্তু প্রতিটি ধাপে কিছু বাড়তি যোগ-বিয়োগ আছে, তাই ছোট n-এ সরল পদ্ধতিই দ্রুত। কোন আকারে বদল ভালো, তা মেপে ঠিক করা হয়, তত্ত্বে নয়। Python-এর নিজস্ব বড় পূর্ণসংখ্যার গুণও বড় সংখ্যায় Karatsuba ব্যবহার করে বলে জানা যায়।
মূল উপলব্ধি: Divide and conquer-এ আসল লাভ আসে উপ-সমস্যার সংখ্যা a কমানো থেকে; একটি গুণ কম মানে সূচক 2 থেকে 1.585, আর তা একটি “স্বাভাবিক” অনুমানকেও ভেঙে দিতে পারে।
প্র ০২Strassen-এর ৭টি গুণের পর matrix গুণের সূচক কতদূর নেমেছে? এই “সূচকের দৌড়” বাস্তবে কতটা কাজের?
Volker Strassen ১৯৬৯ সালে “Gaussian elimination is not optimal” শিরোনামের একটি ছোট প্রবন্ধে দেখান, দুটি 2 × 2 block matrix ৮টির বদলে ৭টি block-গুণে গুণ করা যায়। এই পর্বের হিসাবে তার অর্থ T(n) = 7T(n/2) + Θ(n2) = Θ(nlog2 7) ≈ Θ(n2.807). প্রবন্ধের শিরোনামটিও তাৎপর্যপূর্ণ: matrix inverse আর linear system সমাধানও matrix গুণের মতো দ্রুত করা যায়, তাই সাধারণ পদ্ধতির n3 শেষ কথা নয়।
এরপর গবেষকরা একটি সংখ্যা ω-কে নিয়ে কাজ শুরু করেন: সবচেয়ে ছোট সূচক, যাতে n × n matrix প্রায় nω সময়ে গুণ করা যায়। সহজ নিচের সীমা ω ≥ 2, কারণ ফলের n2টি ঘর লিখতেই হয়। Coppersmith আর Winograd ১৯৯০ সালের কাছাকাছি প্রায় 2.376-এ নামান, আর গত দেড় দশকে কয়েকটি দল একের পর এক ছোট উন্নতি করে সূচকটিকে প্রায় 2.37-এর কাছে এনেছেন। সাম্প্রতিক মানটি ঘন ঘন বদলায়, তাই নির্দিষ্ট দশমিক মান উদ্ধৃত করার আগে সর্বশেষ প্রবন্ধ দেখে নেওয়া উচিত। অনেক গবেষক মনে করেন ω = 2 হতে পারে, কিন্তু তা অপ্রমাণিত।
বাস্তবে গল্পটি অন্য রকম। Coppersmith–Winograd ধারার পদ্ধতিগুলোর ধ্রুবক এত বিশাল যে কোনো বাস্তব আকারে এরা সাধারণ পদ্ধতির চেয়ে ধীর; এদের “galactic algorithm” বলা হয়। Strassen নিজে কিছু বড় matrix-এ ব্যবহারযোগ্য, তবে float-এর সংখ্যাগত স্থিতিশীলতা কিছুটা কমে, আর অনেক লাইব্রেরি তাই cache-বান্ধব সাধারণ n3 পদ্ধতিই পছন্দ করে। এই পর্বের master theorem এখানে একটি সীমাও দেখায়: Θ শুধু বৃদ্ধির হার বলে, ধ্রুবক বা memory-র আচরণ বলে না।
তবু তত্ত্বের মূল্য বিশাল। গ্রাফের অনেক সমস্যা, যেমন ত্রিভুজ খোঁজা বা transitive closure, matrix গুণে রূপান্তর করা যায়, তাই ω কমলে সেগুলোর তাত্ত্বিক সীমাও কমে।
মূল উপলব্ধি: Strassen দেখান n3 অনিবার্য নয়; তারপরের দৌড় ω-কে প্রায় 2.37-এ নামিয়েছে, কিন্তু বাস্তবের দ্রুততা ধ্রুবক আর memory-ও ঠিক করে, শুধু সূচক নয়।
প্র ০৩“Master theorem” নামটি কোথা থেকে এল? সরল তিন case-এর বাইরে গবেষকরা কেন Akra–Bazzi পর্যন্ত গেলেন?
Divide-and-conquer recurrence-এর এই ধরনের সাধারণ সমাধান সাধারণত Jon Bentley, Dorothea Haken আর James Saxe-এর ১৯৮০ সালের একটি প্রবন্ধের সঙ্গে যুক্ত করা হয়, যার শিরোনাম মোটামুটি “divide-and-conquer recurrence সমাধানের একটি সাধারণ পদ্ধতি”. সেখানে তাঁরা T(n) = aT(n/b) + f(n)-এর একটি সাধারণ ফল দেন। “Master method” নামটি জনপ্রিয় হয় Cormen, Leiserson, Rivest (পরে Stein-সহ)-এর বিখ্যাত পাঠ্যবই থেকে, যার প্রথম সংস্করণ ১৯৯০ সালে। এই বইয়ের তিন case-এর ভাষা, polynomially ছোট-বড় আর regularity শর্তসহ, আজ বিশ্বজুড়ে প্রায় মানক হয়ে গেছে।
কিন্তু এই তিন case-এর দুটি সীমাবদ্ধতা আছে, যা এই পর্বে দেখেছেন। প্রথমত, case-গুলোর মাঝে ফাঁক: f(n) = n/log n-এর মতো ফাংশন কোনো case-এ পড়ে না। কিছু বই বর্ধিত case 2 দিয়ে log-এর ঘাতের ফাঁক কিছুটা ঢাকে। দ্বিতীয়ত, সব ভাগ সমান হতে হয়। বাস্তবের algorithm-এ ভাগ প্রায়ই অসমান: median of medians-এ n/5 আর 7n/10, randomized algorithm-এর বিশ্লেষণে আরও অনেক রূপ।
Mohamad Akra আর Louay Bazzi ১৯৯৮ সালে একটি জার্নাল প্রবন্ধে দেখান, ∑ aibip = 1-এর সমাধান p আর একটি সমাকলন দিয়ে প্রায় সব এমন recurrence-এর Θ-উত্তর পাওয়া যায়। এর আগে বা কাছাকাছি সময়ে MIT-র Tom Leighton একটি নোটে এই ফলের একটি পরিষ্কার রূপ, floor-ceiling-এর মতো ছোট বিচ্যুতিসহ, প্রমাণ করেন বলে জানা যায়। উল্লেখযোগ্য যে Akra–Bazzi-তে case ভাগ নেই: একটি সূত্রই সব কিছু ঢাকে, আর master theorem-এর তিন case সমাকলনটির তিন রকম আচরণ থেকে আপনিই বেরিয়ে আসে।
শিক্ষার দিক থেকে একটি প্রশ্ন ওঠে: শিক্ষার্থী কি শুধু তিন case মুখস্থ করবেন? এই পর্বের অবস্থান হলো, case-গুলো একটি geometric series-এর তিন আচরণ। Recursion tree আর স্তর-যোগ বুঝলে নতুন যেকোনো recurrence-এ নিজেই সঠিক উত্তরে পৌঁছানো যায়, এমনকি সূত্র ভুলে গেলেও।
মূল উপলব্ধি: Master theorem একটি সুবিধাজনক সারাংশ, Akra–Bazzi তার সাধারণীকরণ; কিন্তু দুটির পেছনের আসল যন্ত্র হলো স্তর ধরে যোগ, যা কখনো পুরোনো হয় না।
প্র ০৪Floor আর ceiling Θ বদলায় না, তবু নির্ভুল তুলনা-সংখ্যায় তারা কেন গুরুত্বপূর্ণ? “ধরি n = 2k” কখন বিপজ্জনক?
Merge sort-এর সবচেয়ে খারাপ ক্ষেত্রের তুলনা-সংখ্যা M(n) = n⌈lg n⌉ − 2⌈lg n⌉ + 1 (পর্ব ১১)। n = 2k-এ এটি n lg n − n + 1. কিন্তু n = 2k + 1-এ ⌈lg n⌉ এক লাফে বেড়ে যায়, আর M(n)/(n lg n) অনুপাতটি k বাড়ার সঙ্গে স্থির হয় না, বরং প্রতিটি দুইয়ের ঘাতের মাঝে একটি একই রকম ঢেউ আঁকে। Θ-এর দিক থেকে কোনো সমস্যা নেই, কারণ অনুপাত দুটি ধনাত্মক ধ্রুবকের মাঝে আটকে থাকে, যা উপপাদ্য ২.২-এর প্রমাণও দেখায়। কিন্তু কেউ যদি জানতে চান ১০০০টি অর্ডার সাজাতে সবচেয়ে খারাপ ক্ষেত্রে ঠিক কয়টি তুলনা লাগে, তখন floor-ceiling ছাড়া উত্তর ভুল হবে; সঠিক উত্তর ৮৯৭৭ (পর্ব ১১-এ যাচাই করা)।
এই নির্ভুল সংখ্যার একটি সুন্দর প্রয়োগ আছে। n-টি আলাদা সংখ্যা সাজানোর যেকোনো তুলনা-ভিত্তিক পদ্ধতিকে একটি decision tree ভাবা যায়, যার প্রতিটি পাতা একটি বিন্যাস; n!-টি পাতার binary tree-র উচ্চতা অন্তত ⌈lg(n!)⌉. n = 1000-এ lg(1000!) ≈ 8529.4, তাই যেকোনো পদ্ধতিকে সবচেয়ে খারাপ ক্ষেত্রে অন্তত ৮৫৩০টি তুলনা করতে হয়। Merge sort-এর ৮৯৭৭ এই সীমার মাত্র প্রায় ৫% ওপরে। Θ-এর ভাষায় দুটিই n lg n, কিন্তু নির্ভুল হিসাব দেখায় merge sort কতটা কাছে, আর Ford আর Johnson-এর ১৯৫৯ সালের merge insertion-এর মতো পদ্ধতি এই ফাঁক আরও কমায় বলে জানা যায়।
“ধরি n = 2k” বিপজ্জনক হয় দুই ক্ষেত্রে, যা উপপাদ্য ২.২-এর শর্ত থেকেই আসে। এক, উত্তর মসৃণ না হলে: যেমন কোনো recurrence-এর উত্তর 2n-এর মতো হলে, ঘাতগুলোর মাঝে মান বহু গুণ লাফাতে পারে। দুই, T monotone না হলে: এমন algorithm কল্পনা করুন, যা দুইয়ের ঘাতে বিশেষ সুবিধা পায় (যেমন কিছু FFT বাস্তবায়ন), অন্য আকারে padding করে। সেখানে শুধু ঘাত দেখে খরচ বললে মাঝের আকারের খরচ কম দেখাবে।
মূল উপলব্ধি: Floor-ceiling বৃদ্ধির হার বদলায় না, কিন্তু নির্ভুল সংখ্যা বদলায়; আর n = 2k ধরার অধিকার monotonicity আর smoothness থেকে আসে, বিনা মূল্যে নয়।
প্র ০৫Recurrence-এর সমাধান শুধু ওপরের সীমা দেয়; binary search-এর log n আর merge sort-এর n log n কি আরও ভালো করা সম্ভব?
Master theorem একটি নির্দিষ্ট algorithm-এর খরচ বলে, সমস্যাটির নয়। Binary search-এর T(n) = T(n/2) + 1 = Θ(log n) জানার পরও প্রশ্ন থাকে, অন্য কোনো পদ্ধতি কি কম তুলনায় সাজানো তালিকায় খুঁজতে পারে? উত্তর আসে একটি গণনার যুক্তি থেকে। প্রতিটি তুলনার তিনটি ফল (ছোট, সমান, বড়), আর n-টি অবস্থানের যেকোনো একটিতে উত্তর থাকতে পারে, সঙ্গে “নেই”. তাই সম্ভাব্য উত্তর n + 1-টি, আর তুলনার একটি decision tree-তে পাতা অন্তত n + 1. সাধারণ বিশ্লেষণে দেখানো যায় সবচেয়ে খারাপ ক্ষেত্রে ⌈lg(n + 1)⌉টির কম তুলনায় চলে না, আর binary search ঠিক ⌊lg n⌋ + 1 = ⌈lg(n + 1)⌉টি করে। অর্থাৎ এটি তুলনা-ভিত্তিক মডেলে সর্বোত্তম।
সাজানোর ক্ষেত্রেও একই। প্র ০৪-এ দেখেছেন, যেকোনো তুলনা-ভিত্তিক sort-এর সবচেয়ে খারাপ ক্ষেত্রে অন্তত ⌈lg(n!)⌉ তুলনা, আর lg(n!) = Θ(n log n) (Stirling-এর সূত্র থেকে, বা n! ≥ (n/2)n/2 থেকে সহজে)। তাই merge sort-এর recurrence-এর সমাধান Θ(n log n) শুধু একটি algorithm-এর খরচ নয়, এই মডেলে সমস্যাটির আসল জটিলতা। এখানে দুই দিক মিলে গেল: ওপরের সীমা recurrence থেকে, নিচের সীমা গণনা (pigeonhole-এর মতো যুক্তি) থেকে।
কিন্তু মডেল বদলালে সীমাও বদলায়। তালিকার সংখ্যাগুলো ছোট পূর্ণসংখ্যা হলে counting sort বা radix sort তুলনা ছাড়াই প্রায় linear সময়ে সাজায়, কারণ তারা মানকে সরাসরি সূচক হিসেবে ব্যবহার করে। Hash table গড়ে ধ্রুবক সময়ে খোঁজে। এগুলো lg(n!) সীমা ভাঙে না; শুধু তুলনার মডেলের বাইরে চলে যায়। তাই কোনো “নিচের সীমা” উদ্ধৃত করার সময় কোন গণনার মডেলে, তা বলা জরুরি। Algorithm-এর দিকটি DSA কোর্সে বিস্তারিত, আর পরে একটি Theory of Computation কোর্সে গণনার মডেল নিয়ে আরও গভীরে আলোচনা আসবে।
মূল উপলব্ধি: Recurrence একটি algorithm-এর ওপরের সীমা দেয়; সমস্যাটির নিচের সীমা আসে গণনার যুক্তি থেকে, আর দুটি মিললে তবেই বলা যায় algorithm-টি সর্বোত্তম।
অনুশীলন
আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।
-
প্রমাণ: T(1) = 1 আর n = 2k (k ≥ 1)-এ T(n) = 2T(n/2) + n lg n হলে induction দিয়ে প্রমাণ করুন T(2k) = 2k(k(k + 1)/2 + 1). তারপর দেখান T(n) = Θ(n lg2 n).
- ভিত্তি k = 0: 20(0 + 1) = 1 = T(1). — সরাসরি
- অনুমান: T(2k) = 2k(k(k + 1)/2 + 1). — induction
- ধাপ: T(2k+1) = 2T(2k) + 2k+1(k + 1) = 2k+1(k(k + 1)/2 + 1 + (k + 1)) = 2k+1((k + 1)(k + 2)/2 + 1). — recurrence, k(k+1)/2 + (k+1) = (k+1)(k+2)/2
- Θ: n = 2k-এ T(n) = n(lg2 n + lg n)/2 + n. n ≥ 2-এ (1/2) n lg2 n ≤ T(n) ≤ 2n lg2 n, কারণ lg n ≥ 1 হলে lg n ≤ lg2 n আর n ≤ n lg2 n. ∎ — সংজ্ঞা ১.২
T monotone আর n lg2 n মসৃণ হলে উপপাদ্য ২.২ এটিকে সব n-এ ছড়ায়। Scratch-এ k = 4 আর k = 10-এ সূত্রটি (176 আর 57344) recurrence চালিয়ে মেলানো।
-
হিসাব: Θ-উত্তর দিন, আর কোন case তা বলুন। (ক) T(n) = 16T(n/4) + n3; (খ) T(n) = 2T(n/2) + 1; (গ) T(n) = 4T(n/2) + n, আর T(1) = 1 হলে T(16)-এর নির্ভুল মান; (ঘ) T(n) = 9T(n/3) + f(n)-এর recursion tree-তে n = 81 হলে পাতা কয়টি?
(ক) log4 16 = 2 < 3, case 3: Θ(n3); regularity: 16(n/4)3 = n3/4, c = 1/4 < 1.
(খ) log2 2 = 1 > 0, case 1: Θ(n). (T(1) = 1 হলে নির্ভুল 2n − 1.)
(গ) log2 4 = 2 > 1, case 1: Θ(n2). নির্ভুল সূত্র 2n2 − n (§৮-এর stepper), তাই T(16) = 512 − 16 = 496.
(ঘ) 9log3 81 = 94 = 6561 = 812. সবগুলো scratch-এ recurrence চালিয়ে মেলানো।
-
আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। তারপর যেকোনো Python-এ চালিয়ে মেলান।
def T(n): return 1 if n == 1 else 3 * T(n // 2) + n print([T(2 ** k) for k in range(5)]) print(T(1024), 3 ** 10, T(1024) // 3 ** 10)[1, 5, 19, 65, 211] 175099 59049 2T(2k) = 3k+1 − 2k+1: k = 0…4-এ 1, 5, 19, 65, 211. T(1024) = 311 − 211 = 175099, আর nlg 3 = 310 = 59049.
//পূর্ণসংখ্যার ভাগ, তাই অনুপাত 2.965-এর পূর্ণ অংশ 2 ছাপা হয়, 3 নয়: অনুপাতটি 3-এর দিকে যায়, কিন্তু নিচ থেকে। আউটপুট CPython-এ চালিয়ে মেলানো। -
যুক্তির ভুল ধরুন: একজন লিখলেন: “T(n) = T(n/2) + T(n/4) + n. এখানে মোট দুটি উপ-সমস্যা, আর প্রতিটির আকার n/2 বা তার কম; তাই a = 2, b = 2, f(n) = n, master theorem-এর case 2 অনুযায়ী T(n) = Θ(n log n).” ভুলটি কোথায়, আর সঠিক উত্তর কী?
- ভুল ধাপ: Master theorem-এর সব উপ-সমস্যার আকার সমান n/b হতে হয়। “n/2 বা তার কম” ধরে a = 2, b = 2 বসালে T(n) ≤ 2T(n/2) + n থেকে শুধু একটি ওপরের সীমা O(n log n) পাওয়া যায় (T monotone হলে), Θ নয়।
- সঠিক উত্তর, recursion tree দিয়ে: স্তর j-এর যোগ ≤ (3/4)jn, কারণ প্রতিটি node তার আকারের 1/2 + 1/4 = 3/4 অংশ সন্তানদের দেয়। তাই T(n) ≤ n/(1 − 3/4) + পাতা = O(n), আর মূলের খরচ n থেকে T(n) ≥ n. ফলে Θ(n).
- Akra–Bazzi দিয়ে: (1/2)p + (1/4)p = 1-এ p ≈ 0.694 < 1, তাই Θ(n). Scratch-এ floor-সহ চালিয়ে T(n)/n অনুপাত n = 240-এ ≈ 3.999, সীমা 1/(1 − 3/4) = 4-এর দিকে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমT(n) = aT(n/b) + Θ(nd)-এর master theorem প্রমাণের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- n = bk ধরে নেওয়া (পরে monotonicity দিয়ে সব n-এ ছড়ানো হবে)
- Recurrence খুলে লেখা: T(n) = akT(1) + ∑ ajf(n/bj)
- স্তরের যোগকে ndqj আকারে লেখা, যেখানে q = a/bd
- Geometric series-এ q < 1, q = 1, q > 1 আলাদা করে যোগফল সীমিত করা
- পাতার খরচ ak = nlogb a যোগ করে তিন case-এর Θ ঘোষণা
T(n) = 3T(n/2) + n-এর সমাধান কী?
- Θ(n log n)
- Θ(nlog2 3)
- Θ(n2)
- Θ(n)
T(n) = 2T(n/2) + n, n = 2k-এর recursion tree-তে স্তর j (0 ≤ j < k)-এর সব node-এর মোট নিজস্ব খরচ কত?
- n
- n/2j
- 2j
- n lg n
Strassen-এর matrix গুণের recurrence T(n) = 7T(n/2) + Θ(n2). এর সমাধান কী?
- Θ(n3)
- Θ(n2 log n)
- Θ(nlog2 7) ≈ Θ(n2.81)
- Θ(n2)
নিচের কোন recurrence-এ সরল master theorem-এর (তিন case, বর্ধিত রূপ ছাড়া) কোনো case খাটে না?
- T(n) = T(n/2) + 1
- T(n) = 9T(n/3) + n
- T(n) = T(n/2) + n
- T(n) = 2T(n/2) + n/lg n
T(n) = 4T(n/2) + n-এর জন্য substitution method-এ অনুমান T(n) ≤ cn2 নিলে induction ধাপে cn2 + n আসে। কোন নতুন অনুমানে induction সফল হয়?
- T(n) ≤ cn2 + n
- T(n) ≤ cn2 − n
- T(n) ≤ cn3
- একই অনুমান, শুধু c আরও বড় নিয়ে
T(n) = 9T(n/3) + f(n)-এর recursion tree-তে n = 81 হলে পাতা কয়টি?
- 81
- 729
- 6561
- 324
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ছাঁচ: T(n) = aT(n/b) + f(n); a = উপ-সমস্যা, n/b = আকার, f = ভাঙা-জোড়ার খরচ। Θ-তে log-এর ভিত্তি গুরুত্বহীন, সূচকে গুরুত্বপূর্ণ।
- n = bk: T monotone আর উত্তর মসৃণ হলে ঘাতের Θ সব n-এ খাটে; floor-ceiling নির্ভুল সংখ্যা বদলায়, বৃদ্ধির হার নয়।
- Recursion tree: T(n) = akT(1) + ∑ ajf(n/bj), পাতা nlogb a; f = nd হলে স্তরগুলো অনুপাত q = a/bd-এর geometric series.
- Master theorem: q > 1 ⇒ Θ(nlogb a), q = 1 ⇒ Θ(nd log n), q < 1 ⇒ Θ(nd); Karatsuba n1.585, Strassen n2.807.
- ছাঁচের বাইরে: n log n চালিকা ⇒ n log2 n, n/log n ⇒ n log log n, অসমান ভাগে Akra–Bazzi; substitution আটকালে ছোট পদ বিয়োগ করে অনুমান শক্ত করুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৮ · Generating function — গণনাকে বীজগণিতে রূপান্তরপরবর্তী পর্বSequence-কে power series-এ বেঁধে recurrence আর গণনা।
- পর্ব ১১ · Strong induction আর well-orderingপুনরালোচনাBinary search আর merge sort-এর floor-সহ নির্ভুল সূত্রের প্রমাণ।
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল।
- DSA · পর্ব ০৯ · Master theorem — divide and conquer-এর হিসাব এক লাইনেকোডে প্রয়োগআটটি recurrence-এ case বাছাই আর একটি calculator.
- DSA · পর্ব ০৮ · Recurrence সমাধান — substitution আর recursion treeকোডে প্রয়োগকোড দেখে recurrence লেখা আর চলক বদল।
- DSA · পর্ব ৪৯ · Divide and conquer — Strassen, closest pair আর Karatsubaকোডে প্রয়োগতিনটি algorithm-এর পূর্ণ বাস্তবায়ন।