স্ট্রাসেনের গুণন ও মিডিয়ান ফাইন্ডিং অ্যালগরিদম
এই পাঠে যা শিখবেন
- স্ট্রাসেনের ৭-গুণন কৌশলের সুনির্দিষ্ট সূত্রগুলো এবং কেন সেগুলো সঠিক তা বুঝতে পারা
- রিকারেন্স $T(n) = 7T(n/2) + O(n^2)$ থেকে $\Theta(n^{\log_2 7})$ মাস্টার থিওরেম দিয়ে ডেরাইভ করতে পারা
- মিডিয়ান-অফ-মিডিয়ানসের মূল ধারণা (worst-case $O(n)$) এবং কেন সহজ quickselect তার চেয়ে ব্যবহারিক বিকল্প তা বুঝতে পারা
- জেনুইন কোড দিয়ে স্ট্রাসেনের ম্যাট্রিক্স গুণন ও quickselect-ভিত্তিক মিডিয়ান — দুটোই সঠিক তা নিখুঁতভাবে যাচাই করতে পারা
১ · সমস্যা — বড় ম্যাট্রিক্স গুণন
দুটি $n \times n$ ম্যাট্রিক্স $A$ ও $B$ গুণ করে $C = AB$ বের করার নেইভ পদ্ধতি হলো প্রতিটি $c_{ij} = \sum_{k} a_{ik} b_{kj}$ সরাসরি হিসাব করা — তিনটি নেস্টেড লুপ, $\Theta(n^3)$ সময়। প্রশ্ন হলো: D&C দিয়ে কি এর চেয়ে ভালো করা সম্ভব?
সরল ব্লক-ভিত্তিক D&C পদ্ধতি চেষ্টা করলে: $A$ ও $B$-কে চারটি $n/2 \times n/2$ ব্লকে ভাগ করে ($A_{11}, A_{12}, A_{21}, A_{22}$ এবং $B_{11}, B_{12}, B_{21}, B_{22}$), সাধারণ ম্যাট্রিক্স গুণনের নিয়মে:
$$C_{11} = A_{11}B_{11} + A_{12}B_{21}, \quad C_{12} = A_{11}B_{12} + A_{12}B_{22}$$ $$C_{21} = A_{21}B_{11} + A_{22}B_{21}, \quad C_{22} = A_{21}B_{12} + A_{22}B_{22}$$এখানে মোট $8$টি ব্লক-গুণন লাগে (প্রতিটি $C_{ij}$-এ ২টি করে) এবং কিছু $O(n^2)$ যোগ। রিকারেন্স $T(n) = 8T(n/2) + O(n^2)$ — কিন্তু $n^{\log_2 8} = n^3$, তাই মাস্টার থিওরেমের কেস ১ প্রয়োগ করলে $T(n) = \Theta(n^3)$ — নেইভ পদ্ধতির চেয়ে কোনো লাভ নেই! এখানেই স্ট্রাসেনের অন্তর্দৃষ্টি কাজে আসে।
২ · স্ট্রাসেনের মূল অন্তর্দৃষ্টি — ৮টির বদলে ৭টি গুণন
ম্যাট্রিক্স গুণন ($O(n^3)$-ঘেঁষা, ব্যয়বহুল) আর যোগ/বিয়োগ ($O(n^2)$, সস্তা) — এই দুইয়ের খরচে বিশাল ফারাক। স্ট্রাসেন দেখান যে সাতটি চতুরভাবে নির্মিত গুণফল দিয়েই (কিছু অতিরিক্ত যোগ-বিয়োগের বিনিময়ে) সবগুলো $C_{ij}$ ব্লক পুনর্গঠন করা সম্ভব — অষ্টম গুণনটি সম্পূর্ণ বাদ দেওয়া যায়:
$$M_1 = (A_{11}+A_{22})(B_{11}+B_{22}) \qquad M_2 = (A_{21}+A_{22})\,B_{11}$$ $$M_3 = A_{11}\,(B_{12}-B_{22}) \qquad\qquad\ \ M_4 = A_{22}\,(B_{21}-B_{11})$$ $$M_5 = (A_{11}+A_{12})\,B_{22} \qquad\qquad\ \ M_6 = (A_{21}-A_{11})(B_{11}+B_{12})$$ $$M_7 = (A_{12}-A_{22})(B_{21}+B_{22})$$এবং চূড়ান্ত ব্লকগুলো:
$$C_{11} = M_1 + M_4 - M_5 + M_7 \qquad C_{12} = M_3 + M_5$$ $$C_{21} = M_2 + M_4 \qquad\qquad\quad\ \, C_{22} = M_1 - M_2 + M_3 + M_6$$এই সূত্রগুলো সরাসরি বীজগণিতের মাধ্যমে যাচাই করা যায় — উদাহরণস্বরূপ, $M_1 + M_4 - M_5 + M_7$ খুলে সমস্ত পদ বিস্তার করলে দেখা যায় প্রায় সব ক্রস-টার্ম একে অপরকে কেটে দেয়, শুধু $A_{11}B_{11} + A_{12}B_{21}$ অবশিষ্ট থাকে — যা ঠিক $C_{11}$-এর সংজ্ঞা। নিচের কোড সেলে এই সূত্রগুলো হুবহু ইমপ্লিমেন্ট করে নেইভ গুণনের সাথে মিলিয়ে এই দাবিটি সরাসরি যাচাই করা হচ্ছে — শুধু বিশ্বাস করে নয়।
৩ · রিকারেন্স ডেরিভেশন — $\Theta(n^{\log_2 7})$
প্রতিটি $M_i$ একটি $n/2 \times n/2$ ম্যাট্রিক্স গুণন (রিকার্সিভ কল), এবং যোগ/বিয়োগ/কমবাইন মিলিয়ে $O(n^2)$ অতিরিক্ত কাজ। তাই রিকারেন্স:
$$T(n) = 7\,T(n/2) + O(n^2)$$মাস্টার থিওরেম প্রয়োগ করতে ($a=7$, $b=2$, $f(n) = \Theta(n^2)$): প্রথমে $n^{\log_b a}$ হিসাব করা হয়:
$$n^{\log_2 7} \approx n^{2.807}$$যেহেতু $f(n) = \Theta(n^2)$ এবং $2 < \log_2 7 \approx 2.807$, তাই $f(n) = O(n^{\log_2 7 - \epsilon})$ কোনো $\epsilon > 0$-এর জন্য (এখানে $\epsilon \approx 0.807$) — এটি মাস্টার থিওরেমের কেস ১ (L13 দ্রষ্টব্য)। কেস ১ বলে:
$$T(n) = \Theta\!\left(n^{\log_b a}\right) = \Theta\!\left(n^{\log_2 7}\right) \approx \Theta(n^{2.807})$$অর্থাৎ স্ট্রাসেনের অ্যালগরিদম $\Theta(n^{2.807})$ সময়ে চলে — নেইভ $\Theta(n^3)$-এর চেয়ে অ্যাসিম্পটোটিকভাবে দ্রুত (যদিও ব্যবহারিকভাবে ছোট ম্যাট্রিক্সে কনস্ট্যান্ট ফ্যাক্টর ও অতিরিক্ত মেমরি-ব্যবহারের কারণে নেইভ পদ্ধতিই দ্রুত হতে পারে — এটি একটি সাধারণ প্যাটার্ন যা M13-এ ("অ্যালগরিদম অ্যানালাইসিসের সাধারণ ভুল") আবার আসবে)।
৪ · মিডিয়ান ফাইন্ডিং — নেইভ বনাম worst-case $O(n)$ সিলেক্ট
একটি সম্পূর্ণ ভিন্ন সমস্যা, কিন্তু একই D&C পরিবারের: একটি আনসর্টেড অ্যারের $k$-তম ক্ষুদ্রতম এলিমেন্ট (যেমন মিডিয়ান) বের করা। নেইভ পদ্ধতি — পুরো অ্যারে সর্ট করে $k$-তম ইনডেক্সে দেখা — $\Theta(n \log n)$ সময় নেয়, যদিও আমরা পুরো সর্টেড ক্রম চাই না, শুধু একটি এলিমেন্ট।
মিডিয়ান-অফ-মিডিয়ানস (Select) অ্যালগরিদম worst-case $O(n)$-এ এটি অর্জন করে একটি চতুর পিভট-বাছাই কৌশল দিয়ে: অ্যারেকে ৫টি করে গ্রুপে ভাগ করা হয়, প্রতিটি গ্রুপের মিডিয়ান বের করা হয় (ছোট, তাই $O(1)$ প্রতি গ্রুপ), তারপর সেই মিডিয়ানগুলোরও মিডিয়ান রিকার্সিভভাবে বের করে সেটিকে পিভট হিসেবে ব্যবহার করা হয়। এই বিশেষ পিভট নিশ্চিত করে যে পার্টিশনের প্রতিটি ভাগে অন্তত প্রায় $3n/10$টি এলিমেন্ট থাকবে (কখনোই খুব বেশি অসামঞ্জস্যপূর্ণ স্প্লিট হবে না), যা রিকারেন্স $T(n) \le T(n/5) + T(7n/10) + O(n)$ দেয়। যেহেতু $\frac{1}{5} + \frac{7}{10} = \frac{9}{10} < 1$ (উপ-প্রবলেমের আকারের যোগফল সবসময় মূল আকারের একটি ধ্রুবক ভগ্নাংশের কম থাকে), এই রিকারেন্স $T(n) = O(n)$-এ সমাধান হয় (একটি জ্যামিতিক ধারার মতো ক্ষয়িষ্ণু সিরিজ যোগ করে — সম্পূর্ণ প্রমাণ এখানে সংক্ষিপ্ত রাখা হলো, কারণ এই কোর্সের মূল লক্ষ্য D&C টেমপ্লেট চেনা, প্রতিটি নির্বাচন-অ্যালগরিদমের প্রতিটি ধ্রুবক নিখুঁতভাবে অপ্টিমাইজ করা নয়)।
মিডিয়ান-অফ-মিডিয়ানস worst-case গ্যারান্টি দেয়, কিন্তু বাস্তবে এর কনস্ট্যান্ট ফ্যাক্টর বড় এবং ইমপ্লিমেন্টেশন জটিল। ব্যবহারিকভাবে অনেক বেশি জনপ্রিয় হলো quickselect — কুইক সর্টের পার্টিশন ধাপের মতোই, কিন্তু শুধু সেই ভাগে রিকার্স করা হয় যেখানে $k$-তম এলিমেন্ট থাকতে পারে (অন্য ভাগ সম্পূর্ণ বাদ)। র্যান্ডম পিভট ব্যবহার করলে এর প্রত্যাশিত (expected) রানটাইম $O(n)$ — L16-এর কুইক সর্টের মতোই এখানেও ফিক্সড পিভট হলে ওয়ার্স্ট কেস $O(n^2)$ হতে পারত, কিন্তু র্যান্ডম পিভট সেই ঝুঁকি এড়ায় (এই একই নীতি M12/L53-L54-এ বিস্তারিতভাবে আসবে)। নিচের কোড সেলে quickselect ইমপ্লিমেন্ট ও যাচাই করা হচ্ছে।
৫ · কম্পিউটেশনাল যাচাই — স্ট্রাসেনের গুণন
নিচের কোডে স্ট্রাসেনের অ্যালগরিদম $4 \times 4$ (এবং $2 \times 2$) ম্যাট্রিক্সের জন্য ইমপ্লিমেন্ট করা হয়েছে — উপরের সূত্রগুলো হুবহু কোডে রূপান্তরিত — এবং এর আউটপুট একটি সরল নেইভ ট্রিপল-লুপ গুণনের সাথে একাধিক র্যান্ডম ম্যাট্রিক্সে নিখুঁতভাবে মিলিয়ে দেখা হয়েছে।
import random
def matrix_add(A, B):
return [[A[i][j] + B[i][j] for j in range(len(A[0]))] for i in range(len(A))]
def matrix_sub(A, B):
return [[A[i][j] - B[i][j] for j in range(len(A[0]))] for i in range(len(A))]
def split_quadrants(M):
n = len(M)
mid = n // 2
A11 = [row[:mid] for row in M[:mid]]
A12 = [row[mid:] for row in M[:mid]]
A21 = [row[:mid] for row in M[mid:]]
A22 = [row[mid:] for row in M[mid:]]
return A11, A12, A21, A22
def combine_quadrants(C11, C12, C21, C22):
top = [r1 + r2 for r1, r2 in zip(C11, C12)]
bottom = [r1 + r2 for r1, r2 in zip(C21, C22)]
return top + bottom
def strassen_multiply(A, B):
n = len(A)
if n == 1:
return [[A[0][0] * B[0][0]]]
A11, A12, A21, A22 = split_quadrants(A)
B11, B12, B21, B22 = split_quadrants(B)
M1 = strassen_multiply(matrix_add(A11, A22), matrix_add(B11, B22))
M2 = strassen_multiply(matrix_add(A21, A22), B11)
M3 = strassen_multiply(A11, matrix_sub(B12, B22))
M4 = strassen_multiply(A22, matrix_sub(B21, B11))
M5 = strassen_multiply(matrix_add(A11, A12), B22)
M6 = strassen_multiply(matrix_sub(A21, A11), matrix_add(B11, B12))
M7 = strassen_multiply(matrix_sub(A12, A22), matrix_add(B21, B22))
C11 = matrix_add(matrix_sub(matrix_add(M1, M4), M5), M7)
C12 = matrix_add(M3, M5)
C21 = matrix_add(M2, M4)
C22 = matrix_add(matrix_sub(matrix_add(M1, M3), M2), M6)
return combine_quadrants(C11, C12, C21, C22)
def naive_multiply(A, B):
n, m, k = len(A), len(B[0]), len(B)
C = [[0] * m for _ in range(n)]
for i in range(n):
for j in range(m):
total = 0
for x in range(k):
total += A[i][x] * B[x][j]
C[i][j] = total
return C
def random_matrix(rng, n):
return [[rng.randint(-9, 9) for _ in range(n)] for _ in range(n)]
all_match = True
for size in [2, 4]:
for seed in [1, 2, 3, 4, 5]:
rng = random.Random(seed)
A = random_matrix(rng, size)
B = random_matrix(rng, size)
result_strassen = strassen_multiply(A, B)
result_naive = naive_multiply(A, B)
match = (result_strassen == result_naive)
all_match = all_match and match
print(f"size={size} seed={seed}: স্ট্রাসেন == নেইভ ? {match}")
print(f"\nসব কেসে স্ট্রাসেন ও নেইভ গুণন মিলেছে: {all_match}")
assert all_match
strassen_multiply নিজেই একবার রিকার্স করে $2\times2$ ব্লকে নেমে যায় এবং সেখানে সাতটি গুণন
প্রয়োগ করে — অর্থাৎ এই টেস্ট রিকার্সিভ ব্লক-স্তরেও সূত্রগুলোর সঠিকতা যাচাই করে, শুধু একক-স্তরে নয়।
৬ · কম্পিউটেশনাল যাচাই — Quickselect দিয়ে মিডিয়ান
import random
def quickselect(a, k, rng):
"""a-এর মধ্যে k-তম ক্ষুদ্রতম এলিমেন্ট (0-ইনডেক্সড) — র্যান্ডম পিভট, expected O(n)।"""
a = list(a)
lo, hi = 0, len(a) - 1
while True:
if lo == hi:
return a[lo]
pivot_idx = rng.randint(lo, hi)
a[pivot_idx], a[hi] = a[hi], a[pivot_idx]
pivot = a[hi]
store = lo
for i in range(lo, hi):
if a[i] < pivot:
a[store], a[i] = a[i], a[store]
store += 1
a[store], a[hi] = a[hi], a[store]
if k == store:
return a[store]
elif k < store:
hi = store - 1
else:
lo = store + 1
def find_median_quickselect(arr, rng):
n = len(arr)
if n % 2 == 1:
return quickselect(arr, n // 2, rng)
lower = quickselect(arr, n // 2 - 1, rng)
upper = quickselect(arr, n // 2, rng)
return (lower + upper) / 2
def true_median(arr):
s = sorted(arr)
n = len(s)
if n % 2 == 1:
return s[n // 2]
return (s[n // 2 - 1] + s[n // 2]) / 2
all_correct = True
for seed in [1, 2, 3, 4, 5, 6, 7, 8]:
rng = random.Random(seed)
size = rng.randint(1, 60)
arr = [rng.randint(0, 1000) for _ in range(size)]
computed = find_median_quickselect(arr, rng)
expected = true_median(arr)
correct = (computed == expected)
all_correct = all_correct and correct
print(f"seed={seed} n={size}: quickselect মিডিয়ান={computed}, sorted() মিডিয়ান={expected}, মিলেছে? {correct}")
print(f"\nসব সিডে quickselect ও sorted()-ভিত্তিক মিডিয়ান মিলেছে: {all_correct}")
assert all_correct
sorted()-ভিত্তিক (গ্রাউন্ড-ট্রুথ) মিডিয়ানের সাথে প্রতিবার নিখুঁতভাবে মিলেছে,
জোড় ও বিজোড় উভয় আকারের অ্যারেতেই। quickselect একটি Las Vegas-ধরনের অ্যালগরিদম (M12/L53
দ্রষ্টব্য) — র্যান্ডমনেস শুধু গতিকে প্রভাবিত করে, ফলাফল সবসময় সঠিক থাকে, যা এই যাচাইয়ে
নিশ্চিতভাবে দেখা যাচ্ছে।
স্ট্রাসেন ও মিডিয়ান-অফ-মিডিয়ানস — দুটোই দেখায় D&C-এর আসল শক্তি প্রায়ই কতগুলো রিকার্সিভ কল লাগছে সেটা কমানোর একটি চতুর উপায় খুঁজে বের করায় লুকিয়ে থাকে (স্ট্রাসেনে ৮ থেকে ৭, সিলেক্টে উভয় ভাগে রিকার্স করার বদলে একটিমাত্র সাব-প্রবলেমে রিকার্স করা)। এমনকি একটি মাত্র রিকার্সিভ কল কমানোও রিকারেন্সের এক্সপোনেন্ট বদলে দিতে পারে ($n^3 \to n^{2.807}$) — এই সূক্ষ্ম গাণিতিক সংবেদনশীলতাই মাস্টার থিওরেমকে (L13) একটি এত গুরুত্বপূর্ণ টুল করে তোলে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ নেইভ ব্লক-পদ্ধতির রিকারেন্স $T(n) = 8T(n/2) + O(n^2)$ থেকেও মাস্টার থিওরেম $\Theta(n^3)$ দেয় — এটাই তো নেইভ ট্রিপল-লুপ পদ্ধতির জটিলতা। তাহলে ব্লক-ভিত্তিক D&C পুনর্লিখনটাই তো অকেজো ছিল, তাই না?
ঠিক তাই — এবং এটিই একটি গুরুত্বপূর্ণ শিক্ষা: D&C ফর্মে সমস্যা পুনর্লিখলেই স্বয়ংক্রিয়ভাবে অ্যাসিম্পটোটিক উন্নতি হয় না। ব্লক-পদ্ধতি শুধু একই কাজকে ভিন্নভাবে সাজিয়েছিল, রিকার্সিভ কল সংখ্যা না কমিয়ে। প্রকৃত উন্নতি এসেছে যখন স্ট্রাসেন সেই ৮টি গুণনের মধ্যে বীজগাণিতিক নির্ভরতা খুঁজে বের করে সংখ্যা $৭$-এ নামিয়ে আনলেন — অর্থাৎ D&C কাঠামোটা প্রয়োজনীয় শর্ত, কিন্তু যথেষ্ট নয়; আসল কাজ ($a$, $b$, $f(n)$-এর প্রকৃত মান) নির্ধারণ করে দেয় লাভ হবে কি না।
প্র ০২ মিডিয়ান-অফ-মিডিয়ানস worst-case $O(n)$ গ্যারান্টি দেয়, quickselect শুধু expected $O(n)$ দেয় (worst-case $O(n^2)$ সম্ভব) — তাহলে কেন quickselect-ই বেশি ব্যবহৃত হয়?
মিডিয়ান-অফ-মিডিয়ানসের গ্যারান্টিটা "বিনামূল্যে" আসে না — এর কনস্ট্যান্ট ফ্যাক্টর (৫-গ্রুপে ভাগ করা, প্রতিটির মিডিয়ান বের করা, তারপর রিকার্সিভ কল) ব্যবহারিকভাবে যথেষ্ট বড়, এবং ইমপ্লিমেন্টেশন জটিল। অন্যদিকে র্যান্ডম-পিভট quickselect-এর ওয়ার্স্ট কেস ($O(n^2)$) ঘটার সম্ভাবনা এতটাই কম (প্রতিবার "দুর্ভাগ্যজনক" পিভট পড়া দরকার) যে ব্যবহারিক ক্ষেত্রে এটি প্রায় সবসময় দ্রুত — একটি ক্লাসিক "থিওরেটিক্যাল গ্যারান্টি বনাম ব্যবহারিক গড় পারফরম্যান্স" ট্রেড-অফ, যা M12-এ আরও বিস্তারিতভাবে আলোচিত হবে।
প্র ০৩ স্ট্রাসেনের কোড সেলে $2\times2$ এবং $4\times4$ — দুটি ভিন্ন সাইজ টেস্ট করা হলো কেন, শুধু $4\times4$ যথেষ্ট ছিল না?
$2\times2$ টেস্ট যাচাই করে যে সাতটি $M_i$ সূত্র ও কমবাইন-সূত্র নিজেরাই (বেস-কেসের ঠিক এক স্তর উপরে) সঠিক। $4\times4$ টেস্ট এর চেয়ে বেশি কিছু যাচাই করে — এখানে প্রতিটি $M_i$ নিজেই আবার রিকার্সিভভাবে একটি $2\times2$ স্ট্রাসেন গুণন (যার ইনপুট এখন স্কেলার নয়, বরং যোগ-বিয়োগ করা সাব-ম্যাট্রিক্স), অর্থাৎ এটি রিকার্সনের একাধিক স্তর জুড়ে সূত্রগুলোর সঠিকতা নিশ্চিত করে। শুধু একটি সাইজ টেস্ট করলে একটি নির্দিষ্ট রিকার্সন-গভীরতায় লুকানো বাগ ধরা নাও পড়তে পারত।
অনুশীলন
-
চিন্তা করুন: যদি কোনোভাবে $2\times2$ ব্লক গুণনে মাত্র $6$টি রিকার্সিভ গুণন দিয়ে কাজ
চালানো যেত (কাল্পনিকভাবে), তাহলে $T(n) = 6T(n/2) + O(n^2)$-এর জটিলতা কত হতো?
$n^{\log_2 6} \approx n^{2.585}$, এবং যেহেতু $2 < 2.585$, মাস্টার থিওরেমের কেস ১ প্রযোজ্য — $T(n) = \Theta(n^{2.585})$, যা স্ট্রাসেনের $\Theta(n^{2.807})$-এর চেয়েও ভালো হতো। (বাস্তবে $2\times2$ ব্লকের জন্য $7$টির কমে সম্ভব নয় বলে প্রমাণিত — কিন্তু এই চিন্তা-পরীক্ষা দেখায় কেন গবেষকরা এখনও আরও কম গুণনের কৌশল খোঁজেন।)
-
পরীক্ষা করুন: উপরের quickselect কোড সেলে
for seed in [1, 2, 3, 4, 5, 6, 7, 8]:লাইনে আরও কিছু সিড (যেমন9, 10, 11, 12) যোগ করে Run চেপে দেখুন সবগুলোতেইall_correctএখনওTrueথাকে কি না।নতুন সিডগুলোর জন্যও প্রতিটি লাইনে
মিলেছে? Trueদেখা যাবে এবং শেষেসব সিডে ... মিলেছে: Trueঅপরিবর্তিত থাকবে — quickselect-এর সঠিকতা কোনো নির্দিষ্ট সিডের উপর নির্ভর করে না, কারণ এটি Las Vegas অ্যালগরিদম (সবসময় সঠিক, শুধু গতি এলোমেলো)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাসিম্পটোটিক অ্যানালাইসিস, রিকারেন্স, ডিভাইড অ্যান্ড কনকার, গ্রিডি, DP, গ্রাফ অ্যালগরিদম, ব্যাকট্র্যাকিং, স্ট্রিং অ্যালগরিদম, অ্যামর্টাইজড অ্যানালাইসিস, NP-কমপ্লিটনেস ও অ্যাপ্রক্সিমেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- বাইনারি সার্চ ও এর ভ্যারিয়েন্ট আগের পাঠ M4-এর আগের D&C কেস স্টাডি — এখানকার $O(n)$ সিলেক্ট অ্যালগরিদমের সাথে তুলনীয় আরেকটি ভ্যারিয়েন্ট-ভিত্তিক আলোচনা।
- গ্রিডি প্যারাডাইম ও এক্সচেঞ্জ আর্গুমেন্ট পরের মডিউল D&C মডিউল শেষ — এবার M5-এ সম্পূর্ণ ভিন্ন একটি ডিজাইন প্যারাডাইমে প্রবেশ।