পাঠ ১০ · ৫৭-এর মধ্যে · মডিউল ৩
Home / Courses / Design and Analysis of Algorithms / রিকারেন্স রিলেশন

রিকারেন্স রিলেশন ও রিকার্সিভ অ্যালগরিদম

Recurrence relations & recursive algorithms
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • রিকারেন্স রিলেশনের আনুষ্ঠানিক গঠন — বেস কেস ও রিকার্সিভ কেস
  • কেন একটি রিকার্সিভ অ্যালগরিদমের "কী গণনা করে" এবং "কতটা কাজ করে" — এই দুটো আলাদা প্রশ্ন, এবং দ্বিতীয়টিরও নিজস্ব একটি রিকারেন্স থাকে
  • নেইভ রিকার্সিভ ফিবোনাচির প্রকৃত কল সংখ্যা গুনে এক্সপোনেনশিয়াল গ্রোথ প্রত্যক্ষ করা
  • M3 মডিউলের বাকি তিনটি পাঠ (সাবস্টিটিউশন, রিকার্সন ট্রি, মাস্টার থিওরেম) ঠিক কোন সমস্যাটি সমাধান করতে যাচ্ছে তার প্রেক্ষাপট

১ · রিকারেন্স রিলেশন কী

রিকারেন্স রিলেশনRecurrence Relationএকটি ফাংশনকে তার নিজেরই ছোট ইনপুটের মানের মাধ্যমে সংজ্ঞায়িত করা একটি সমীকরণ, সাথে একটি বা একাধিক বেস কেস। হলো এমন একটি সমীকরণ যা একটি ফাংশনের মান তার নিজেরই ছোট ইনপুটের উপর নির্ভর করে সংজ্ঞায়িত করে। L01-এ আমরা দেখেছি একটি সত্যিকারের অ্যালগরিদমের একটি বৈশিষ্ট্য হলো সসীমতা (finiteness) — একটি রিকারেন্স রিলেশনও এই একই নীতি মেনে চলে: এতে দুটো অংশ বাধ্যতামূলক —

বেস কেস (Base Case)
একটি বা একাধিক ছোট ইনপুটের জন্য সরাসরি, রিকার্সন ছাড়াই একটি উত্তর দেওয়া হয় — এটাই থামার শর্ত।
রিকার্সিভ কেস (Recursive Case)
বাকি সব ইনপুটের জন্য, ফাংশনের মান একই ফাংশনের কঠোরভাবে ছোট ইনপুটের মানের মাধ্যমে সংজ্ঞায়িত হয়।

"কঠোরভাবে ছোট" শর্তটি গুরুত্বপূর্ণ — এটি নিশ্চিত করে যে বারবার প্রয়োগ করলে শেষ পর্যন্ত বেস কেসে পৌঁছাতেই হবে, অসীম রিকার্সন ঘটবে না। ক্লাসিক উদাহরণ হলো ফিবোনাচি সংখ্যা:

$$F(n) = \begin{cases} 0 & n = 0 \quad \text{(বেস কেস)}\\ 1 & n = 1 \quad \text{(বেস কেস)}\\ F(n-1) + F(n-2) & n \ge 2 \quad \text{(রিকার্সিভ কেস)}\end{cases}$$

এখানে n=0 ও n=1 বেস কেস — সরাসরি উত্তর দেওয়া হয়েছে। n \ge 2-এর জন্য F(n)-কে সংজ্ঞায়িত করা হয়েছে দুটো ছোট উপসমস্যা F(n-1) ও F(n-2)-এর মাধ্যমে — উভয়ই n-এর চেয়ে ছোট, তাই বারবার প্রয়োগ করলে শেষমেশ F(0) বা F(1)-এ পৌঁছানো নিশ্চিত। ফিবোনাচি ও ফ্যাক্টোরিয়ালের রিকার্সিভ Python ইমপ্লিমেন্টেশন ইতিমধ্যে DSA কোর্সে দেখানো হয়েছে — এই পাঠে আমরা ইমপ্লিমেন্টেশন নয়, বরং এই রিকারেন্স রিলেশনের গাণিতিক কাঠামো ও এর "লুকানো দ্বিতীয় রিকারেন্স" নিয়ে আলোচনা করব।

২ · প্রতিটি রিকার্সিভ অ্যালগরিদমেরই একটি "কল-কাউন্ট" রিকারেন্স থাকে

F(n) নিজে কী মান তা একটি প্রশ্ন। কিন্তু F(n) নেইভভাবে (উপরের সংজ্ঞা অনুযায়ী সরাসরি রিকার্সন দিয়ে, কোনো মেমোয়াইজেশন ছাড়া) গণনা করতে কতগুলো ফাংশন কল লাগবে — এটা সম্পূর্ণ ভিন্ন একটা প্রশ্ন, এবং এর উত্তরও একটা রিকারেন্স রিলেশন দিয়েই দিতে হয়। ধরা যাক T(n) = fib(n) গণনা করতে মোট কতগুলো ফাংশন কল হয় (নিজেরটাসহ):

$$T(n) = \begin{cases} \Theta(1) & n \le 1\\ T(n-1) + T(n-2) + \Theta(1) & n \ge 2\end{cases}$$

লক্ষ্য করুন — T(n)-এর রিকার্সিভ কেসের গঠন F(n)-এর রিকার্সিভ কেসের সাথে প্রায় হুবহু একই রকম (দুটো ছোট উপসমস্যার যোগফল), তাই এটাও এক্সপোনেনশিয়ালি বেড়ে যাওয়ার আশঙ্কা থাকে। নিচের রিকার্সন ট্রি স্কেচটি দেখায় কেন — প্রতিটি নন-বেস-কেস কল থেকে দুটো নতুন কল জন্ম নেয়, ফলে গাছটি প্রতি স্তরে প্রায় দ্বিগুণ চওড়া হতে থাকে।

fib(4) fib(3) fib(2) fib(2) fib(1) fib(1) fib(0) ... এবং fib(2)-এর নিচেও আরও দুটো কল জন্ম নেয় — গাছ ক্রমাগত চওড়া হতে থাকে
শুধু চারটি স্তরেই ৮টিরও বেশি কল দেখা যাচ্ছে, এবং fib(2) দুইবার আলাদাভাবে পুনরায় গণনা করা হচ্ছে — এই পুনরাবৃত্তিই (overlapping subproblems) M6/L24-এ মেমোয়াইজেশনের মূল প্রেরণা।
DSA কোর্সের সাথে সম্পর্ক

DSA কোর্সে ফিবোনাচি ও অন্যান্য রিকার্সিভ ফাংশন কীভাবে লিখতে হয় তা ইতিমধ্যে দেখানো হয়েছে। এই কোর্স সেই পরিচিতি ধরে নিয়ে প্রশ্ন করে — এই রিকার্সিভ কাঠামো কতটা কাজ করে, এবং সেই প্রশ্নের উত্তর দেওয়ার জন্য রিকারেন্স রিলেশন গাণিতিকভাবে সমাধান করার তিনটি পদ্ধতি (সাবস্টিটিউশন, রিকার্সন ট্রি, মাস্টার থিওরেম) হলো এই মডিউলের বাকি তিনটি পাঠ (L11-L13)।

৩ · সত্যিকারের কোড — প্রতিটি কল গুনে দেখা

নিচের কোড সেলে একটি গ্লোবাল কাউন্টার ব্যবহার করে প্রতিটি fib কলে +১ করা হচ্ছে — অর্থাৎ আমরা উপরের T(n) রিকারেন্সের প্রকৃত মান সরাসরি পরিমাপ করছি (কোনো সূত্র অনুমান করা হচ্ছে না)।

Python
call_count = 0

def fib(n):
    global call_count
    call_count += 1          # প্রতিটি কলেই গোনা হচ্ছে -- এটাই T(n)
    if n <= 1:
        return n              # বেস কেস
    return fib(n - 1) + fib(n - 2)   # রিকার্সিভ কেস

def count_calls(n):
    global call_count
    call_count = 0
    fib(n)
    return call_count

sizes = [5, 10, 15, 20, 25]
counts = [(n, count_calls(n)) for n in sizes]

print(f"{'n':>5} | {'T(n) -- মোট কল সংখ্যা':>22}")
for n, c in counts:
    print(f"{n:>5} | {c:>22}")

print("\nn প্রতি +৫ বাড়লে কল সংখ্যা কত গুণ বাড়ে:")
for i in range(1, len(counts)):
    n_prev, c_prev = counts[i - 1]
    n_cur, c_cur = counts[i]
    print(f"n={n_prev}->{n_cur}: {c_cur / c_prev:.3f}x")

    
আউটপুট দেখাবে T(5)=15, T(10)=177, T(15)=1973, T(20)=21891, T(25)=242785 — এবং প্রতি +৫ ধাপে অনুপাত ক্রমে ১১.৮, ১১.১৫, ১১.১, ১১.০৯-এর দিকে এগিয়ে যায়। এটা কাকতালীয় নয় — T(n)-এর ঠিক সমাধান হলো T(n) = 2F(n+1) - 1 (এখানে F নিজেই ফিবোনাচি ফাংশন), এবং ফিবোনাচি সংখ্যা বাড়ে গোল্ডেন রেশিও $\varphi = \frac{1+\sqrt5}{2} \approx 1.618$-এর ঘাত অনুযায়ী। তাই $n$ প্রতি ৫ বাড়লে অনুপাত এগিয়ে যায় $\varphi^5 \approx 11.09$-এর দিকে — ঠিক যা কোড চালিয়ে দেখা যাচ্ছে। মূল কথা: F(n)-এর মান নিজে যত ধীরেই বাড়ুক না কেন, একে নেইভভাবে গণনা করতে লাগা কল সংখ্যা এক্সপোনেনশিয়ালি বাড়ে ($\varphi^n$ হারে) — এবং এটা যাচাই করতে আমাদের T(n)-এর নিজস্ব রিকারেন্স সমাধান করতে হলো, শুধু F(n)-এর সংজ্ঞা দেখলেই চলবে না।
মূল কথা · Key takeaway

একটি রিকারেন্স রিলেশনে সবসময় একটি বেস কেস ও একটি রিকার্সিভ কেস থাকে, এবং প্রতিটি রিকার্সিভ অ্যালগরিদম নিজে কী গণনা করে তার চেয়ে কতটা কাজ করে — এই দুটো ভিন্ন প্রশ্ন। দ্বিতীয় প্রশ্নের উত্তর নিজেই একটা রিকারেন্স, যা হাতে গণনা করে বোঝা কঠিন হতে পারে (উপরের মতো এক্সপোনেনশিয়াল ক্ষেত্রে) বা সহজ (পরের পাঠগুলোর মতো পলিনমিয়াল ক্ষেত্রে)। M3-এর বাকি তিনটি পাঠ — সাবস্টিটিউশন মেথড (L11), রিকার্সন ট্রি মেথড (L12), এবং মাস্টার থিওরেম (L13) — ঠিক এই ধরনের রিকারেন্স রিলেশন গাণিতিকভাবে সমাধান করার তিনটি পদ্ধতিগত টুল শেখাবে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ F(n) নিজে তো এক্সপোনেনশিয়ালি বাড়ে না (ফিবোনাচি সংখ্যা $F(n)$ পলিনমিয়ালি বাড়ে না ঠিকই, কিন্তু এটাই তো আসল "উত্তর") — তাহলে কল সংখ্যা এক্সপোনেনশিয়াল হওয়া কি সমস্যাজনক?

হ্যাঁ, এটাই মূল সমস্যা। F(n)-এর মান নিজে $\varphi^n/\sqrt5$ হারে বাড়ে (এক্সপোনেনশিয়াল হলেও), কিন্তু একটিমাত্র সংখ্যা গণনা করতে যে কতগুলো ফাংশন কল লাগছে তাও একই হারে বাড়ছে — অথচ একই তথ্য (যেমন fib(2)) বারবার পুনরায় গণনা করা হচ্ছে (উপরের ডায়াগ্রামে দেখুন)। এই পুনরাবৃত্ত উপসমস্যা (overlapping subproblems) এড়ানো গেলে — অর্থাৎ প্রতিটি মান একবার গণনা করে সংরক্ষণ করে রাখলে (মেমোয়াইজেশন) — মাত্র $O(n)$ কলেই একই উত্তর পাওয়া সম্ভব। এই কৌশল M6/L24-এ বিস্তারিত আসবে।

প্র ০২ যদি বেস কেস বাদ দিয়ে শুধু F(n) = F(n-1) + F(n-2) লেখা হতো, তাহলে কী সমস্যা হতো?

তাহলে এটি L01-এর "সসীমতা" বৈশিষ্ট্য লঙ্ঘন করত — রিকার্সন কখনোই থামত না, কারণ F(n-1) গণনা করতে F(n-2) লাগবে, তার জন্য F(n-3), এভাবে অসীম পর্যন্ত চলতেই থাকবে। Python-এ এটি চালালে RecursionError: maximum recursion depth exceeded ব্যতিক্রম (exception) দেখা যাবে। বেস কেসই একমাত্র জিনিস যা রিকার্সনকে থামার একটা মেঝে (floor) দেয়।

প্র ০৩ উপরের কোডে call_count একটি গ্লোবাল ভেরিয়েবল হিসেবে ব্যবহার করা হয়েছে কেন — এটা কি রিকারেন্স রিলেশনের সংজ্ঞার সাথে সম্পর্কিত?

গ্লোবাল কাউন্টারটি বিশুদ্ধভাবে পরিমাপের জন্য ব্যবহৃত হয়েছে — এটি fib-এর গাণিতিক সংজ্ঞার অংশ নয়, বরং T(n) (কল সংখ্যার রিকারেন্স) পরিমাপের একটি ব্যবহারিক কৌশল। প্রতিটি fib কলে ঠিক একবার call_count += 1 চালানো হচ্ছে বলেই, চূড়ান্ত মানটি ঠিক $T(n)$-এর সংজ্ঞার সাথে মেলে (বেস কেসে ১ কল, রিকার্সিভ কেসে দুটো উপকলের যোগফল + নিজের ১ কল)।

অনুশীলন

  1. চিন্তা করুন: উপরের প্যাটার্ন অনুযায়ী, n=30-এ কল সংখ্যা মোটামুটি কত হবে বলে আপনার ধারণা? (হিন্ট: $T(25)=242785$, এবং প্রতি +৫-এ অনুপাত প্রায় $11.09$।)

    আনুমানিক $242785 \times 11.09 \approx 26,92,300$ — এবং প্রকৃত মান ($T(n)=2F(n+1)-1$ সূত্র দিয়ে হিসেব করলে) $T(30) = 2{,}692{,}537$, যা অনুমানের খুবই কাছাকাছি।

  2. পরীক্ষা করুন: উপরের কোড সেলে sizes লিস্টে 30 যোগ করে (sizes = [5, 10, 15, 20, 25, 30]) Run চেপে দেখুন এটি চলতে কেমন সময় নেয়, এবং আপনার অনুমানের সাথে মেলে কি না। খেয়াল করুন — মাত্র n ৫ বাড়ানোতেই কোডটি চলতে লক্ষণীয়ভাবে বেশি সময় নিচ্ছে।

    হ্যাঁ, T(30) = 2692537 — উপরের অনুমানের প্রায় সমান। প্রায় ২৭ লক্ষ ফাংশন কল সম্পন্ন করতে ব্রাউজারের Python স্যান্ডবক্সে লক্ষণীয় বিলম্ব হবে, যেখানে n=25 পর্যন্ত তা প্রায় চোখে পড়ে না — এটাই এক্সপোনেনশিয়াল গ্রোথের বাস্তব প্রভাব।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাসিম্পটোটিক অ্যানালাইসিস, রিকারেন্স, ডিভাইড অ্যান্ড কনকার, গ্রিডি, DP, গ্রাফ অ্যালগরিদম, ব্যাকট্র্যাকিং, স্ট্রিং অ্যালগরিদম, অ্যামর্টাইজড অ্যানালাইসিস, NP-কমপ্লিটনেস ও অ্যাপ্রক্সিমেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Data Structures & Algorithms কোর্স সহোদর কোর্স ফিবোনাচি ও অন্যান্য রিকার্সিভ ফাংশনের ইমপ্লিমেন্টেশন সেই কোর্সেই দেখানো হয়েছে — এই কোর্স এদের রিকারেন্স রিলেশন গাণিতিকভাবে সমাধান করায় মনোযোগ দেয়।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks, Mobile App Development, Ethics in Computing & AI Safety, Software Testing & Quality Assurance ও Design and Analysis of Algorithms — সব এক জায়গায়।
আগের পাঠ
স্পেস কমপ্লেক্সিটি অ্যানালাইসিস