সরাসরি পর্বে যান
পর্ব ১২ · ৮৩-এর মধ্যে · মডিউল ২

Recursive সংজ্ঞা আর structural induction

Recursive Definitions and Structural Induction — Functions, Sets, Trees, Lexicographic Induction and Recursive Algorithms

Taught in Bangla, this lesson defines functions and sets recursively, including Σ*, balanced parentheses, well-formed formulas and full binary trees, and proves properties by structural induction with one case per rule. It bounds nodes by height in full binary trees, uses lexicographic order on ℕ × ℕ to justify the Ackermann function, proves Fibonacci growth and Cassini's identity, and argues correctness of recursive algorithms. In-page Python enumerates small cases.

এই পর্বে যা শিখবেন

  • Recursive function আর sequence-এর সংজ্ঞা, আর কখন সেটি সত্যিই একটি function দেয়
  • Basis, recursive step আর exclusion rule দিয়ে সেট বানানো: Σ*, balanced string, formula, tree
  • Structural induction দিয়ে পূর্ণ প্রমাণ লেখা, আর ভুল প্রমাণে বাদ পড়া ক্ষেত্র ধরা
  • Full binary tree-এ n(T) ≤ 2h(T)+1 − 1, Fibonacci-র বৃদ্ধি আর Cassini-র সূত্রের প্রমাণ
  • ℕ×ℕ-এ lexicographic induction, Ackermann function আর recursive algorithm-এর সঠিকতা

১ · নিজের ছোট রূপ দিয়ে সংজ্ঞা — recursive function

বাজারঘরের মেনুতে “ইলেকট্রনিক্স” বিভাগের ভেতরে “মোবাইল”, তার ভেতরে “চার্জার”, তার ভেতরে আবার “দ্রুত চার্জার”। বিভাগের সংজ্ঞা দিতে গেলে স্বাভাবিকভাবে বলি: “একটি বিভাগ হলো একটি নাম আর কয়েকটি পণ্য, সঙ্গে শূন্য বা বেশি বিভাগ।” সংজ্ঞার ভেতরেই সংজ্ঞায়িত শব্দটি এলো। এটি চক্রাকার যুক্তি নয়, কারণ ভেতরের বিভাগগুলো ছোট, আর একসময় পণ্য ছাড়া আর কিছু থাকে না। এই ধরনের সংজ্ঞাকে বলে recursive definition (পুনরাবৃত্তিমূলক সংজ্ঞা)।

পারিবারিক বংশতালিকা মনে করুন। “আমার পূর্বপুরুষ” মানে আমার বাবা বা মা, অথবা আমার কোনো পূর্বপুরুষের বাবা বা মা। প্রথম অংশ সরাসরি উত্তর দেয়; দ্বিতীয় অংশ এক প্রজন্ম পিছিয়ে একই প্রশ্ন করে। প্রতিবার এক ধাপ পেছনে যাওয়া, এটিই recursion-এর মূল চাল।

সংজ্ঞা

ℕ = {0, 1, 2, …}-এর ওপর একটি function f-এর recursive সংজ্ঞায় দুটি অংশ থাকে: basis step, যেখানে f(0) (বা প্রথম কয়েকটি মান) সরাসরি দেওয়া; আর recursive step, যেখানে n > 0-এর জন্য f(n)-কে আগের মান f(0), …, f(n − 1) দিয়ে লেখার নিয়ম। প্রোগ্রামিংয়ে প্রথমটিকে বলে base case, দ্বিতীয়টিকে recursive case.

তিনটি পরিচিত উদাহরণ:

  • Factorial: 0! = 1; n! = n · (n − 1)! যখন n ≥ 1. তাই 4! = 4 · 3! = 4 · 3 · 2! = … = 24.
  • Fibonacci: f(0) = 0, f(1) = 1; f(n) = f(n − 1) + f(n − 2) যখন n ≥ 2. এখানে দুটি basis মান লাগে, কারণ নিয়মটি দুই ধাপ পেছনে তাকায়। প্রথম কয়েকটি: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89.
  • GCD (গরিষ্ঠ সাধারণ গুণনীয়ক): gcd(a, 0) = a; gcd(a, b) = gcd(b, a mod b) যখন b > 0. যেমন gcd(252, 198) = gcd(198, 54) = gcd(54, 36) = gcd(36, 18) = gcd(18, 0) = 18. এই নিয়ম কেন ঠিক, তা পর্ব ২১ “GCD আর Euclid-এর algorithm”-এর বিষয়; এখানে শুধু এর আকারটি দেখুন।

সংজ্ঞা থেকে সূত্র — অনুমান, তারপর প্রমাণ

বাজারঘরের গুদামে প্যাকিংয়ের একটি নিয়ম: n স্তরের একটি চালান সাজাতে প্রথমে n − 1 স্তরের চালান দুবার সাজাতে হয়, আর মাঝে একটি বাড়তি কাজ। কাজের সংখ্যা c(0) = 0, c(n) = 2c(n − 1) + 1 (n ≥ 1). এটি একটি recursion, কিন্তু c(20) জানতে কুড়িবার হিসাব করতে হয়। বন্ধ সূত্র কি আছে? প্রথমে ছোট মান: c(0) = 0, c(1) = 1, c(2) = 3, c(3) = 7, c(4) = 15. প্রতিটি একটি দুইয়ের ঘাতের চেয়ে ১ কম। অনুমান: c(n) = 2n − 1.

  1. Basis: c(0) = 0 = 20 − 1.
  2. ধরি কোনো k ≥ 0-এর জন্য c(k) = 2k − 1 (inductive hypothesis).
  3. সংজ্ঞায় c(k + 1) = 2c(k) + 1 = 2(2k − 1) + 1 = 2k+1 − 2 + 1 = 2k+1 − 1. ∎

এটিই এই মডিউলের কাজের ধারা: ছোট ক্ষেত্রে হিসাব, প্যাটার্ন দেখে অনুমান, তারপর induction-এ প্রমাণ। c(20) = 220 − 1 = 1048575, এক লাইনে। এই recurrence-টি Tower of Hanoi-র চালের সংখ্যাও; recurrence থেকে মডেল বানানো পর্ব ৪৪-এর বিষয়।

Factorial-এ n কমে n − 1 হয়; Fibonacci-তে n − 1 আর n − 2; gcd-তে দ্বিতীয় argument b থেকে a mod b হয়, যা b-এর চেয়ে ছোট। তিন ক্ষেত্রেই কিছু একটা অঋণাত্মক পূর্ণসংখ্যা প্রতিবার কমে। অঋণাত্মক পূর্ণসংখ্যা অসীমবার কমতে পারে না, তাই recursion একসময় basis-এ পৌঁছায়। এই যুক্তির নাম well-ordering principle: ℕ-এর প্রতিটি অশূন্য উপসেটে একটি ক্ষুদ্রতম উপাদান আছে। এর বিস্তারিত আর induction-এর সঙ্গে এর সম্পর্ক পর্ব ১১ “Strong induction আর well-ordering”-এ আছে।

উপপাদ্য ১.১ (recursive সংজ্ঞা well-defined)

ধরুন f(0) = c দেওয়া, আর প্রতিটি n ≥ 1-এর জন্য f(n) = g(n, f(n − 1)), যেখানে g একটি নির্দিষ্ট function. তাহলে ঠিক একটি function f : ℕ → ℝ আছে যা এই দুই শর্ত মানে।

প্রমাণ (uniqueness অংশ, induction দিয়ে): ধরি f আর f′ দুটোই শর্ত মানে। P(n): “f(n) = f′(n)”.

  1. Basis: f(0) = c = f′(0), দুটোই প্রথম শর্ত থেকে। তাই P(0) সত্য।
  2. ধরি কোনো k ≥ 0-এর জন্য P(k) সত্য, অর্থাৎ f(k) = f′(k) (inductive hypothesis).
  3. দ্বিতীয় শর্তে f(k + 1) = g(k + 1, f(k)) আর f′(k + 1) = g(k + 1, f′(k)).
  4. ধাপ ২ অনুযায়ী g-এর দুই argument একই, আর g একটি function, তাই দুই মান সমান: P(k + 1) সত্য।
  5. Mathematical induction (পর্ব ১০) অনুযায়ী সব n-এর জন্য P(n) সত্য, তাই f = f′. ∎

অস্তিত্বের অংশটিও (এমন f আসলে আছে) induction দিয়ে প্রমাণ করা যায়: প্রতিটি n-এর জন্য “0 থেকে n পর্যন্ত শর্ত মানে এমন একটিমাত্র মান-তালিকা আছে” দেখিয়ে তালিকাগুলো জোড়া দেওয়া হয়। আমরা সেটি বাদ রাখছি; সেট-তত্ত্বের বইয়ে এটি “recursion theorem” নামে পাবেন।

সব “recursive-দেখতে” সংজ্ঞা function দেয় না। f(n) = f(n + 1) + 1 লিখলে recursion বড় সংখ্যার দিকে যায়, কখনো basis-এ পৌঁছায় না। আবার f(0) = 1, f(n) = 2 · f(n − 2) লিখলে f(1) কোথাও থেকে আসে না: n − 2 = −1 ℕ-এ নেই। প্রতিটি recursive সংজ্ঞায় দুটি প্রশ্ন করুন: প্রতিটি ডাক কি basis-এর দিকে কমছে, আর basis কি সব শাখাকে ঢাকছে?

এই পর্বে Python-এ recursion লেখার কৌশল শেখানো হবে না; Python-এ function আর recursion-এর প্রথম পরিচয় Python প্রোগ্রামিং কোর্সের পর্ব ১৩-এ, আর call stack-সহ পূর্ণ আলোচনা DSA কোর্সের পর্ব ৭-এ। আমাদের কাজ এর গণিত: সংজ্ঞা কখন অর্থপূর্ণ, আর তার ওপর দাবি কীভাবে প্রমাণ করব।

২ · Recursive সেট — basis, নিয়ম আর “আর কিছু নয়”

Function-এর মতো সেটও recursive ভাবে বানানো যায়। একটি recursively defined set-এর সংজ্ঞায় তিনটি অংশ:

  1. Basis step: কয়েকটি নির্দিষ্ট উপাদান সরাসরি সেটে আছে।
  2. Recursive step: সেটে থাকা উপাদান থেকে নতুন উপাদান বানানোর নিয়ম।
  3. Exclusion rule: প্রথম দুই অংশ থেকে সসীম সংখ্যক ধাপে যা পাওয়া যায়, শুধু তা-ই সেটে আছে; আর কিছু নয়।

তৃতীয় অংশ সাধারণত লেখা হয় না, তবু সবসময় ধরা থাকে। এটি না থাকলে “সব string-এর সেট”ও যেকোনো নিয়ম মেনে চলত। Exclusion rule-ই structural induction-এর ভিত্তি, পরের অংশে দেখবেন।

উদাহরণ ১ · একটি alphabet-এর ওপর সব string: Σ*

একটি alphabet Σ হলো চিহ্নের একটি সসীম সেট, যেমন Σ = {a, b}, অথবা বাজারঘরের কুপন কোডের অক্ষর-সংখ্যা। Σ-এর ওপর সব string-এর সেট Σ* (পড়া হয় “সিগমা স্টার”; * চিহ্নের নাম Kleene star):

  • Basis: λ ∈ Σ*, যেখানে λ হলো empty string (শূন্য দৈর্ঘ্যের string; কোনো কোনো বইয়ে ε)।
  • Recursive step: w ∈ Σ* আর x ∈ Σ হলে wx ∈ Σ* (w-এর শেষে x বসানো)।

Σ*-এর ওপর function-ও recursive ভাবে দেওয়া যায়, নিয়ম ধরে ধরে। দৈর্ঘ্য: l(λ) = 0, l(wx) = l(w) + 1. দুই string জোড়া দেওয়া, অর্থাৎ concatenation: w · λ = w, আর w · (vx) = (w · v)x.

উদাহরণ ২ · Balanced parentheses

বাজারঘরের অনুসন্ধান-বাক্সে কেউ লিখলেন (মোবাইল AND (চার্জার OR কেবল)). Query ঠিকঠাক কি না জানতে প্রথমে দেখতে হয় বন্ধনী মেলে কি না। Balanced parentheses-এর সেট B:

  • Basis: λ ∈ B.
  • Recursive step (নিয়ম ক): x ∈ B হলে (x) ∈ B.
  • Recursive step (নিয়ম খ): x, y ∈ B হলে xy ∈ B.

যেমন () ∈ B, (()) ∈ B, ()() ∈ B. কিন্তু )( বা (() কোনো নিয়মে তৈরি হয় না, তাই exclusion rule অনুযায়ী B-তে নেই। কোনো string B-তে আছে দেখাতে হলে নিয়মের একটি শিকল দেখাতে হয়, যাকে বলে derivation. “পরে” চাপুন:

1λ          basis2()         নিয়ম ক, 13(())       নিয়ম ক, 24(())()     নিয়ম খ, 3, 2
এই ধাপ
নিয়ম
ইনপুট
নতুন string
দৈর্ঘ্য
  1. Basis. λ সংজ্ঞা অনুযায়ী B-তে আছে। এখান থেকেই সব শুরু; এর দৈর্ঘ্য 0.
  2. নিয়ম ক, x = λ. (x)-এ x = λ বসালে পাই (). এটি লাইন ১ থেকে এলো, তাই () ∈ B.
  3. নিয়ম ক আবার, x = (). লাইন ২-এর string-কে বন্ধনীতে মুড়ে পাই (()). দৈর্ঘ্য 2 থেকে 4 হলো: নিয়ম ক সবসময় ঠিক দুটি চিহ্ন যোগ করে।
  4. নিয়ম খ-এর প্রস্তুতি. নিয়ম খ দুটি আগে থেকে তৈরি string চায়। আমাদের আছে লাইন ৩-এর (()) আর লাইন ২-এর (). একই string দুবার ব্যবহার করা চলে।
  5. নিয়ম খ, x = (()), y = (). পাশাপাশি বসিয়ে পাই (())(). দৈর্ঘ্য 4 + 2 = 6.
  6. ফল. চার লাইনে (())() ∈ B প্রমাণিত। খেয়াল করুন, প্রতিটি লাইনে দৈর্ঘ্য জোড় থেকেছে। এটি কাকতালীয় নয়; পরের অংশে প্রমাণ করব যে B-এর প্রতিটি string-এর দৈর্ঘ্য জোড়।

উদাহরণ ৩ · Well-formed formula

পর্ব ০২-এ যে formula লিখেছেন, তার “ঠিক লেখা” রূপগুলোর সেটও recursive. Propositional logic-এর well-formed formula (wff):

  • Basis: T, F আর প্রতিটি propositional variable (p, q, r, …) wff.
  • Recursive step: E আর F′ wff হলে (¬E), (E ∧ F′), (E ∨ F′), (E → F′), (E ↔ F′)-ও wff.

তাই ((p ∧ (¬q)) → r) একটি wff, কিন্তু p ∧ ∨ q নয়। Programming language-এর syntax-ও ঠিক এভাবে লেখা হয় (BNF grammar); এর পূর্ণ আলোচনা পর্ব ৭৩ “Grammar আর formal language”-এ।

উদাহরণ ৪ · Full binary tree

একটি full binary tree-এ প্রতিটি node-এর হয় শূন্য, নয়তো ঠিক দুটি সন্তান। Recursive সংজ্ঞা:

  • Basis: একটিমাত্র node (root) একটি full binary tree.
  • Recursive step: T1 আর T2 full binary tree হলে একটি নতুন root r নিয়ে, r-এর বাঁ দিকে T1 আর ডান দিকে T2 জুড়ে যে tree হয়, সেটিও full binary tree. একে লিখব T1 · T2.

Full binary tree তৈরির দুই নিয়ম

Basis: one node · Recursive step: new root over T1 and T2

Recursive definition of full binary trees • r T1 T2 basis recursive step h = 0 n = 1 h = 1 + max(h1, h2) n = 1 + n1 + n2
ছবি ২.১ — প্রতিটি full binary tree হয় একটি একক node, নয়তো একটি নতুন root-এর নিচে দুটি ছোট full binary tree. Height h আর node-সংখ্যা n-এর সংজ্ঞাও এই দুই নিয়ম ধরে চলে।

এই সংজ্ঞার সঙ্গেই দুটি function আসে, নিয়ম ধরে ধরে: height h(T) আর node-সংখ্যা n(T). Basis-এ h = 0, n = 1; recursive step-এ h(T1 · T2) = 1 + max(h(T1), h(T2)) আর n(T1 · T2) = 1 + n(T1) + n(T2). সেটের নিয়ম ধরে function-এর এমন সংজ্ঞাকে বলে structural recursion. Tree-এর সাধারণ তত্ত্ব পর্ব ৬৫ “Tree”-তে আসবে।

উদাহরণ ৫ · বাজারঘরের মেনু

শুরুর মেনুর প্রশ্নে ফিরি। মেনুর বিভাগের সেট M: basis, শুধু পণ্য আছে এমন একটি বিভাগ (উপবিভাগ নেই) M-এ আছে; recursive step, M1, …, Mk ∈ M (k ≥ 1) হলে এদের উপবিভাগ হিসেবে নিয়ে একটি নতুন বিভাগও M-এ আছে। এখানে নিয়মটি যেকোনো সংখ্যক ইনপুট নেয়, তবু structural induction একইভাবে চলে। দুটি function: গভীরতা d (basis-এ 0; নইলে 1 + সবচেয়ে গভীর উপবিভাগের d) আর মোট বিভাগ-সংখ্যা c (basis-এ 1; নইলে 1 + c(M1) + … + c(Mk)).

উপপাদ্য ২.১

প্রতিটি মেনু-বিভাগ X-এর জন্য c(X) ≥ d(X) + 1.

  1. Basis: c = 1 আর d = 0, তাই 1 ≥ 0 + 1.
  2. Recursive step: X-এর উপবিভাগ M1, …, Mk, প্রতিটিতে দাবি সত্য ধরি। Mj সবচেয়ে গভীর উপবিভাগ, তাই d(X) = 1 + d(Mj).
  3. সব c মান ধনাত্মক, তাই c(X) = 1 + Σ c(Mi) ≥ 1 + c(Mj) ≥ 1 + d(Mj) + 1 = d(X) + 1. ∎

অর্থাৎ ৫ স্তর গভীর মেনুতে অন্তত ৬টি বিভাগ থাকবেই; সমতা হয় যখন মেনু একটি সরল শিকল, প্রতিটি বিভাগে একটিমাত্র উপবিভাগ।

৩ · Structural induction — প্রতিটি নিয়মের জন্য একটি ক্ষেত্র

Recursive সেটের প্রতিটি উপাদানের ওপর দাবি প্রমাণ করার পদ্ধতি structural induction. এটি পর্ব ১০-এর mathematical induction-এর সরাসরি সম্প্রসারণ: সেখানে ℕ-কে ভাবা হয় “0 আছে; n থাকলে n + 1 আছে” দিয়ে বানানো সেট হিসেবে।

Structural induction-এর নীতি

ধরুন S একটি recursively defined set, আর P(x) S-এর উপাদান সম্পর্কে একটি দাবি। যদি

(১) Basis: basis step-এর প্রতিটি উপাদান x-এর জন্য P(x) সত্য, এবং

(২) Recursive step: প্রতিটি নিয়মের জন্য, নিয়মের ইনপুট উপাদানগুলোতে P সত্য ধরে নিলে (এটিই inductive hypothesis) নিয়মের তৈরি নতুন উপাদানেও P সত্য,

তাহলে S-এর প্রতিটি x-এর জন্য P(x) সত্য।

কেন এটি valid: exclusion rule অনুযায়ী S-এর প্রতিটি উপাদান সসীম সংখ্যক নিয়ম-প্রয়োগে তৈরি। Q(n): “যে উপাদান সর্বোচ্চ n ধাপের derivation-এ তৈরি হয়, তাতে P সত্য”। (১) থেকে Q(0); (২) থেকে Q(0), …, Q(n) সত্য হলে Q(n + 1), কারণ শেষ ধাপের ইনপুটগুলো আরও কম ধাপে তৈরি। তাই strong induction-এ সব n-এর জন্য Q(n), অর্থাৎ সব উপাদানে P. মানে structural induction নতুন কোনো স্বতঃসিদ্ধ নয়; derivation-এর দৈর্ঘ্যের ওপর সাধারণ induction-ই।

একটি ইটের দেয়াল ভাবুন, যেখানে প্রতিটি ইট হয় ভিতের ওপর, নয়তো আগে গাঁথা ইটের ওপর বসানো। আপনি যদি নিশ্চিত হন ভিতের প্রতিটি ইট মজবুত, আর রাজমিস্ত্রির প্রতিটি গাঁথুনির কৌশল মজবুত ইটের ওপর মজবুত ইটই বসায়, তাহলে দেয়ালের প্রতিটি ইট মজবুত; একটি একটি করে পরীক্ষা করতে হয় না। কিন্তু রাজমিস্ত্রির একটি কৌশলও যদি পরীক্ষা বাদ পড়ে, সেই কৌশলে বসানো ইট সম্পর্কে কিছুই বলা যায় না।

উপপাদ্য ৩.১

B-এর (balanced parentheses) প্রতিটি string-এর দৈর্ঘ্য জোড়।

প্রমাণ লেখার আগে নিয়ম তিনটির দিকে তাকান: basis-এর দৈর্ঘ্য 0, নিয়ম ক 2 যোগ করে, নিয়ম খ দুটি দৈর্ঘ্য যোগ করে। প্রমাণটি ঠিক এই তিন পর্যবেক্ষণ, তিন ক্ষেত্রে সাজানো। Stepper-এ প্রমাণটি ধাপে ধাপে, পাশে (())()-এর ওপর মিলিয়ে দেখা:

1P(s): l(s) জোড়2basis: s = λ3নিয়ম ক: s = (x)4নিয়ম খ: s = xy
এই ধাপ
ধরে নিচ্ছি
দেখাতে হবে
(())()-তে
  1. দাবি ঠিক করা। P(s): “s-এর দৈর্ঘ্য l(s) জোড়”, অর্থাৎ কোনো পূর্ণসংখ্যা k-এর জন্য l(s) = 2k. B-এর সংজ্ঞায় একটি basis আর দুটি নিয়ম, তাই প্রমাণে তিনটি ক্ষেত্র লাগবে। উদাহরণ (())()-এর দৈর্ঘ্য 6.
  2. Basis: s = λ. l(λ) = 0 = 2 · 0, তাই P(λ) সত্য। এখানে কিছু ধরে নিতে হয়নি।
  3. নিয়ম ক-এর inductive hypothesis. ধরি x ∈ B আর P(x) সত্য: কোনো পূর্ণসংখ্যা a-এর জন্য l(x) = 2a. নতুন string s = (x). উদাহরণে x = ().
  4. নিয়ম ক-এর ধাপ। s-এ x-এর সব চিহ্ন আর দুটি নতুন বন্ধনী, তাই l(s) = l(x) + 2 = 2a + 2 = 2(a + 1), জোড়। উদাহরণে (())-এর দৈর্ঘ্য 2 + 2 = 4.
  5. নিয়ম খ। ধরি x, y ∈ B আর P(x), P(y) সত্য: l(x) = 2a, l(y) = 2b. s = xy-এর দৈর্ঘ্য l(x) + l(y) = 2a + 2b = 2(a + b), জোড়। উদাহরণে x = (()), y = (): 4 + 2 = 6.
  6. উপসংহার। Basis আর দুই নিয়মের প্রতিটিতে P টিকে থাকে। Structural induction-এর নীতি অনুযায়ী B-এর প্রতিটি s-এর জন্য P(s) সত্য। ∎ (উল্টো দাবি, “প্রতিটি জোড় দৈর্ঘ্যের string balanced”, মিথ্যা: )( এর counterexample.)

এবার Σ*-এ একটি প্রমাণ, যেখানে recursion চলে দ্বিতীয় string-এর ওপর। এটি দেখায়, কোন variable-এর ওপর induction চালাবেন, সেটি বাছাই করাও প্রমাণের অংশ।

উপপাদ্য ৩.২

Σ*-এর যেকোনো w আর v-এর জন্য l(w · v) = l(w) + l(v).

প্রমাণ: w স্থির রেখে v-এর ওপর structural induction. P(v): “l(w · v) = l(w) + l(v)”.

  1. Basis, v = λ: concatenation-এর সংজ্ঞায় w · λ = w, তাই l(w · λ) = l(w) = l(w) + 0 = l(w) + l(λ).
  2. Recursive step: ধরি v ∈ Σ*, x ∈ Σ, আর P(v) সত্য। দেখাতে হবে P(vx).
  3. Concatenation-এর সংজ্ঞায় w · (vx) = (w · v)x, তাই দৈর্ঘ্যের সংজ্ঞায় l(w · (vx)) = l(w · v) + 1.
  4. Inductive hypothesis-এ l(w · v) = l(w) + l(v), তাই এটি l(w) + l(v) + 1.
  5. দৈর্ঘ্যের সংজ্ঞায় l(vx) = l(v) + 1, তাই মান l(w) + l(vx). P(vx) সত্য। ∎

w-এর ওপর induction চালাতে চাইলে আটকে যেতেন: সংজ্ঞা শেষ অক্ষর ধরে ভাঙে, আর w-এর শেষ অক্ষর w · v-এর শেষে থাকে না। সংজ্ঞা যেদিক থেকে ভাঙে, induction-ও সেদিক থেকে চালান।

পরের উপপাদ্যটি দেখায়, কেন )( বা ())(() B-তে থাকতে পারে না। একটি string s-এর শুরুর অংশ (prefix) মানে s-এর প্রথম কয়েকটি চিহ্ন, শূন্যটি বা সবগুলো হতেও পারে।

উপপাদ্য ৩.৩

B-এর প্রতিটি s-এর প্রতিটি prefix-এ “(”-এর সংখ্যা ≥ “)”-এর সংখ্যা, আর পুরো s-এ দুটি সংখ্যা সমান।

প্রমাণ: d(u) = u-তে “(”-এর সংখ্যা − “)”-এর সংখ্যা। P(s): s-এর প্রতিটি prefix u-তে d(u) ≥ 0, আর d(s) = 0.

  1. Basis s = λ: একমাত্র prefix λ, d(λ) = 0. P(λ) সত্য।
  2. নিয়ম ক, s = (x), P(x) ধরে: s-এর prefix হয় λ (d = 0), নয়তো “(” আর তারপর x-এর একটি prefix v (d = 1 + d(v) ≥ 1), নয়তো পুরো s (d = 1 + d(x) − 1 = 0). সবগুলো ≥ 0, আর d(s) = 0.
  3. নিয়ম খ, s = xy, P(x) আর P(y) ধরে: s-এর prefix হয় x-এর একটি prefix (d ≥ 0, P(x) থেকে), নয়তো x-এর পরে y-এর একটি prefix v (d = d(x) + d(v) = 0 + d(v) ≥ 0). আর d(s) = d(x) + d(y) = 0.
  4. সব ক্ষেত্রে P টিকে থাকে, তাই B-এর প্রতিটি s-এ P(s) সত্য। ∎

())(()-এর prefix ())-এ d = 1 − 2 = −1 < 0, তাই এটি B-তে নেই, যদিও দৈর্ঘ্য জোড় আর মোট বন্ধনী সমান। উল্টো দিকটিও সত্য (এই দুই শর্ত মানা প্রতিটি string B-তে আছে), তবে তার প্রমাণ string-এর দৈর্ঘ্যের ওপর strong induction, আমরা বাদ রাখছি। আমাদের scratch হিসাবে দৈর্ঘ্য 8 পর্যন্ত সব বন্ধনী-string-এ দুই বর্ণনা হুবহু মিলেছে।

খুঁজে দেখা — Python দিয়ে ছোট ক্ষেত্র

প্রমাণ লেখার আগে দাবিটি ছোট ক্ষেত্রে পরীক্ষা করা ভালো অভ্যাস। নিচের ব্লক নিয়ম অনুসরণ করেই Σ* আর B তৈরি করে (দৈর্ঘ্য 8 পর্যন্ত), দাবিগুলো যাচাই করে, আর একটি মিথ্যা দাবির counterexample খোঁজে। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।

from collections import Counter

# Σ* over {a, b} by the rules: basis λ, step w -> wx
level, sigma = [""], [""]
for _ in range(8):
    level = [w + x for w in level for x in "ab"]
    sigma += level
print("Σ* up to length 8:", len(sigma), "strings; 2^9 - 1 =", 2**9 - 1)

# B: basis λ; rule A: x -> (x); rule B: x, y -> xy
def balanced_upto(L):
    B = {""}
    while True:
        new = set(B)
        new |= {"(" + x + ")" for x in B if len(x) + 2 <= L}
        new |= {x + y for x in B for y in B if len(x + y) <= L}
        if new == B:              # closed under both rules
            return B
        B = new

B = balanced_upto(8)
print("length: count", sorted(Counter(map(len, B)).items()))
print("every length even?", all(len(s) % 2 == 0 for s in B))

# false claim: "every nonempty s in B looks like (x) with x in B"
bad = sorted((s for s in B if s and not (s[0] == "(" and
              s[-1] == ")" and s[1:-1] in B)), key=lambda s: (len(s), s))
print("counterexamples:", bad[:3], "... total", len(bad))

আসল আউটপুট (ব্রাউজারের Python):

Σ* up to length 8: 511 strings; 2^9 - 1 = 511
length: count [(0, 1), (2, 1), (4, 2), (6, 5), (8, 14)]
every length even? True
counterexamples: ['()()', '(())()', '()(())'] ... total 13

Σ*-এ দৈর্ঘ্য k-এর string 2kটি, তাই 0 থেকে 8 পর্যন্ত মোট 1 + 2 + … + 28 = 29 − 1 = 511. B-তে দৈর্ঘ্য 0, 2, 4, 6, 8-এ যথাক্রমে 1, 1, 2, 5, 14টি string; এগুলো Catalan সংখ্যা, যার সূত্র পর্ব ৫০ “বিশেষ সংখ্যা”-য় আসবে। বিজোড় দৈর্ঘ্যের একটিও নেই, যা উপপাদ্য ৩.১-এর সঙ্গে মেলে। তবে খেয়াল করুন: এই পরীক্ষা শুধু দৈর্ঘ্য 8 পর্যন্ত; প্রমাণটিই সব দৈর্ঘ্যের নিশ্চয়তা দেয়। শেষ লাইনটি পরের অংশের ভুল প্রমাণের জন্য।

ভুল প্রমাণ — কোন ক্ষেত্র বাদ পড়েছে?

একজন শিক্ষার্থী লিখলেন: “দাবি: B-এর প্রতিটি অশূন্য string দেখতে (x), যেখানে x ∈ B. প্রমাণ: structural induction. Basis λ অশূন্য নয়, তাই কিছু দেখাতে হবে না। নিয়ম ক: নতুন string (x), x ∈ B; এটি সংজ্ঞা অনুযায়ীই (x) আকারের। ∎”

ভুলটি: B-এর সংজ্ঞায় দুটি নিয়ম, কিন্তু প্রমাণে শুধু নিয়ম ক. নিয়ম খ-এর ক্ষেত্র (s = xy) একেবারে বাদ। সেই ক্ষেত্রে প্রমাণ চালানোই যায় না, কারণ দাবিটি মিথ্যা: ওপরের ব্লক ছোট-থেকে-বড় ক্রমে প্রথম counterexample দিয়েছে ()(). এটি (x) আকারের হতে হলে x = )( হতো, যা B-তে নেই। Structural induction-এ প্রতিটি নিয়মের জন্য একটি ক্ষেত্র লিখুন, আর প্রমাণ শেষে নিয়মের তালিকার সঙ্গে ক্ষেত্রের তালিকা মিলিয়ে নিন।

৪ · Full binary tree — node আর height-এর সম্পর্ক

বাজারঘরের পণ্য-অনুসন্ধানের একটি সিদ্ধান্ত-গাছ ভাবুন: প্রতিটি প্রশ্নে দুটি উত্তর (হ্যাঁ/না), আর শেষে একটি পণ্য-তালিকা। এটি একটি full binary tree. গাছের height মানে সর্বোচ্চ কয়টি প্রশ্ন করতে হয়। প্রশ্ন: h প্রশ্নের মধ্যে গাছে সর্বোচ্চ কয়টি node রাখা যায়? প্রথমে ছোট ক্ষেত্র: h = 0-এ 1টি node; h = 1-এ 3টি; h = 2-এ বড়জোর 7টি। অনুমান: 2h+1 − 1.

উপপাদ্য ৪.১

প্রতিটি full binary tree T-এর জন্য n(T) ≤ 2h(T)+1 − 1.

প্রমাণ (structural induction):

  1. Basis: T একক node. n(T) = 1, h(T) = 0, আর 20+1 − 1 = 1. তাই 1 ≤ 1, দাবি সত্য।
  2. Recursive step: T = T1 · T2. Inductive hypothesis: n(T1) ≤ 2h(T1)+1 − 1 আর n(T2) ≤ 2h(T2)+1 − 1.
  3. n-এর recursive সংজ্ঞায় n(T) = 1 + n(T1) + n(T2).
  4. Inductive hypothesis বসিয়ে: n(T) ≤ 1 + (2h(T1)+1 − 1) + (2h(T2)+1 − 1).
  5. M = max(h(T1), h(T2)) লিখি। 2x বর্ধমান, তাই প্রতিটি পদ ≤ 2M+1; ফলে n(T) ≤ 2 · 2M+1 − 1 = 2M+2 − 1.
  6. h-এর সংজ্ঞায় h(T) = 1 + M, তাই M + 2 = h(T) + 1, আর n(T) ≤ 2h(T)+1 − 1. ∎

ধাপ ৫-ই প্রমাণের আসল চাবি: দুটি ভিন্ন height-কে বড়টি দিয়ে ঢাকা। সমতা হয় ঠিক তখন, যখন দুই subtree একই height-এর আর নিজেরাও “পূর্ণ” (perfect binary tree). নিচের ব্লক height 3 পর্যন্ত সব full binary tree বানিয়ে সীমাটি পরীক্ষা করে:

from collections import Counter

# () is a single node; (T1, T2) is a new root over T1 and T2
def trees(h):                  # all full binary trees of height <= h
    if h == 0:
        return [()]
    smaller = trees(h - 1)
    return [()] + [(a, b) for a in smaller for b in smaller]

def n(t):
    return 1 if t == () else 1 + n(t[0]) + n(t[1])

def ht(t):
    return 0 if t == () else 1 + max(ht(t[0]), ht(t[1]))

def leaves(t):
    return 1 if t == () else leaves(t[0]) + leaves(t[1])

T = trees(3)
print("full binary trees with height <= 3:", len(T))
for h in range(4):
    sizes = Counter(n(t) for t in T if ht(t) == h)
    print(f"h={h}: n -> count {dict(sorted(sizes.items()))}",
          f"| bound {2 ** (h + 1) - 1}")
print("n(T) <= 2^(h+1) - 1 for all?",
      all(n(t) <= 2 ** (ht(t) + 1) - 1 for t in T))
print("leaves = internal + 1 for all?",
      all(leaves(t) == n(t) - leaves(t) + 1 for t in T))

আসল আউটপুট (ব্রাউজারের Python):

full binary trees with height <= 3: 26
h=0: n -> count {1: 1} | bound 1
h=1: n -> count {3: 1} | bound 3
h=2: n -> count {5: 2, 7: 1} | bound 7
h=3: n -> count {7: 4, 9: 6, 11: 6, 13: 4, 15: 1} | bound 15
n(T) <= 2^(h+1) - 1 for all? True
leaves = internal + 1 for all? True

Height 3 পর্যন্ত মোট ২৬টি full binary tree. প্রতিটি height-এ সর্বোচ্চ node-সংখ্যা ঠিক সীমার সমান (1, 3, 7, 15), আর তা একটি গাছেই ঘটে: perfect tree. খেয়াল করুন, node-সংখ্যা সবসময় বিজোড়; শেষ লাইনের সম্পর্ক “leaf = ভেতরের node + 1” থেকেই তা আসে, যার প্রমাণ অনুশীলন ১-এ। ফাংশন trees নিজেই recursive সংজ্ঞাটির হুবহু অনুবাদ: height h-এর গাছ বানাতে height < h-এর গাছ জোড়ায় জোড়ায় নেওয়া।

নিচের সীমা আর একটি পরীক্ষার হিসাব

ওপরের সীমার পাশাপাশি একটি নিচের সীমাও আছে: n(T) ≥ 2h(T) + 1. Basis-এ 1 ≥ 0 + 1. Recursive step-এ ধরি h(T1) ≥ h(T2) (অন্যথায় নাম বদলে নিন; এটি “without loss of generality”-র সঠিক ব্যবহার, কারণ দাবিটি দুই subtree-তে প্রতিসম)। তাহলে h(T) = 1 + h(T1), আর hypothesis থেকে n(T) = 1 + n(T1) + n(T2) ≥ 1 + (2h(T1) + 1) + 1 = 2(h(T1) + 1) + 1 = 2h(T) + 1, যেখানে n(T2) ≥ 1 ব্যবহার হয়েছে। ∎

পরীক্ষার ধাঁচের প্রশ্ন: height 3-এর full binary tree-তে কয়টি node থাকতে পারে? দুই সীমা মিলিয়ে 7 ≤ n ≤ 15, আর অনুশীলন ১-এর ফল অনুযায়ী n বিজোড়। তাই সম্ভাব্য মান 7, 9, 11, 13, 15; full_trees.py-র h = 3 সারিতে ঠিক এই পাঁচটি মানই এসেছে। Height 4-এ ৮টি node অসম্ভব, কারণ 8 < 2 · 4 + 1 = 9.

৫ · Structural recursion-এ মূল্যায়ন — একটি formula-র মান

Recursive সেটের ওপর function সংজ্ঞায়িত করতে প্রতিটি নিয়মের জন্য একটি সূত্র দিলেই হয়। Wff-এর মান (truth value) তার একটি উদাহরণ: variable-এর মান দেওয়া থাকলে v(p) টেবিল থেকে; v((¬E)) = ¬v(E); v((E ∧ F′)) = v(E) ∧ v(F′); বাকি connective-গুলোও একইভাবে। Python interpreter, ক্যালকুলেটর আর spreadsheet ঠিক এভাবে expression-এর মান বের করে: ভেতর থেকে বাইরে।

একটি সূক্ষ্ম কথা: এই সংজ্ঞা function দেয় কারণ প্রতিটি wff একটিমাত্র উপায়ে নিয়ম থেকে তৈরি (unique readability). বন্ধনী না থাকলে p ∧ q ∨ r দুইভাবে পড়া যেত, আর মান দুই রকম হতে পারত। বাজারঘরের একটি কুপন-নিয়ম: “(নতুন গ্রাহক এবং ঢাকার বাইরে নয়) হলে (প্রি-অর্ডার অথবা ঢাকার বাইরে)”। p = নতুন গ্রাহক (T), q = ঢাকার বাইরে (F), r = প্রি-অর্ডার (F):

1p, q, r        variable2(¬q)            ¬ নিয়ম3(p ∧ (¬q))      ∧ নিয়ম4(r ∨ q)         ∨ নিয়ম5(… → …)         → নিয়ম
এই ডাক
subformula
নিয়ম
মান
  1. Basis: variable. পাতাগুলোর মান সরাসরি টেবিল থেকে: v(p) = T, v(q) = F, v(r) = F. Recursion এখানে থামে।
  2. (¬q). ¬ নিয়ম: v((¬q)) = ¬v(q) = ¬F = T. এই ডাকটি একটি ছোট subformula q-কে ডেকেছে।
  3. (p ∧ (¬q)). ∧ নিয়ম: v(p) ∧ v((¬q)) = T ∧ T = T. বাঁ দিকের বড় অংশ শেষ।
  4. ডান অংশের পাতা। (r ∨ q)-এর দুই অংশ variable: v(r) = F, v(q) = F. আবার basis.
  5. (r ∨ q). ∨ নিয়ম: F ∨ F = F.
  6. পুরো formula. → নিয়ম: v = v(বাঁ) → v(ডান) = T → F = F. শর্ত সত্য অথচ ফল মিথ্যা, তাই implication মিথ্যা।
  7. ফল। এই গ্রাহকের জন্য কুপন-নিয়মটি ভাঙে। প্রতিটি ডাক একটি ছোট subformula-তে গেছে, তাই মূল্যায়ন থামবেই; ঠিক এই কারণেই structural induction দিয়ে মূল্যায়নকারীর সঠিকতাও প্রমাণ করা যায়।
উপপাদ্য ৫.১

প্রতিটি wff-এ বাঁ বন্ধনী “(” আর ডান বন্ধনী “)”-এর সংখ্যা সমান।

প্রমাণ: L(E) আর R(E) যথাক্রমে বাঁ আর ডান বন্ধনীর সংখ্যা; P(E): L(E) = R(E).

  1. Basis: T, F বা একটি variable-এ কোনো বন্ধনী নেই, তাই L = R = 0.
  2. ¬ নিয়ম: ধরি L(E) = R(E). (¬E)-তে একটি নতুন “(” আর একটি নতুন “)”, তাই L = L(E) + 1 = R(E) + 1 = R.
  3. দুই-অংশের নিয়ম (∧, ∨, →, ↔ চারটিই একই আকারের): ধরি L(E) = R(E) আর L(F′) = R(F′). (E ∗ F′)-এ L = L(E) + L(F′) + 1 = R(E) + R(F′) + 1 = R, যেখানে ∗ যেকোনো একটি connective; connective চিহ্নে বন্ধনী নেই।
  4. সব নিয়মে P টিকে থাকে, তাই structural induction অনুযায়ী প্রতিটি wff-এ L = R. ∎

৬ · ℕ×ℕ-এ induction — lexicographic order আর Ackermann

কিছু recursion দুই argument নিয়ে চলে। বাজারঘরের গুদামে র‍্যাকের তাক m আর খোপ n দিয়ে চিহ্নিত; একটি গণনা a(m, n) এভাবে সংজ্ঞায়িত:

a(0, 0) = 0;   a(m, 0) = a(m − 1, 0) + 1 যখন m > 0;   a(m, n) = a(m, n − 1) + n যখন n > 0.

একটি ডাক একটি জোড়াকে “ছোট” জোড়ায় নিয়ে যায়। কোন অর্থে ছোট? Lexicographic order (অভিধান-ক্রম): (m′, n′) ≺ (m, n) যদি m′ < m, অথবা m′ = m আর n′ < n. অভিধানে যেমন প্রথম অক্ষর আগে তুলনা হয়, পরে দ্বিতীয়টি।

নীতি ৬.১ (generalized induction)

ℕ×ℕ-এর প্রতিটি অশূন্য উপসেটে ≺ অনুযায়ী একটি ক্ষুদ্রতম উপাদান আছে (প্রথমে ক্ষুদ্রতম m, তারপর সেই m-এ ক্ষুদ্রতম n). তাই: P(0, 0) সত্য, আর প্রতিটি (m, n) ≠ (0, 0)-এর জন্য “(m, n)-এর চেয়ে ≺-ছোট সব জোড়ায় P সত্য” থেকে P(m, n) সত্য হলে, সব জোড়ায় P সত্য।

নীতিটি কেন ঠিক: P মিথ্যা এমন জোড়ার সেট অশূন্য হলে তার একটি ক্ষুদ্রতম উপাদান থাকত; তার চেয়ে ছোট সব জোড়ায় P সত্য, তাই অনুমান অনুযায়ী সেখানেও P সত্য, বিরোধ। এটিই পর্ব ১১-এর minimal counterexample কৌশল, জোড়ার ওপর।

উপপাদ্য ৬.২

সব m, n ∈ ℕ-এর জন্য a(m, n) = m + n(n + 1)/2.

আবিষ্কারের পথ: হাতে কয়েকটি মান: a(1, 0) = 1, a(2, 0) = 2, তাই a(m, 0) = m মনে হয়। তারপর a(2, 1) = 2 + 1 = 3, a(2, 2) = 3 + 2 = 5, a(2, 3) = 5 + 3 = 8: m-এর সঙ্গে 1 + 2 + … + n যোগ হচ্ছে। সেখান থেকেই অনুমান। প্রমাণ (generalized induction):

  1. Basis (0, 0): a(0, 0) = 0 = 0 + 0 · 1/2.
  2. ধরি (m, n) ≠ (0, 0), আর ≺-ছোট সব জোড়ায় সূত্রটি সত্য (inductive hypothesis).
  3. ক্ষেত্র n = 0 (তাই m > 0): (m − 1, 0) ≺ (m, 0). সংজ্ঞা আর hypothesis থেকে a(m, 0) = a(m − 1, 0) + 1 = (m − 1) + 0 + 1 = m = m + 0 · 1/2.
  4. ক্ষেত্র n > 0: (m, n − 1) ≺ (m, n). তাই a(m, n) = a(m, n − 1) + n = m + (n − 1)n/2 + n.
  5. বীজগণিত: (n − 1)n/2 + n = (n² − n + 2n)/2 = n(n + 1)/2. তাই a(m, n) = m + n(n + 1)/2.
  6. দুই ক্ষেত্র সব (m, n) ≠ (0, 0) ঢাকে; নীতি ৬.১ অনুযায়ী সূত্রটি সব জোড়ায় সত্য। ∎

Lexicographic order-এর শক্তি আসল বোঝা যায় Ackermann function-এ (এখানে Ackermann–Péter রূপ): A(0, n) = n + 1; A(m, 0) = A(m − 1, 1) যখন m > 0; A(m, n) = A(m − 1, A(m, n − 1)) যখন m, n > 0. শেষ নিয়মে ভেতরের ডাকের ফল A(m, n − 1) বিশাল হতে পারে, তবু বাইরের ডাকে প্রথম argument m − 1, তাই lexicographic অর্থে জোড়া ছোট হয়। ফলে generalized induction দিয়েই প্রমাণ হয় যে প্রতিটি ডাক থামে।

from functools import lru_cache

@lru_cache(maxsize=None)
def a(m, n):
    if m == 0 and n == 0:
        return 0
    if n == 0:
        return a(m - 1, 0) + 1
    return a(m, n - 1) + n

print("a(2,3) =", a(2, 3), " a(3,2) =", a(3, 2))
print("a(m,n) == m + n(n+1)/2 for all m, n < 30?",
      all(a(m, n) == m + n * (n + 1) // 2
          for m in range(30) for n in range(30)))

calls = 0
def A(m, n):                     # Ackermann-Peter function
    global calls
    calls += 1
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))

for m in range(4):
    row = []
    for n in range(5):
        calls = 0
        value = A(m, n)
        row.append(f"{value}[{calls}]")
    print(f"A({m}, n), n=0..4:", " ".join(row))

আসল আউটপুট (ব্রাউজারের Python); বন্ধনীর [ ]-এ মোট ডাকের সংখ্যা:

a(2,3) = 8  a(3,2) = 6
a(m,n) == m + n(n+1)/2 for all m, n < 30? True
A(0, n), n=0..4: 1[1] 2[1] 3[1] 4[1] 5[1]
A(1, n), n=0..4: 2[2] 3[4] 4[6] 5[8] 6[10]
A(2, n), n=0..4: 3[5] 5[14] 7[27] 9[44] 11[65]
A(3, n), n=0..4: 5[15] 13[106] 29[541] 61[2432] 125[10307]

৯০০টি জোড়ায় সূত্রটি মিলেছে, প্রমাণের সঙ্গে সংগতিপূর্ণ; হাতে করা a(2, 3) = 8-ও মিলল। Ackermann-এর সারিগুলো দেখুন: A(1, n) = n + 2, A(2, n) = 2n + 3, A(3, n) = 2n+3 − 3. মান ছোট থাকলেও ডাকের সংখ্যা দ্রুত বাড়ছে। A(4, 1) = 216 − 3 = 65533 এখনো গোনা যায়, কিন্তু সেটি সরাসরি এই কোডে চালালে ডাকের সংখ্যা আর recursion-এর গভীরতা দুটোই বিশাল; আর A(4, 2)-এর দশমিক অঙ্কই প্রায় বিশ হাজার। এখানে brute force থেমে যায়, প্রমাণ নয়।

আরও গভীরে: Ackermann function প্রতিটি (m, n)-এ থামে এবং গণনাযোগ্য, অথচ এটি primitive recursive নয়: শুধু “n থেকে n − 1-এ নামা” ধরনের সরল recursion আর composition দিয়ে এটি লেখা যায় না, কারণ এটি প্রতিটি primitive recursive function-এর চেয়ে দ্রুত বাড়ে। এর উল্টো দিক, অতি ধীরে বাড়া “inverse Ackermann” α(n), union-find data structure-এর বিশ্লেষণে আসে (DSA কোর্সে)। আরও গভীরে, কোন recursion থামে তা সাধারণভাবে নির্ধারণ করা যায় না (halting problem), পর্ব ৭৫-এ।

৭ · Fibonacci-র দুটি তথ্য — বৃদ্ধির হার আর Cassini-র সূত্র

Fibonacci সংখ্যা কত দ্রুত বাড়ে? α = (1 + √5)/2 ≈ 1.618 (সোনালি অনুপাত, golden ratio) নিন। এর মূল ধর্ম α² = α + 1, কারণ α হলো x² − x − 1 = 0-এর ধনাত্মক মূল। প্রথমে Python-এ খোঁজ, তারপর প্রমাণ:

from math import sqrt

f = [0, 1]                          # f(0) = 0, f(1) = 1
for n in range(2, 61):
    f.append(f[n - 1] + f[n - 2])   # f(n) = f(n-1) + f(n-2)

alpha = (1 + sqrt(5)) / 2
print("alpha =", alpha, " alpha^2 =", alpha ** 2, " alpha+1 =", alpha + 1)

# scratch work: from which n does f(n) > alpha^(n-2) hold?
for n in range(7):
    print(n, f[n], round(alpha ** (n - 2), 3), f[n] > alpha ** (n - 2))
print("f(n) > alpha^(n-2) for all 3 <= n <= 60?",
      all(f[n] > alpha ** (n - 2) for n in range(3, 61)))

# Cassini: f(n+1) * f(n-1) - f(n)^2 == (-1)^n
vals = [f[n + 1] * f[n - 1] - f[n] ** 2 for n in range(1, 60)]
print("Cassini values, n = 1..8:", vals[:8])
print("Cassini holds for 1 <= n <= 59?",
      all(v == (-1) ** n for n, v in zip(range(1, 60), vals)))
print("f(60) =", f[60])

আসল আউটপুট (ব্রাউজারের Python):

alpha = 1.618033988749895  alpha^2 = 2.618033988749895  alpha+1 = 2.618033988749895
0 0 0.382 False
1 1 0.618 True
2 1 1.0 False
3 2 1.618 True
4 3 2.618 True
5 5 4.236 True
6 8 6.854 True
f(n) > alpha^(n-2) for all 3 <= n <= 60? True
Cassini values, n = 1..8: [-1, 1, -1, 1, -1, 1, -1, 1]
Cassini holds for 1 <= n <= 59? True
f(60) = 1548008755920

প্রথম লাইনে α² আর α + 1 একই ছাপা হয়েছে; floating point-এ এমন মিল সবসময় নিশ্চিত নয়, তবে গণিতে এরা হুবহু সমান। টেবিলে n = 0 আর n = 2-এ দাবিটি খাটে না (n = 2-এ f(2) = 1 = α0, “>” নয়); n = 1-এ খাটে, কিন্তু টানা খাটে n = 3 থেকে। তাই উপপাদ্যে শর্ত n ≥ 3, আর basis-এ লাগবে n = 3 আর n = 4. Cassini-র মান +1 আর −1 পালাক্রমে। Python-এর int যত বড় দরকার তত বড় হতে পারে, তাই f(60) নির্ভুল।

উপপাদ্য ৭.১

প্রতিটি n ≥ 3-এর জন্য f(n) > αn−2, যেখানে α = (1 + √5)/2.

প্রমাণ (strong induction): P(n): f(n) > αn−2.

  1. Basis n = 3: f(3) = 2 > α ≈ 1.618. Basis n = 4: f(4) = 3 > α² = α + 1 ≈ 2.618. দুটি basis লাগে, কারণ ধাপে দুই আগের মান ব্যবহার হবে।
  2. ধরি k ≥ 4, আর 3 ≤ j ≤ k সব j-এর জন্য P(j) সত্য। দেখাতে হবে P(k + 1).
  3. α² = α + 1-কে αk−3 দিয়ে গুণ করে: αk−1 = αk−2 + αk−3.
  4. k ≥ 4 বলে k − 1 ≥ 3, তাই hypothesis P(k) আর P(k − 1) দুটোই খাটে: αk−2 < f(k) আর αk−3 < f(k − 1).
  5. যোগ করে: αk−1 < f(k) + f(k − 1) = f(k + 1), Fibonacci-র সংজ্ঞায়। এটিই P(k + 1). ∎

অর্থাৎ Fibonacci সংখ্যা exponential হারে বাড়ে। এর একটি বিখ্যাত প্রয়োগ Lamé-র উপপাদ্য: Euclid-এর algorithm-এ ভাগের ধাপ ছোট সংখ্যাটির দশমিক অঙ্কের পাঁচ গুণের বেশি হয় না; পর্ব ২১-এ।

উপপাদ্য ৭.২ (Cassini-র সূত্র)

প্রতিটি n ≥ 1-এর জন্য f(n + 1) · f(n − 1) − f(n)² = (−1)n.

প্রমাণ (induction):

  1. Basis n = 1: f(2) · f(0) − f(1)² = 1 · 0 − 1 = −1 = (−1)1.
  2. ধরি কোনো k ≥ 1-এর জন্য f(k + 1) f(k − 1) − f(k)² = (−1)k.
  3. f(k + 2) = f(k + 1) + f(k) বসিয়ে: f(k + 2) f(k) − f(k + 1)² = f(k + 1) f(k) + f(k)² − f(k + 1)².
  4. f(k + 1)² = f(k + 1)(f(k) + f(k − 1)) লিখে: = f(k + 1) f(k) + f(k)² − f(k + 1) f(k) − f(k + 1) f(k − 1) = f(k)² − f(k + 1) f(k − 1).
  5. Hypothesis অনুযায়ী এটি −(−1)k = (−1)k+1. ∎

উদাহরণ n = 10: f(11) · f(9) − f(10)² = 89 · 34 − 55² = 3026 − 3025 = 1 = (−1)10. Fibonacci-র মতো recurrence-এর বন্ধ রূপ (Binet-এর সূত্র) পর্ব ৪৪ আর ৪৫-এ আসবে।

৮ · Recursive algorithm ঠিক কি না — induction-ই উত্তর

একটি recursive algorithm নিজেই একটি recursive সংজ্ঞা, তাই তার সঠিকতার স্বাভাবিক প্রমাণ induction, সাধারণত ইনপুটের আকারের ওপর strong induction. ছাঁচটি সবসময় এক: (১) base case-এ ফল ঠিক; (২) ধরে নিন সব ছোট ইনপুটে recursive ডাক ঠিক ফল দেয়; (৩) দেখান সেই ফলগুলো জুড়ে এই ইনপুটেও ফল ঠিক; (৪) দেখান প্রতিটি ডাক সত্যিই ছোট ইনপুটে যায়, যাতে algorithm থামে।

একটি জেলা অফিস বড় একটি জরিপ নিজে না করে প্রতিটি উপজেলাকে তাদের অংশ করতে বলে, আর উপজেলা আবার ইউনিয়নকে। জেলা অফিসের কর্মকর্তাকে প্রমাণ করতে হয় শুধু দুটি কথা: ছোট এলাকা থেকে সঠিক ফল এলে তিনি সেগুলো ঠিকভাবে যোগ করেন, আর ভাগ করা এলাকা সত্যিই ছোট। নিচের স্তরের কাজ কীভাবে হলো, তা তাঁর দেখার দরকার নেই; সেটিই inductive hypothesis.

উপপাদ্য ৮.১ (recursive binary search)

search(A, lo, hi, x): lo > hi হলে “নেই”; নইলে mid = ⌊(lo + hi)/2⌋; A[mid] = x হলে mid; A[mid] < x হলে search(A, mid + 1, hi, x); নইলে search(A, lo, mid − 1, x). A ছোট থেকে বড় ক্রমে সাজানো হলে, এটি x-এর একটি অবস্থান ফেরত দেয় যদি A[lo..hi]-তে x থাকে, নইলে “নেই”।

প্রমাণ (খণ্ডের দৈর্ঘ্য n = hi − lo + 1-এর ওপর strong induction):

  1. Basis n ≤ 0: খণ্ড ফাঁকা, x সেখানে নেই, আর algorithm “নেই” বলে। ঠিক।
  2. ধরি n ≥ 1, আর দৈর্ঘ্য < n-এর সব খণ্ডে algorithm ঠিক (hypothesis). lo ≤ mid ≤ hi, তাই A[mid] খণ্ডের ভেতরে।
  3. A[mid] = x হলে mid ফেরত দেয়: ঠিক।
  4. A[mid] < x হলে, A সাজানো বলে A[lo..mid]-এর সব মান ≤ A[mid] < x; তাই x থাকলে শুধু A[mid+1..hi]-তে। এই খণ্ডের দৈর্ঘ্য hi − mid < n, তাই hypothesis অনুযায়ী ডাকটি ঠিক উত্তর দেয়।
  5. A[mid] > x হলে প্রতিসমভাবে x থাকলে শুধু A[lo..mid−1]-এ, দৈর্ঘ্য mid − lo < n; আবার hypothesis.
  6. তিন ক্ষেত্রেই ফল ঠিক, আর প্রতিটি ডাক কঠোরভাবে ছোট খণ্ডে যায়, তাই algorithm থামে। ∎

Merge sort-এর প্রমাণের রূপরেখা: দৈর্ঘ্য n ≤ 1 হলে তালিকা আগেই সাজানো। n ≥ 2 হলে দুই অর্ধেকের দৈর্ঘ্য ⌊n/2⌋ আর ⌈n/2⌉, দুটোই < n; hypothesis অনুযায়ী recursive ডাক দুটি তাদের সাজায়। এরপর merge যে দুই সাজানো তালিকা থেকে একটি সাজানো তালিকা দেয়, তা merge-এর loop-এর একটি invariant দিয়ে আলাদা প্রমাণ করতে হয়। তাহলে পুরো তালিকা সাজানো হয়, আর strong induction অনুযায়ী সব n-এর জন্য merge sort ঠিক। এখানে একটি খুঁটিনাটি গুরুত্বপূর্ণ: n ≥ 2-এর শর্ত না থাকলে n = 1-এ এক অর্ধেকের দৈর্ঘ্য 1 হয়ে যেত, অর্থাৎ ইনপুট ছোট হতো না, আর hypothesis ব্যবহার করা যেত না। Base case-এর সীমা আর “ডাক ছোট ইনপুটে যায়” শর্ত একে অপরের সঙ্গে বাঁধা। এই loop invariant-এর যুক্তি পর্ব ১৩ “Program-এর সঠিকতা”-য়, আর algorithm-এর দিক থেকে DSA কোর্সের পর্ব ৩, পর্ব ১৮ আর পর্ব ২১-এ। চলার সময়ের recurrence T(n) = 2T(n/2) + Θ(n)-এর সমাধান DSA কোর্সের পর্ব ৮-এ।

একটি সাধারণ ভুল: “ডাকটি ছোট ইনপুটে যায়” যাচাই না করে hypothesis ব্যবহার। ওপরের binary search-এ mid − 1-এর বদলে mid লিখলে (search(A, lo, mid, x)), lo = hi-এর খণ্ডে x < A[mid] হলে ডাক একই খণ্ডে ফিরে আসে: দৈর্ঘ্য কমে না, induction খাটে না, আর প্রোগ্রাম অসীম recursion-এ পড়ে।

পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:

  • Balanced parentheses-এর recursive সংজ্ঞা দিন। λ ∈ B; x ∈ B ⇒ (x) ∈ B; x, y ∈ B ⇒ xy ∈ B.
  • Structural induction-এ কয়টি ক্ষেত্র লাগে? Basis-এর প্রতিটি রূপ আর প্রতিটি recursive নিয়মের জন্য একটি করে।
  • Height h-এর full binary tree-এ সর্বোচ্চ node? 2h+1 − 1; প্রমাণ structural induction.
  • f(12) · f(10) − f(11)² কত? Cassini অনুযায়ী (−1)11 = −1 (144 · 55 − 89² = 7920 − 7921).
  • Ackermann function কেন থামে? প্রতিটি ডাক lexicographic order-এ ছোট জোড়ায় যায়, আর ℕ×ℕ-এ এই ক্রম well-ordered.

৯ · AI-এর যুগে — AI-এর লেখা induction প্রমাণ যাচাই

AI সহকারী structural induction-এর প্রমাণ দ্রুত লিখে দেয়, আর লেখার ধরন নিখুঁত দেখায়। কিন্তু এই বিষয়ে তার ভুলগুলো চেনা ছাঁচের:

AI-এর ভুলকীভাবে ধরবেন
একটি recursive নিয়মের ক্ষেত্র বাদ (যেমন শুধু (x), xy নয়)সংজ্ঞার নিয়ম গুনে প্রমাণের ক্ষেত্র গুনুন; সংখ্যা মিলতে হবে
Basis বাদ, বা ভুল basis (Fibonacci-তে n = 2 থেকে শুরু)Python-এ ছোট n-এ দাবিটি চালিয়ে দেখুন কোথা থেকে খাটে
দুই subtree-এর height সমান ধরে নেওয়াএকটি অসম গাছে প্রতিটি ধাপ হাতে মিলিয়ে দেখুন
যা প্রমাণ করার, ধাপে সেটিই ব্যবহার (circular)প্রতিটি “অতএব”-এর পেছনে সংজ্ঞা, hypothesis না পূর্ববর্তী উপপাদ্য, লিখে নিন
Recursion থামে কি না না দেখেই “ঠিক” বলাপ্রতিটি ডাকে কোন মাপ কমছে, আর তা well-ordered কি না জিজ্ঞেস করুন
বড় Fibonacci বা Ackermann মান, বা বইয়ের উদ্ধৃতি বানিয়ে বলাPython-এ গুনুন; উদ্ধৃতি নিজে বিশ্বস্ত পাঠ্যবইয়ে খুঁজুন

AI-কে প্রমাণ লিখতে দিলে কাঠামো বেঁধে দিন, যাতে বাদ পড়া ক্ষেত্র চোখে পড়ে:

নিচের recursive সংজ্ঞার সেটে দাবিটির structural induction প্রমাণ লেখো। সংজ্ঞা: [basis আর প্রতিটি নিয়ম] দাবি: [P(x)] ১. প্রথমে সংজ্ঞার সব নিয়মের তালিকা দাও, তারপর প্রতিটি নিয়মের জন্য আলাদা ক্ষেত্র লেখো। ২. প্রতিটি ক্ষেত্রে inductive hypothesis ঠিক কোন উপাদানের ওপর, তা স্পষ্ট লেখো। ৩. প্রতিটি ধাপের পাশে কারণ দাও: সংজ্ঞা, hypothesis, না বীজগণিত। ৪. দাবি মিথ্যা মনে হলে প্রমাণের চেষ্টা না করে সবচেয়ে ছোট counterexample দাও। ৫. শেষে একটি ছোট Python কোড দাও, যা নিয়ম ধরে ছোট উপাদান বানিয়ে দাবিটি যাচাই করে।

AI-এর প্রমাণ হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১স্বাভাবিক সংখ্যা নিজেই কি একটি recursive সংজ্ঞা? Dedekind আর Peano কী করেছিলেন?

উনিশ শতকের শেষে গণিতবিদরা প্রশ্ন তোলেন: 1 + 1 = 2 আমরা কীসের ভিত্তিতে জানি? Richard Dedekind ১৮৮৮ সালে Was sind und was sollen die Zahlen? (“সংখ্যা কী, আর কী হওয়া উচিত?”) বইয়ে, আর Giuseppe Peano ১৮৮৯ সালে স্বাভাবিক সংখ্যার স্বতঃসিদ্ধ দেন, যা এখন Peano axioms নামে পরিচিত। মূল কথা: একটি প্রথম সংখ্যা 0 আছে; প্রতিটি সংখ্যা n-এর একটি “পরবর্তী” S(n) আছে; 0 কারও পরবর্তী নয়; ভিন্ন সংখ্যার পরবর্তী ভিন্ন; আর induction-এর স্বতঃসিদ্ধ: 0-কে ধারণ করে আর পরবর্তীতে বন্ধ এমন প্রতিটি সেটে সব স্বাভাবিক সংখ্যা আছে।

এই পর্বের ভাষায় এটি হুবহু একটি recursively defined set: basis 0, নিয়ম n ↦ S(n), আর induction স্বতঃসিদ্ধটিই exclusion rule. তাই ℕ-এর ওপর mathematical induction আসলে structural induction-এর সবচেয়ে সরল রূপ।

যোগও recursive ভাবে আসে: m + 0 = m, m + S(n) = S(m + n). তাহলে 1 + 1 = 1 + S(0) = S(1 + 0) = S(1) = 2. গুণ: m · 0 = 0, m · S(n) = m · n + m. যোগের বিনিময় নিয়ম m + n = n + m-ও structural induction দিয়ে প্রমাণ করতে হয়; সংজ্ঞায় এটি দেওয়া নেই। Lean-এর মতো proof assistant-এ শুরুর অনুশীলনে ঠিক এই প্রমাণগুলো লিখতে হয়।

এখানে একটি গভীর সতর্কতাও আছে। পরে Kurt Gödel (১৯৩১) দেখান যে Peano-র first-order স্বতঃসিদ্ধ থেকে স্বাভাবিক সংখ্যার কিছু সত্য বাক্য প্রমাণ করা যায় না। অর্থাৎ recursive সংজ্ঞা সংখ্যাগুলো ঠিকঠাক বানায়, কিন্তু তাদের সম্পর্কে সব সত্য প্রমাণের নিশ্চয়তা দেয় না।

মূল উপলব্ধি: ℕ নিজেই basis আর একটি নিয়মে বানানো সেট; তাই induction কোনো আলাদা কৌশল নয়, সংখ্যার সংজ্ঞারই অংশ।

প্র ০২Ackermann function কেন বানানো হয়েছিল, আর “primitive recursive নয়” কথাটির তাৎপর্য কী?

১৯২০-এর দশকে David Hilbert-এর দল জানতে চাইছিল, “গণনাযোগ্য” function বলতে ঠিক কী বোঝায়। একটি প্রার্থী ছিল primitive recursive function: শূন্য, পরবর্তী (n + 1), প্রক্ষেপণ, composition আর এই পর্বের উপপাদ্য ১.১-এর মতো সরল recursion (f(n + 1) নির্ভর করে f(n)-এর ওপর) দিয়ে যা বানানো যায়। যোগ, গুণ, ঘাত, factorial, gcd সবই এর মধ্যে।

Hilbert-এর ছাত্র Wilhelm Ackermann ১৯২৮ সালে একটি function প্রকাশ করেন যা স্পষ্টতই গণনাযোগ্য (প্রতিটি মান সসীম ধাপে বের হয়) অথচ primitive recursive নয়। আজ যে দুই-argument রূপটি পড়ানো হয়, সেটি পরে Rózsa Péter আর Raphael Robinson-এর সরলীকরণ বলে প্রচলিত; মূল রূপে তিনটি argument ছিল। প্রমাণের ধারণা: প্রতিটি primitive recursive function-এর জন্য একটি m আছে, যাতে function-টি A(m, ·)-এর চেয়ে ধীরে বাড়ে; কিন্তু n ↦ A(n, n) প্রতিটি সারিকে শেষে ছাড়িয়ে যায়। এটি পর্ব ১৮-তে আসা Cantor-এর diagonal ধারণার আত্মীয়।

তাৎপর্য: “সরল এক-ধাপ recursion” দিয়ে সব গণনা ধরা যায় না। এই উপলব্ধি পরে general recursive function, Turing machine আর lambda calculus-এর দিকে নিয়ে যায়, যারা গণনাযোগ্যতার একই শ্রেণি দেয় (Church–Turing thesis). এই পর্বের ভাষায়, Ackermann থামে কারণ জোড়ার ওপর lexicographic order well-ordered; কিন্তু সেই প্রমাণ “n কমে” ধরনের সাধারণ induction-এ লেখা যায় না, ℕ×ℕ-এর induction লাগে।

ব্যবহারিক দিক: Robert Tarjan ১৯৭৫ সালে দেখান যে union-find-এ path compression আর union by rank একসঙ্গে ব্যবহার করলে m-টি অপারেশনে সময় প্রায় m · α(n), যেখানে α হলো Ackermann-এর একটি বিপরীত function. সব বাস্তব n-এর জন্য α(n) খুব ছোট (প্রায় ৪ বা তার কম বলে সাধারণত বলা হয়)। তাই গণিতের এই “অদ্ভুত” function graph algorithm-এর বিশ্লেষণে সরাসরি কাজে লাগে।

মূল উপলব্ধি: থামার প্রমাণে কোন well-ordered মাপ কমে, সেটিই recursion-এর শক্তি ঠিক করে; জোড়ার ওপর lexicographic মাপ এক-সংখ্যার মাপের চেয়ে অনেক শক্তিশালী।

প্র ০৩Programming language-এর syntax কেন recursive সংজ্ঞায় লেখা হয়? BNF-এর ইতিহাস কী?

১৯৫০-এর দশকের শেষে ALGOL নামে একটি আন্তর্জাতিক programming language নকশা হচ্ছিল। John Backus ১৯৫৯ সালে syntax লেখার একটি আনুষ্ঠানিক পদ্ধতি প্রস্তাব করেন, আর Peter Naur ১৯৬০ সালের ALGOL 60 রিপোর্টে সেটি ব্যবহার করেন। পদ্ধতিটি এখন Backus–Naur Form বা BNF নামে পরিচিত। এর আগে বেশির ভাগ ভাষার নিয়ম লেখা হতো সাধারণ ইংরেজিতে, যা অস্পষ্ট ছিল।

BNF হুবহু এই পর্বের recursive সেট। যেমন: <expr> ::= <number> | ( <expr> + <expr> ) | ( <expr> * <expr> ). বাঁ দিকের নাম সংজ্ঞায়িত হচ্ছে, ডান দিকে সেই নামই আবার এসেছে। প্রথম বিকল্প basis, বাকিগুলো recursive step, আর “এগুলো ছাড়া আর কিছু নয়” অংশটি অন্তর্নিহিত। ঠিক এই কারণে compiler সব সম্ভাব্য program-এর ওপর structural recursion চালাতে পারে: parse করে একটি tree বানায় (abstract syntax tree), তারপর প্রতিটি নিয়মের জন্য একটি করে ফাংশন দিয়ে type-check আর code তৈরি করে।

Structural induction এখানে নিশ্চয়তাও দেয়। “আমার compiler প্রতিটি সঠিক program-এর জন্য সমতুল্য machine code দেয়” এমন দাবি প্রমাণের স্বাভাবিক কাঠামো program-এর syntax-এর ওপর structural induction. CompCert-এর মতো গবেষণা-প্রকল্প এ ধরনের প্রমাণ proof assistant-এ যাচাই করেছে বলে প্রকাশিত হয়েছে।

তবে সব recursive grammar সমান নয়। কিছু grammar অস্পষ্ট (ambiguous): একই string দুইভাবে তৈরি হয়, যেমন বন্ধনী ছাড়া 1 + 2 * 3. তখন structural recursion দিয়ে মান নির্দিষ্ট হয় না, এই পর্বের unique readability-র কথা। Grammar-এর শ্রেণি আর তাদের ক্ষমতা পর্ব ৭৩-এ, আর পরে একটি Theory of Computation কোর্সে গভীরভাবে আসবে।

মূল উপলব্ধি: একটি language-এর syntax একটি recursive সেট; তার ওপর প্রতিটি compiler-ধাপ structural recursion, আর তার সঠিকতা structural induction.

প্র ০৪Fibonacci সংখ্যার ইতিহাস কী, আর Euclid-এর algorithm-এর সঙ্গে এর সম্পর্ক কোথায়?

পিসার Leonardo, যিনি পরে Fibonacci নামে পরিচিত হন, ১২০২ সালে Liber Abaci বইয়ে খরগোশের একটি ধাঁধা দেন: প্রতি জোড়া খরগোশ দ্বিতীয় মাস থেকে প্রতি মাসে একটি নতুন জোড়া জন্ম দেয়; এক বছরে কত জোড়া হবে? উত্তরের মাসিক সংখ্যাগুলোই এই ধারা। বইটির মূল লক্ষ্য ছিল ইউরোপে হিন্দু-আরবি দশমিক সংখ্যা পরিচিত করানো; খরগোশের ধাঁধাটি ছিল অনেক অনুশীলনের একটি।

তবে ধারাটি এর আগেই ভারতীয় ছন্দশাস্ত্রে দেখা যায় বলে ইতিহাসবিদরা উল্লেখ করেন: ছোট আর বড় মাত্রা (দৈর্ঘ্য ১ আর ২) দিয়ে মোট n মাত্রার কয়টি ছন্দ বানানো যায়, তার গণনা। Virahanka, Gopala আর Hemachandra-র নাম এ প্রসঙ্গে আসে; সঠিক তারিখ নিয়ে সূত্রভেদ আছে। এই গণনা নিজেই একটি recursive যুক্তি: শেষ মাত্রা ছোট হলে বাকি n − 1, বড় হলে বাকি n − 2.

১৮৪৪ সালে Gabriel Lamé Fibonacci সংখ্যা দিয়ে Euclid-এর algorithm বিশ্লেষণ করেন, যাকে প্রায়ই computational complexity-র প্রথম দিকের একটি বিশ্লেষণ বলা হয়। মূল পর্যবেক্ষণ: পরপর দুটি Fibonacci সংখ্যা Euclid-এর algorithm-এর “সবচেয়ে খারাপ” ইনপুট, কারণ প্রতিটি ভাগফল 1. যেমন gcd(89, 55) → (55, 34) → (34, 21) → … প্রতিটি ধাপে ঠিক এক ধাপ পেছনের Fibonacci জোড়া। তাই ধাপ-সংখ্যা k হলে ছোট সংখ্যাটি অন্তত একটি Fibonacci সংখ্যা f(k + 1); আর এই পর্বের উপপাদ্য ৭.১ বলে Fibonacci exponential হারে বাড়ে। দুটি মিলিয়ে ধাপ-সংখ্যা অঙ্ক-সংখ্যার সমানুপাতিক, অর্থাৎ বিশাল সংখ্যাতেও Euclid দ্রুত। এটি RSA-র মতো cryptography-তে অপরিহার্য (পর্ব ২৮)।

একটি সতর্কতা: Fibonacci আর সোনালি অনুপাত নিয়ে অনেক জনপ্রিয় দাবি (যেমন বিখ্যাত স্থাপত্য বা শরীরের মাপে α) প্রমাণের চেয়ে কল্পনায় বেশি দাঁড়িয়ে আছে। সূর্যমুখীর বীজের সর্পিল সারির মতো কিছু উদ্ভিদ-বিন্যাসে Fibonacci সংখ্যা ঘন ঘন দেখা যায় বলে উদ্ভিদবিজ্ঞানে নথিভুক্ত, তবে সবখানে নয়।

মূল উপলব্ধি: একটি সরল recursive সংজ্ঞার বৃদ্ধির হার প্রমাণ করা গেলে তা অন্য algorithm-এর গতির নিশ্চয়তাও দেয়।

প্র ০৫Lisp থেকে আধুনিক proof assistant পর্যন্ত, recursive data type আর structural recursion কীভাবে programming-এর কেন্দ্রে এলো?

John McCarthy ১৯৬০ সালে Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I প্রবন্ধে Lisp ভাষার ধারণা দেন। এর কেন্দ্রে ছিল একটি recursive সেট, S-expression: একটি atom একটি S-expression; দুটি S-expression-এর জোড়াও একটি S-expression. তালিকা, tree, এমনকি program নিজেও এই সেটের উপাদান। McCarthy দেখান যে এই সেটের ওপর structural recursion আর কয়েকটি মৌলিক অপারেশন দিয়ে একটি পূর্ণ interpreter (eval) লেখা যায়।

পরের দশকগুলোতে ML, Haskell আর OCaml-এর মতো ভাষায় এটি হয় algebraic data type: Tree = Leaf | Node(Tree, Tree), যা হুবহু এই পর্বের full binary tree-এর সংজ্ঞা। Compiler পরীক্ষা করে, একটি function প্রতিটি রূপ (Leaf আর Node) সামলেছে কি না; বাদ পড়লে সতর্ক করে। এটি হলো “প্রতিটি নিয়মের জন্য একটি ক্ষেত্র” নীতির যান্ত্রিক রূপ, যা ভুল প্রমাণের অংশে দেখলেন।

Coq (এখন Rocq), Agda আর Lean-এর মতো proof assistant এক ধাপ এগিয়ে। সেখানে recursive সেটকে বলে inductive type, আর প্রতিটি inductive type-এর জন্য structural induction-এর নীতি যন্ত্র নিজেই তৈরি করে। Recursive function গ্রহণ করার আগে যন্ত্র যাচাই করে যে প্রতিটি ডাক একটি গঠনগতভাবে ছোট argument-এ যায়, অথবা ব্যবহারকারীকে একটি well-founded মাপ দিতে হয়, ঠিক Ackermann-এর lexicographic যুক্তির মতো। কারণ: একটি না-থামা function দিয়ে মিথ্যা বাক্যও “প্রমাণ” করা যেত, ফলে পুরো যুক্তিব্যবস্থা অর্থহীন হয়ে যেত।

দৈনন্দিন programming-এও এর প্রভাব: JSON ডেটা (মান হলো সংখ্যা, string, অথবা মানের তালিকা বা মানচিত্র), ফাইল-সিস্টেমের ফোল্ডার, বাজারঘরের মেনুর বিভাগ সবই recursive সেট। এগুলোর ওপর ফাংশন লিখতে একই প্রশ্ন: প্রতিটি রূপ সামলেছি কি, আর প্রতিটি ডাক ছোট অংশে যায় কি?

মূল উপলব্ধি: Recursive সংজ্ঞা, structural recursion আর structural induction একই মুদ্রার তিন পিঠ; আধুনিক ভাষা আর proof assistant এদের একসঙ্গে যন্ত্রে বসিয়ে দিয়েছে।

অনুশীলন

আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।

  1. প্রমাণ: Full binary tree T-এ leaf (সন্তানহীন node)-এর সংখ্যা l(T) আর ভেতরের node-এর সংখ্যা i(T). Structural induction দিয়ে প্রমাণ করুন যে l(T) = i(T) + 1.

    Recursive সংজ্ঞা: একক node-এ l = 1, i = 0; T = T1 · T2-এ l(T) = l(T1) + l(T2) (নতুন root leaf নয়, আর পুরোনো leaf-গুলো leaf-ই থাকে), আর i(T) = 1 + i(T1) + i(T2).

    1. Basis: একক node-এ l = 1 = 0 + 1 = i + 1.
    2. Hypothesis: l(T1) = i(T1) + 1, l(T2) = i(T2) + 1.
    3. l(T) = l(T1) + l(T2) = i(T1) + i(T2) + 2 = (1 + i(T1) + i(T2)) + 1 = i(T) + 1. ∎

    ফল: n(T) = l + i = 2i(T) + 1, তাই node-সংখ্যা সবসময় বিজোড়। ওপরের full_trees.py-র শেষ লাইন height 3 পর্যন্ত ২৬টি গাছে এটি যাচাই করেছে।

  2. হিসাব: (ক) Σ = {a, b, c} হলে Σ*-এ দৈর্ঘ্য ৩ পর্যন্ত (λ-সহ) কয়টি string? (খ) a(m, n)-এর সংজ্ঞা (অংশ ৬) থেকে সরাসরি ধাপে ধাপে a(1, 3) বের করুন, তারপর সূত্র দিয়ে মেলান। (গ) Cassini-র সূত্র ব্যবহার না করে f(11) · f(9) − f(10)² হিসাব করুন। (ঘ) দৈর্ঘ্য 10-এর কয়টি balanced string আছে বলে আপনার অনুমান?

    (ক) 1 + 3 + 9 + 27 = ৪০টি।

    (খ) a(0, 0) = 0 → a(1, 0) = 1 → a(1, 1) = 1 + 1 = 2 → a(1, 2) = 2 + 2 = 4 → a(1, 3) = 4 + 3 = 7. সূত্র: 1 + 3 · 4/2 = 7. মেলে।

    (গ) f(9) = 34, f(10) = 55, f(11) = 89. 89 · 34 = 3026, 55² = 3025, পার্থক্য 1 = (−1)10.

    (ঘ) ধারা 1, 1, 2, 5, 14-এর পরের Catalan সংখ্যা 42. Scratch-এ নিয়ম ধরে দৈর্ঘ্য 10 পর্যন্ত বানিয়ে গুনে ঠিক ৪২টিই পাওয়া গেছে।

  3. আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। তারপর যেকোনো Python-এ চালিয়ে মেলান।
    def g(a, b):
        print(a, b)
        return a if b == 0 else g(b, a % b)
    
    print("gcd =", g(252, 198))
    252 198
    198 54
    54 36
    36 18
    18 0
    gcd = 18

    প্রতিটি ডাক প্রথমে নিজের argument ছাপে, তারপর recursive ডাক দেয়; তাই লাইনগুলো বাইরের ডাক থেকে ভেতরের দিকে। দ্বিতীয় argument 198 → 54 → 36 → 18 → 0 কঠোরভাবে কমছে, যা থামার নিশ্চয়তা। শেষ ডাক b = 0-এ basis: 18 ফেরত, আর সেই মানই সব স্তর পেরিয়ে বাইরে আসে।

  4. প্রমাণের ভুল ধরুন: “দাবি: প্রতিটি full binary tree-এ n(T) = 2h(T)+1 − 1. প্রমাণ: basis-এ 1 = 21 − 1. T = T1 · T2, h = h(T) হলে T1 আর T2-এর height h − 1, তাই hypothesis অনুযায়ী প্রতিটিতে 2h − 1টি node, আর n(T) = 1 + 2(2h − 1) = 2h+1 − 1. ∎” ভুল কোথায়?

    ভুল ধাপ: “T1 আর T2-এর height h − 1”. সংজ্ঞায় h(T) = 1 + max(h(T1), h(T2)), তাই শুধু বড় subtree-টির height h − 1; অন্যটি ছোট হতে পারে। দাবিটিও মিথ্যা: T1 একক node আর T2 তিন-node-এর গাছ হলে h(T) = 2, n(T) = 5, কিন্তু 23 − 1 = 7. full_trees.py-র আউটপুটেও h = 2-এ দুটি গাছের n = 5. সঠিক দাবি হলো উপপাদ্য ৪.১-এর অসমতা; সমতা শুধু perfect binary tree-তে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমRecursive সেটের ওপর structural induction প্রমাণ লেখার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. সেটের basis আর প্রতিটি recursive নিয়মের তালিকা লেখা
  2. দাবি P(x) নির্ভুলভাবে লেখা
  3. Basis-এর প্রতিটি উপাদানে P সত্য দেখানো
  4. প্রতিটি নিয়মের জন্য: ইনপুটে P ধরে তৈরি উপাদানে P দেখানো
  5. নিয়মের তালিকা আর ক্ষেত্রের তালিকা মিলিয়ে উপসংহার টানা

Balanced parentheses-এর সেট B: λ ∈ B; x ∈ B হলে (x) ∈ B; x, y ∈ B হলে xy ∈ B. নিচের কোন string B-তে নেই?

  • (()())
  • (())()
  • ())(()
  • ((()))

একটি সেট তিনটি recursive নিয়ম আর একটি basis দিয়ে সংজ্ঞায়িত। এর ওপর structural induction প্রমাণে ন্যূনতম কয়টি ক্ষেত্র লাগবে?

  • ২টি: basis আর একটি recursive ধাপ
  • ৪টি
  • ৩টি: শুধু তিন নিয়ম
  • অসীম, কারণ সেট অসীম

f(0) = 0, f(1) = 1, f(n) = f(n − 1) + f(n − 2) (n ≥ 2) হলে f(8) · f(6) − f(7)²-এর মান কত?

  • 0
  • 1
  • −13
  • −1

Height 3-এর একটি full binary tree-তে সর্বোচ্চ কয়টি node থাকতে পারে?

  • 15
  • 8
  • 7
  • 16

ℕ×ℕ-এর lexicographic order-এ (m′, n′) ≺ (m, n) মানে m′ < m, অথবা m′ = m আর n′ < n. নিচের কোনটি সত্য?

  • (3, 0) ≺ (2, 1000)
  • (4, 7) ≺ (4, 5)
  • (2, 1000) ≺ (3, 0)
  • এই ক্রমে অসীম নিম্নগামী শিকল আছে

Strong induction দিয়ে প্রমাণ করা হবে: n ≥ 3-এর জন্য f(n) > αn−2, যেখানে f Fibonacci আর α = (1 + √5)/2. Inductive ধাপে f(k + 1) = f(k) + f(k − 1) ব্যবহার হয়। Basis-এ কোন মানগুলো যাচাই করতে হবে?

  • শুধু n = 3
  • n = 3 আর n = 4
  • n = 2 আর n = 3
  • n = 0 আর n = 1

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Recursive function: basis আর আগের মান থেকে পরের মানের নিয়ম; প্রতিটি ডাক একটি well-ordered মাপে কমলে সংজ্ঞাটি ঠিক একটি function দেয়।
  2. Recursive সেট: basis, recursive step আর exclusion rule. Σ*, balanced parentheses, wff, full binary tree.
  3. Structural induction: basis-এ দাবি, আর প্রতিটি নিয়মের জন্য আলাদা ক্ষেত্রে “ইনপুটে সত্য ⇒ নতুন উপাদানে সত্য”। একটি ক্ষেত্র বাদ মানেই প্রমাণ অসম্পূর্ণ।
  4. মূল ফল: balanced string-এর দৈর্ঘ্য জোড়; n(T) ≤ 2h(T)+1 − 1; n ≥ 3-এ f(n) > αn−2; f(n+1)f(n−1) − f(n)² = (−1)n.
  5. জোড়া আর algorithm: ℕ×ℕ-এ lexicographic induction (Ackermann থামে); recursive algorithm-এর সঠিকতা ইনপুটের আকারের ওপর strong induction.

পরের পর্বে: Program-এর সঠিকতা — pre- আর postcondition, Hoare triple, loop invariant আর program থামার যুক্তি।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান