ডিভাইড অ্যান্ড কনকার প্যারাডাইম
এই পাঠে যা শিখবেন
- ডিভাইড, কনকার ও কমবাইন — তিনটি ধাপ সুনির্দিষ্টভাবে সংজ্ঞায়িত করতে পারা
- পরিচিত অ্যালগরিদমে (মার্জ সর্ট, কুইক সর্ট, বাইনারি সার্চ) এই তিনটি ধাপ শনাক্ত করতে পারা
- একটি D&C অ্যালগরিদমের রানটাইমকে রিকারেন্স রিলেশন হিসেবে লিখতে পারা
- রিকার্সিভ স্প্লিটিং প্যাটার্নের একটি জেনুইন কম্পিউটেশনাল ডেমো দেখা — মোট রিকার্সিভ কল ও ডেপথ প্রকৃতপক্ষে গুনে
১ · তিনটি ধাপ — ডিভাইড, কনকার, কমবাইন
ডিভাইড অ্যান্ড কনকারDivide and Conquerএকটি ডিজাইন প্যারাডাইম যেখানে একটি সমস্যাকে ছোট, একই ধরনের সাব-প্রবলেমে ভাগ করা হয়, প্রতিটি রিকার্সিভভাবে সমাধান করা হয়, এবং সাব-সমাধানগুলো জোড়া দিয়ে মূল সমাধান তৈরি করা হয়। হলো একটি সাধারণ, বহু অ্যালগরিদমে পুনরাবৃত্ত হওয়া টেমপ্লেট। এর তিনটি ধাপ:
মূল সমস্যাকে একই ধরনের কিন্তু ছোট আকারের একাধিক সাব-প্রবলেমে ভাগ করা — সাধারণত আকার প্রায় সমান ভাগে (যেমন $n/2$)।
প্রতিটি সাব-প্রবলেম রিকার্সিভভাবে সমাধান করা — যদি সাব-প্রবলেমের আকার যথেষ্ট ছোট হয় (বেস কেস), তবে সরাসরি সমাধান করা।
সাব-প্রবলেমগুলোর সমাধান জোড়া দিয়ে মূল সমস্যার সমাধান তৈরি করা।
সাধারণ সিউডোকোড আকারে লিখলে:
function solve(problem):
if problem is small enough (base case):
return direct_solution(problem)
subproblems = divide(problem) # ডিভাইড
sub_solutions = [solve(sp) for sp in subproblems] # কনকার (রিকার্সিভ কল)
return combine(sub_solutions) # কমবাইন
ডিভাইড অ্যান্ড কনকার তখনই কার্যকর যখন দুটি শর্ত পূরণ হয়: (১) সাব-প্রবলেমগুলো মূল সমস্যার সাথে একই ধরনের (তাই একই রিকার্সিভ ফাংশন দিয়ে সমাধান করা যায়), এবং (২) সাব-প্রবলেমগুলো একে অপরের থেকে স্বাধীন — একটির সমাধান আরেকটির উপর নির্ভর করে না। এই স্বাধীনতার কারণেই কনকার ধাপে সাব-প্রবলেমগুলো (নীতিগতভাবে) যেকোনো ক্রমে বা এমনকি সমান্তরালে সমাধান করা যায়। M6-এ আমরা দেখব, যখন সাব-প্রবলেমগুলো ওভারল্যাপ করে (একই সাব-প্রবলেম বারবার আসে), তখন প্লেইন রিকার্সন অপচয়মূলক হয়ে যায় এবং ডাইনামিক প্রোগ্রামিং প্রয়োজন হয়।
২ · পরিচিত D&C অ্যালগরিদম — ../dsa/ কোর্সে যা দেখেছেন
নিচের তিনটি অ্যালগরিদমের ইমপ্লিমেন্টেশন ও মেকানিক্স ইতিমধ্যে DSA কোর্সে কভার করা হয়েছে — এখানে শুধু D&C টেমপ্লেটের সাথে কীভাবে মেলে তা দেখা হচ্ছে; L15–L18-এ প্রতিটির রিগোরাস রানটাইম-অ্যানালাইসিস আসবে।
ডিভাইড: অ্যারেকে মাঝখান থেকে দুই ভাগে ভাগ করা। কনকার: প্রতিটি অর্ধেক রিকার্সিভভাবে সর্ট করা। কমবাইন: দুটি সর্টেড অর্ধেক $O(n)$ সময়ে মার্জ করা। বিস্তারিত L15।
ডিভাইড: একটি পিভট বেছে অ্যারেকে পিভটের চেয়ে ছোট/বড় দুই ভাগে পার্টিশন করা (এই ধাপেই আসল কাজ হয়)। কনকার: দুই ভাগ রিকার্সিভভাবে সর্ট করা। কমবাইন: তুচ্ছ — কিছুই করতে হয় না, পার্টিশন হওয়া অ্যারেই চূড়ান্ত। বিস্তারিত L16।
ডিভাইড: মাঝের এলিমেন্টের সাথে তুলনা করে অ্যারেকে দুই ভাগে ভাগ করা। কনকার: শুধু একটি ভাগেই রিকার্সিভভাবে খোঁজা (অন্যটি বাতিল)। কমবাইন: তুচ্ছ — সাব-প্রবলেমের উত্তরই চূড়ান্ত উত্তর। বিস্তারিত L17।
লক্ষ্য করুন — কমবাইন ধাপের খরচ প্রতিটিতে ভিন্ন: মার্জ সর্টে $O(n)$ (মার্জ করতে পুরো অ্যারে ঘুরতে হয়), কুইক সর্ট ও বাইনারি সার্চে $O(1)$ (কিছুই করার নেই)। এই পার্থক্যই মূলত তাদের রানটাইম রিকারেন্সের $f(n)$ অংশ নির্ধারণ করে।
৩ · রানটাইম একটি রিকারেন্স হিসেবে
যেকোনো D&C অ্যালগরিদমের রানটাইম সাধারণভাবে এভাবে লেখা যায়:
$$T(n) = a \cdot T(n/b) + f(n)$$এখানে $a$ = কতগুলো সাব-প্রবলেম তৈরি হয় (কনকার ধাপে কতবার রিকার্সিভ কল হয়), $b$ = প্রতিটি সাব-প্রবলেমের আকার মূল সমস্যার তুলনায় কত ভাগের এক ভাগ (ডিভাইড ফ্যাক্টর), এবং $f(n)$ = ডিভাইড ও কমবাইন ধাপের নিজস্ব খরচ (রিকার্সিভ কলগুলো বাদে)। এটি ঠিক সেই ফর্ম যা M3-এ (L10–L13) সাবস্টিটিউশন মেথড, রিকার্সন ট্রি মেথড এবং মাস্টার থিওরেম দিয়ে সমাধান করার কৌশল শেখা হয়েছে — এই মডিউল সেই টুলগুলো সরাসরি প্রয়োগ করবে:
$T(n) = 2T(n/2) + O(n)$ — L15
ওয়ার্স্ট: $T(n) = T(n-1) + O(n)$; অ্যাভারেজ: $T(n) \approx 2T(n/2) + O(n)$ — L16
$T(n) = T(n/2) + O(1)$ — L17
$T(n) = 7T(n/2) + O(n^2)$ — L18
৪ · কম্পিউটেশনাল ডেমো — রিকার্সিভ স্প্লিটিং প্যাটার্নের আকার
L15–L18-এ আমরা প্রতিটি অ্যালগরিদমের তুলনার সংখ্যা গুনব। এখানে, তার আগে, শুধু D&C-এর রিকার্সিভ কল-স্ট্রাকচারটাই কেমন দেখতে — সেটা সরাসরি গুনে দেখি। নিচের কোডে একটি অ্যারের সর্বোচ্চ মান বের করার একটি (ইচ্ছাকৃতভাবে সরল, অপটিমাইজড নয়) D&C ফাংশন লেখা হয়েছে — এটি প্রতিটি রিকার্সিভ কল এবং সর্বোচ্চ ডেপথ গুনে রাখে।
import random
import math
def max_divide_and_conquer(arr, counter, depth=0):
counter['calls'] += 1
counter['max_depth'] = max(counter['max_depth'], depth)
if len(arr) == 1:
return arr[0]
mid = len(arr) // 2
left_max = max_divide_and_conquer(arr[:mid], counter, depth + 1)
right_max = max_divide_and_conquer(arr[mid:], counter, depth + 1)
return left_max if left_max > right_max else right_max
random.seed(42)
sizes = [8, 16, 32, 64, 128]
print(f"{'n':>5} | {'সঠিক?':>7} | {'কল':>6} | {'2n-1':>6} | {'ডেপথ':>6} | {'log2(n)':>8}")
for n in sizes:
arr = list(range(n))
random.shuffle(arr)
counter = {'calls': 0, 'max_depth': 0}
result = max_divide_and_conquer(arr, counter)
predicted_calls = 2 * n - 1
predicted_depth = int(math.log2(n))
correct = (result == max(arr))
print(f"{n:>5} | {str(correct):>7} | {counter['calls']:>6} | {predicted_calls:>6} | {counter['max_depth']:>6} | {predicted_depth:>8}")
ডিভাইড অ্যান্ড কনকার একটি টেমপ্লেট, একটি নির্দিষ্ট অ্যালগরিদম নয়। এর শক্তি নির্ভর করে নির্দিষ্ট সমস্যায় ডিভাইড ও কমবাইন ধাপ কতটা সস্তায় করা যায় তার উপর — এবং সেই খরচ ($f(n)$) ও শাখা-সংখ্যা ($a$, $b$) নির্ণয় করার পরই রিকারেন্স রিলেশন সমাধান করে প্রকৃত জটিলতা বের করা সম্ভব হয়। পরবর্তী চারটি পাঠ ঠিক এই কাজটিই করবে — মার্জ সর্ট, কুইক সর্ট, বাইনারি সার্চ ও স্ট্রাসেনের গুণনের জন্য রিগোরাসভাবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ D&C কার্যকর হতে সাব-প্রবলেমগুলো "স্বাধীন" হতে হবে কেন? স্বাধীন না হলে কী সমস্যা হয়?
সাব-প্রবলেমগুলো স্বাধীন হলে প্রতিটি সাব-প্রবলেম একবার সমাধান করলেই যথেষ্ট — কমবাইন ধাপে শুধু ফলাফলগুলো জোড়া দিলেই চলে। কিন্তু সাব-প্রবলেমগুলো যদি ওভারল্যাপ করে (একই সাব-প্রবলেম বহুবার বিভিন্ন রিকার্সিভ শাখা থেকে আসে), তাহলে প্লেইন D&C রিকার্সন সেই একই সাব-প্রবলেম বারবার সমাধান করবে — যেমন নেইভ ফিবোনাচি রিকার্সন এক্সপোনেনশিয়াল সময় নেয় (L10-এ দেখা হয়েছে)। এই ওভারল্যাপিং সাব-প্রবলেম পরিস্থিতিতেই M6-এর ডাইনামিক প্রোগ্রামিং (মেমোয়াইজেশন/ট্যাবুলেশন দিয়ে প্রতিটি সাব-প্রবলেম একবারই সমাধান করা) কাজে লাগে।
প্র ০২ মার্জ সর্টের কমবাইন ধাপ $O(n)$ কিন্তু কুইক সর্ট ও বাইনারি সার্চের কমবাইন ধাপ প্রায় $O(1)$ — এই পার্থক্য কেন হয়?
মার্জ সর্টে ডিভাইড ধাপ তুচ্ছ (শুধু মাঝখান থেকে ভাগ করা) কিন্তু "আসল কাজ" — দুটি সর্টেড অর্ধেককে একত্রে একটি সর্টেড ক্রমে সাজানো — কমবাইন ধাপে ঘটে, যা পুরো অ্যারে ঘুরে করতে হয় ($O(n)$)। কুইক সর্টে উল্টো — "আসল কাজ" (পার্টিশনিং, অর্থাৎ পিভটের চেয়ে ছোট/বড় আলাদা করা) ডিভাইড ধাপেই ঘটে যায়, ফলে কমবাইন ধাপে আর কিছুই করার থাকে না। বাইনারি সার্চেও একই রকম — ডিভাইড ধাপেই (মাঝের এলিমেন্টের সাথে তুলনা করে) সিদ্ধান্ত নেওয়া হয়ে যায় কোন অর্ধেকে উত্তর আছে, তাই শুধু একটি সাব-প্রবলেম সমাধান করলেই চলে এবং কমবাইন করার কিছু নেই। কোন ধাপে "আসল কাজ" হচ্ছে তা লক্ষ্য করাই একটি D&C অ্যালগরিদম বোঝার চাবিকাঠি।
প্র ০৩ উপরের কোডে সর্বোচ্চ মান বের করার D&C পদ্ধতিটি একটি সাধারণ লিনিয়ার স্ক্যানের চেয়ে দ্রুত নয় (উভয়ই $O(n)$) — তাহলে এটি দেখানোর উদ্দেশ্য কী?
এই ডেমোর উদ্দেশ্য গতি দেখানো নয় — উদ্দেশ্য হলো D&C-এর রিকার্সিভ কল-কাঠামো (কতগুলো কল হয়, কত গভীরে যায়) প্রকৃতপক্ষে গুনে দেখানো, কারণ পরবর্তী পাঠগুলোতে (L15–L18) ঠিক এই একই কাঠামোর উপর ভিত্তি করে রিকারেন্স রিলেশন লেখা ও মাস্টার থিওরেম প্রয়োগ করা হবে। কিছু D&C অ্যালগরিদম (যেমন মার্জ সর্ট, স্ট্রাসেনের গুণন) নেইভ পদ্ধতির চেয়ে দ্রুত হয়; কিছু (যেমন এই max উদাহরণ) হয় না — কারণটা নির্ভর করে কমবাইন ধাপে কতটা "অতিরিক্ত কাজ" বাঁচানো যাচ্ছে তার উপর, যা L15/L18-এ স্পষ্ট হবে।
অনুশীলন
-
চিন্তা করুন: উপরের প্যাটার্ন অনুযায়ী $n = 256$ হলে মোট রিকার্সিভ কল সংখ্যা ও সর্বোচ্চ
ডেপথ কত হবে বলে আপনার ধারণা?
কল সংখ্যা $2 \times 256 - 1 = 511$টি হবে, এবং ডেপথ $\log_2 256 = 8$ হবে — প্যাটার্ন অনুসরণ করে।
-
পরীক্ষা করুন: উপরের কোড সেলে
sizesলিস্টে256যোগ করে (sizes = [8, 16, 32, 64, 128, 256]) Run চেপে আপনার অনুমান যাচাই করুন।আউটপুটে নতুন লাইনে দেখা যাবে
n=256-এর জন্য কল511এবং ডেপথ8— দুটোই আপনার হিসাবের সাথে মিলে যাবে, কারণ $256 = 2^8$।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাসিম্পটোটিক অ্যানালাইসিস, রিকারেন্স, ডিভাইড অ্যান্ড কনকার, গ্রিডি, DP, গ্রাফ অ্যালগরিদম, ব্যাকট্র্যাকিং, স্ট্রিং অ্যালগরিদম, অ্যামর্টাইজড অ্যানালাইসিস, NP-কমপ্লিটনেস ও অ্যাপ্রক্সিমেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- মাস্টার থিওরেম আগের পাঠ এই পাঠের রিকারেন্স $T(n) = aT(n/b) + f(n)$ সমাধান করার তিনটি কেস — L15–L18-এ বারবার ব্যবহৃত হবে।
- Data Structures & Algorithms কোর্স সহোদর কোর্স মার্জ সর্ট, কুইক সর্ট ও বাইনারি সার্চের ইমপ্লিমেন্টেশন ওয়াকথ্রু সেই কোর্সেই আছে।