পাঠ ৪৪ · ৪৪-এর মধ্যে · মডিউল ৯
Home / Courses / Discrete Mathematics / চূড়ান্ত প্রকল্প

চূড়ান্ত প্রকল্প — একাধিক ক্ষেত্র একত্র করে বাস্তব সমস্যা সমাধান

Capstone — combining multiple areas to solve a real problem
১৬ মিনিট পড়া উচ্চ · Advanced ক্যাপস্টোন প্রজেক্ট Python কোডসহ সম্পূর্ণ বাংলায়

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

  • একটি বাস্তব-সদৃশ সমস্যাকে একাধিক discrete math ক্ষেত্রে ভেঙে মডেল করা
  • L20 (ইউলার থিওরেম), L28 (RSA), L13 (পারমুটেশন), এবং M8 (Big-O বিশ্লেষণ) — চারটি পূর্ববর্তী পাঠের ফলাফল একটিমাত্র প্রোগ্রামে একত্রিত করা
  • কেন একই প্রোগ্রামের ভিন্ন ভিন্ন ধাপ সম্পূর্ণ ভিন্ন জটিলতা ক্লাসে পড়তে পারে — P (RSA) বনাম NP-সম্পূর্ণ (রুট অপ্টিমাইজেশন)
  • সম্পূর্ণ ৪৪-পাঠের কোর্সের একটি সংশ্লিষ্ট, প্রয়োগযোগ্য পুনরালোচনা

১ · প্রকল্পের প্রেক্ষাপট

কল্পনা করুন একটি ছোট ডেলিভারি কোম্পানি ৬টি শহরের মধ্যে একটি রুট ও যোগাযোগ ব্যবস্থা পরিচালনা করে — এবং তাদের তিনটি ভিন্ন গাণিতিক প্রশ্নের উত্তর দরকার: (১) তাদের রোড নেটওয়ার্কে কি এমন একটি রুট আছে যা প্রতিটি রাস্তা ঠিক একবার ব্যবহার করে? (২) তাদের ডেলিভারি সিস্টেমে বার্তা কীভাবে নিরাপদে এনক্রিপ্ট করা হয়? (৩) সব শহর পরিদর্শনের সর্বোত্তম ক্রম খুঁজতে কতটা কম্পিউটেশনাল কাজ লাগবে? আমরা এই তিনটি প্রশ্নের প্রতিটিকে এই কোর্সের একটি নির্দিষ্ট মডিউলের গণিত দিয়ে সমাধান করব।

২ · ধাপ ১ (M4, গ্রাফ থিওরি) — ইউলার পথ যোগ্যতা

৬টি শহর — ঢাকা, চট্টগ্রাম, সিলেট, রাজশাহী, খুলনা, বরিশাল — একটি চক্র (cycle) আকারে সংযুক্ত (ঢাকা→চট্টগ্রাম→ সিলেট→রাজশাহী→খুলনা→বরিশাল→ঢাকা), প্লাস একটি অতিরিক্ত সরাসরি রাস্তা ঢাকা-রাজশাহী (দ্রুত রুটের জন্য)। মোট $V=6$টি শহর, $E=7$টি রাস্তা।

L20-এর ইউলার থিওরেমEuler's Theoremএকটি কানেক্টেড গ্রাফে ইউলার সার্কিট থাকে যদি প্রতিটি ভার্টেক্সের ডিগ্রি জোড় হয়; ঠিক ২টি বিজোড়-ডিগ্রি ভার্টেক্স থাকলে ইউলার পথ (সার্কিট নয়) থাকে। স্মরণ করুন: কানেক্টেড গ্রাফে ইউলার সার্কিট থাকে যদি প্রতিটি ভার্টেক্সের ডিগ্রি জোড় হয়; ঠিক ২টি বিজোড়-ডিগ্রি ভার্টেক্স থাকলে ইউলার পথ (সার্কিট নয়) থাকে; ২-এর বেশি বিজোড়-ডিগ্রি ভার্টেক্স থাকলে কোনোটিই সম্ভব নয়। অতিরিক্ত ঢাকা-রাজশাহী রাস্তাটি ঠিক এই দুটি শহরের ডিগ্রি ১ বাড়িয়ে জোড় থেকে বিজোড় করে দেয় — বাকি চারটি শহর জোড়-ডিগ্রিই থেকে যায়। নিচের কোড এই ডিগ্রি-চেক লাইভ চালিয়ে নিশ্চিত করবে ঢাকা ও রাজশাহীর মধ্যে একটি ইউলার পথ (প্রতিটি রাস্তা ঠিক একবার ব্যবহার করে) সম্ভব কি না।

৩ · ধাপ ২ (M5, নাম্বার থিওরি) — RSA এনক্রিপশন (L28 পুনর্ব্যবহার)

কোম্পানির ডেলিভারি অর্ডার নম্বর নিরাপদে পাঠাতে আমরা ঠিক L28-এর টয় RSA উদাহরণ পুনর্ব্যবহার করব — একই প্রাইম-জোড়া থেকে আসা $n=3233$, পাবলিক এক্সপোনেন্ট $e=17$, প্রাইভেট এক্সপোনেন্ট $d=2753$ (যেখানে $\varphi(3233)=3120$ এবং $17 \times 2753 \equiv 1 \pmod{3120}$, L28-এ যাচাই করা হয়েছিল)। বার্তা $m=65$ এনক্রিপ্ট করতে $c = m^e \bmod n$ এবং ডিক্রিপ্ট করতে $m = c^d \bmod n$ — কোড সেলে pow(m, e, n) ও pow(c, d, n) দিয়ে লাইভ গণনা করে দেখানো হবে ডিক্রিপ্ট করা মান ঠিক মূল বার্তায় ফিরে আসে কি না।

৪ · ধাপ ৩ (M3, কম্বিনেটরিক্স) — রুট অর্ডারিং গণনা

কোম্পানি যদি প্রতিদিন ৬টি শহরের সবগুলোতেই ডেলিভারি দিতে চায় এবং সবচেয়ে ছোট (দ্রুততম) রুট খুঁজতে চায়, তাহলে L13-এর পারমুটেশন গণনা অনুযায়ী সম্ভাব্য ভ্রমণ-ক্রমের সংখ্যা $6! = 720$টি। এটি নিজে থেকে বড় সংখ্যা মনে না হলেও — L39 স্মরণ করুন — শহরের সংখ্যা সামান্য বাড়লেই ($n=15$-এ $15! \approx 1.3 \times 10^{12}$) এই সংখ্যা কম্পিউটেশনালি অসম্ভব হয়ে যায়। এই "সব রুট চেষ্টা করে সেরাটা বাছাই" সমস্যাটিই আসলে Traveling Salesman Problem (TSP)-এর decision version — যা L42-এ দেখা হয়েছে NP-সম্পূর্ণ।

M3 + M8 + M9-এর সংযোগ

RSA-এর এনক্রিপশন/ডিক্রিপশন (ধাপ ২) দ্রুত — কারণ modular exponentiation Class P-তে পড়ে (নিচে দেখুন, $O(\log e)$)। কিন্তু সব রুট এনুমারেট করে সেরাটা খোঁজা (ধাপ ৩) NP-সম্পূর্ণ — brute force $O(n!)$। দুটোই "বড় সংখ্যা নিয়ে কাজ করে" মনে হলেও, তাদের কম্পিউটেশনাল প্রকৃতি সম্পূর্ণ ভিন্ন — এটাই এই ক্যাপস্টোনের কেন্দ্রীয় শিক্ষা।

৫ · ধাপ ৪ (M8, অ্যালগরিদম বিশ্লেষণ) — প্রতিটি ধাপের Big-O

গ্রাফ ডিগ্রি-চেক
$O(V+E)$ — প্রতিটি ভার্টেক্স ও এজ ঠিক একবার প্রসেস হয় (L19-এর graph traversal ধারণা)।
RSA modular exponentiation
$O(\log e)$ — repeated squaring (L26) ব্যবহার করে, এক্সপোনেন্টের বিট-সংখ্যার সমানুপাতিক ধাপ লাগে, এক্সপোনেন্টের মান নিজে নয়।
সব রুট এনুমারেশন
$O(n!)$ — L39-এ দেখা সবচেয়ে দ্রুত-বর্ধনশীল ক্লাস, ব্যবহারিকভাবে $n$ ছোট রাখা ছাড়া অসম্ভব।

৬ · সম্পূর্ণ কোড — তিনটি ধাপই একসাথে

নিচের কোড সেল তিনটি ধাপই চালিয়ে একটি "চূড়ান্ত রিপোর্ট" তৈরি করে — ঠিক যেমন একটি বাস্তব সিস্টেম করত।

Python
import math
from collections import defaultdict

# ============ ধাপ ১ · গ্রাফ থিওরি (M4) — ইউলার পথ যাচাই ============
cities = ["Dhaka", "Chittagong", "Sylhet", "Rajshahi", "Khulna", "Barisal"]
roads = [
    ("Dhaka", "Chittagong"), ("Chittagong", "Sylhet"), ("Sylhet", "Rajshahi"),
    ("Rajshahi", "Khulna"), ("Khulna", "Barisal"), ("Barisal", "Dhaka"),
    ("Dhaka", "Rajshahi"),   # অতিরিক্ত দ্রুত-রুট রাস্তা
]

degree = defaultdict(int)
for u, v in roads:
    degree[u] += 1
    degree[v] += 1

odd_degree_cities = [c for c in cities if degree[c] % 2 == 1]
V, E = len(cities), len(roads)

print("=== ধাপ ১ · গ্রাফ থিওরি (M4) — ইউলার পথ যাচাই (L20) ===")
for c in cities:
    print(f"  {c}: degree {degree[c]}")
print("বিজোড়-ডিগ্রি শহর:", odd_degree_cities)

if len(odd_degree_cities) == 0:
    euler_result = "প্রতিটি রাস্তা ঠিক একবার ব্যবহার করে ইউলার সার্কিট সম্ভব।"
elif len(odd_degree_cities) == 2:
    euler_result = f"{odd_degree_cities[0]} থেকে {odd_degree_cities[1]} পর্যন্ত ইউলার পথ সম্ভব (সার্কিট নয়)।"
else:
    euler_result = "কোনো ইউলার পথ সম্ভব নয় — ২-এর বেশি বিজোড়-ডিগ্রি ভার্টেক্স আছে।"
print("ফলাফল:", euler_result)

# ============ ধাপ ২ · নাম্বার থিওরি (M5) — RSA (L28-এর ঠিক একই সংখ্যা) ============
n, e, d = 3233, 17, 2753
message = 65
ciphertext = pow(message, e, n)
decrypted = pow(ciphertext, d, n)

print("\n=== ধাপ ২ · নাম্বার থিওরি (M5) — RSA এনক্রিপশন (L28 পুনর্ব্যবহার) ===")
print(f"পাবলিক কী (n,e) = ({n},{e}) | প্রাইভেট কী d = {d}")
print(f"বার্তা m = {message}  ->  সাইফারটেক্সট c = {ciphertext}  ->  ডিক্রিপ্ট = {decrypted}")
assert decrypted == message, "RSA রাউন্ড-ট্রিপ ব্যর্থ!"
print("ডিক্রিপশন যাচাই: সফল — RSA সঠিকভাবে কাজ করছে।")

# ============ ধাপ ৩ · কম্বিনেটরিক্স (M3) — রুট অর্ডারিং গণনা (L13) ============
num_cities = len(cities)
num_orderings = math.factorial(num_cities)

print("\n=== ধাপ ৩ · কম্বিনেটরিক্স (M3) — রুট অর্ডারিং গণনা (L13) ===")
print(f"{num_cities}টি শহর ভ্রমণের সম্ভাব্য ক্রম সংখ্যা = {num_cities}! = {num_orderings}")
print(f"(তুলনার জন্য, L39-এর মতো: ১৫টি শহর হলে 15! = {math.factorial(15):,} — ব্যবহারিকভাবে অসম্ভব)")

# ============ ধাপ ৪ · অ্যালগরিদম বিশ্লেষণ (M8) — প্রতিটি ধাপের জটিলতা ============
print("\n=== ধাপ ৪ · অ্যালগরিদম বিশ্লেষণ (M8) — প্রতিটি ধাপের Big-O ===")
print(f"গ্রাফ ডিগ্রি-চেক (V={V}, E={E})         -> O(V + E)")
print("RSA মডুলার এক্সপোনেনশিয়েশন (fast pow) -> O(log e)   [Class P]")
print(f"সব রুট এনুমারেট করা (n={num_cities})           -> O(n!)     [NP-সম্পূর্ণ, decision version]")

# ============ চূড়ান্ত রিপোর্ট ============
print("\n=== চূড়ান্ত রিপোর্ট ===")
print(f"ইউলার পথযোগ্যতা : {euler_result}")
print(f"RSA রাউন্ড-ট্রিপ : সফল (m={message} -> c={ciphertext} -> m={decrypted})")
print(f"রুট বিকল্প সংখ্যা : {num_orderings}")

    
কোডটি চালিয়ে দেখুন — RSA এনক্রিপশন ও ডিক্রিপশন প্রতিবারই সঠিক মূল বার্তায় ফিরে আসে (কারণ এটি একটি নিশ্চিত গাণিতিক পরিচয়, L27-এর ইউলার'স থিওরেমের সরাসরি ফলাফল), আর ইউলার পথের ফলাফল গ্রাফের গঠনের উপর সম্পূর্ণ নির্ভরশীল।
🚚 রুট-ও-সাইফার টুল Capstone project 🗺️ গ্রাফ থিওরি (M4) Euler path — L20 🔐 নাম্বার থিওরি (M5) RSA — L28 🔢 কম্বিনেটরিক্স (M3) Permutations — L13 📊 জটিলতা বিশ্লেষণ (M8) Big-O of each step
ক্যাপস্টোন — কোর্সের চারটি মূল মডিউল একটি একক প্রয়োগে সংশ্লেষিত।

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

প্রতিটি প্রশ্ন একাধিক মডিউল জুড়ে ভাবতে হবে — নিজে কিছুক্ষণ ভাবুন, তারপর "→ উত্তর" চাপুন।

প্র ০১ ধাপ ৩-এর ডেলিভারি-রুট সমস্যা কেন ধাপ ২-এর এনক্রিপশনের চেয়ে মৌলিকভাবে বেশি কঠিন, যদিও দুটোতেই বড় সংখ্যা জড়িত?

RSA-এর এনক্রিপশন/ডিক্রিপশন ($c=m^e \bmod n$) modular exponentiation ব্যবহার করে, যা repeated squaring (L26) দিয়ে মাত্র $O(\log e)$ ধাপে হয় — এক্সপোনেন্ট $e$ যত বড়ই হোক, ধাপ সংখ্যা তার ডিজিটের সংখ্যার সমানুপাতিক, নিজে তার মানের নয়। এটি Class P। কিন্তু সব শহর ভ্রমণের সেরা ক্রম খোঁজা (TSP, L42) brute force-এ $O(n!)$ — শহরের সংখ্যা $n$ সামান্য বাড়লেই এটি এক্সপোনেনশিয়ালের চেয়েও দ্রুত অসম্ভব হয়ে যায়। "বড় সংখ্যা" নিজে সমস্যা নয় — আসল প্রশ্ন হলো কাজের পরিমাণ ইনপুট সাইজের সাথে কীভাবে স্কেল করে।

প্র ০২ একটি নিখুঁত ৬-চক্র (cycle) গ্রাফে সবগুলো ভার্টেক্সের ডিগ্রি জোড় ছিল (ইউলার সার্কিট সম্ভব)। শুধু একটি অতিরিক্ত রাস্তা যোগ করায় কেন এটি সার্কিট থেকে শুধু "পথে" নেমে এলো?

L18-এর handshake theorem-এর corollary অনুযায়ী, একটি নতুন এজ যোগ করলে ঠিক তার দুই প্রান্তের ভার্টেক্সের ডিগ্রি ১ করে বাড়ে — জোড় থেকে বিজোড়ে পরিণত হয় (বাকি সব ভার্টেক্স অপরিবর্তিত থাকে)। তাই একটি "নিখুঁত" (সব জোড়-ডিগ্রি) গ্রাফে একটিমাত্র অতিরিক্ত এজ যোগ করলে ঠিক ২টি ভার্টেক্স বিজোড়-ডিগ্রি হয়ে যায় — যা L20-এর থিওরেম অনুযায়ী ইউলার সার্কিট থেকে ইউলার পথে (এবং সেই দুই ভার্টেক্সের মধ্যেই) নামিয়ে আনে। এটি দেখায় গ্রাফের একটি ছোট্ট পরিবর্তনও তার topological বৈশিষ্ট্যকে পুরোপুরি বদলে দিতে পারে।

প্র ০৩ শহরের সংখ্যা ৬ থেকে ১৫-তে বাড়লে, এই প্রকল্পের চারটি ধাপের মধ্যে কোনটি প্রথম "বাধা" (bottleneck) হয়ে দাঁড়াবে — এবং এটি L43-এর এম্পিরিক্যাল বেঞ্চমার্কিং দিয়ে কীভাবে যাচাই করবেন?

গ্রাফ ডিগ্রি-চেক ($O(V+E)$) ও RSA ($O(\log e)$) উভয়ই ১৫টি শহরেও তাৎক্ষণিক থাকবে — এরা বহুপদী/লগারিদমিক, তাই ইনপুট সামান্য বাড়লে কার্যত অপরিবর্তিত থাকে। কিন্তু রুট এনুমারেশন ($O(n!)$) হবে দ্রুততম বাধা — L39-এর টেবিল অনুযায়ী $15! \approx 1.3 \times 10^{12}$, যা মিলিয়ন গুণ বেশি কাজ মাত্র $9$টি শহর বৃদ্ধিতে। L43-এর পদ্ধতি প্রয়োগ করে এটি যাচাই করতে, প্রতিটি ধাপকে ইনস্ট্রুমেন্ট করে (কাউন্টার দিয়ে) $n=6,8,10,12$-এ চালিয়ে দেখা যেত রুট-এনুমারেশনের কাউন্ট বাকি দুটোর তুলনায় কত দ্রুত বিস্ফোরিত হয়।

অনুশীলন

  1. পরিবর্তন করুন: কোড সেলে একটি ৭ম শহর ("Rangpur") ও একটি নতুন রাস্তা (যেমন Chittagong-Rangpur) যোগ করুন, যাতে মোট ৪টি বিজোড়-ডিগ্রি ভার্টেক্স তৈরি হয়। Run চাপুন — কোড কী ফলাফল দেয়?

    কোডটি সঠিকভাবে রিপোর্ট করবে "কোনো ইউলার পথ সম্ভব নয়" — কারণ len(odd_degree_cities) এখন ৪ (২-এর বেশি), যা L20-এর থিওরেমের তৃতীয় কেসে পড়ে। এটি নিশ্চিত করে কোডটি শুধু একটি নির্দিষ্ট গ্রাফের জন্য হার্ডকোড করা নয়, বরং থিওরেমটি সাধারণভাবে সঠিকভাবে প্রয়োগ করছে।

  2. ব্যাখ্যা করুন (কোড ছাড়া): RSA মডুলার এক্সপোনেনশিয়েশনের জটিলতা $O(\log e)$ — এক্সপোনেন্ট $e$ যদি ৬০০ ডিজিটের একটি সংখ্যা হতো (বাস্তব RSA-তে যেমন হয়), তাহলেও কেন এটি দ্রুত থাকবে, L26-এর repeated squaring ধারণা ব্যবহার করে ব্যাখ্যা করুন।

    Repeated squaring (L26) $m^e \bmod n$ গণনা করতে $e$-কে বাইনারিতে লিখে, প্রতিটি বিটের জন্য একটি স্কোয়ারিং (এবং প্রয়োজনে একটি গুণ) করে — মোট ধাপ সংখ্যা $e$-এর বিট-সংখ্যার সমানুপাতিক, অর্থাৎ $O(\log_2 e)$। একটি ৬০০-ডিজিটের সংখ্যায় প্রায় $600 \times \log_2 10 \approx 1993$ বিট থাকে — তাই মাত্র প্রায় ২,০০০ স্কোয়ারিং/গুণ ধাপে পুরো গণনা শেষ হয়ে যায়, যা যেকোনো আধুনিক কম্পিউটারে মুহূর্তের ব্যাপার — সরাসরি $e$ বার গুণ করলে যা সম্পূর্ণ অসম্ভব হতো।

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

অভিনন্দন — আপনি কোর্সটি সম্পূর্ণ করেছেন

L01-এ আমরা বলেছিলাম বিচ্ছিন্ন গণিত কম্পিউটার সায়েন্সের প্রকৃত ভাষা — কোনো "থিওরিটিক্যাল" বিষয় নয় যা বাস্তব প্রোগ্রামিং থেকে আলাদা। এই ৪৪টি পাঠে আপনি লজিক ও প্রমাণ পদ্ধতি (M1) থেকে শুরু করে সেট-রিলেশন-ফাংশন (M2), কম্বিনেটরিক্স ও গণনা (M3), গ্রাফ থিওরি (M4), নাম্বার থিওরি ও ক্রিপ্টোগ্রাফি (M5), রিকারেন্স রিলেশন (M6), বুলিয়ান অ্যালজেব্রা ও অটোমাটা (M7), এবং সম্পূর্ণ অ্যালগরিদম বিশ্লেষণ ও জটিলতা তত্ত্ব (M8) পেরিয়ে — এই ক্যাপস্টোনে সবকিছু একত্রিত করেছেন একটি বাস্তব, কার্যকর প্রোগ্রামে। এখন থেকে আপনি যখন কোনো অ্যালগরিদমের Big-O দেখবেন, একটি এনক্রিপ্টেড কানেকশনের পেছনের গণিত ভাববেন, বা একটি গ্রাফ সমস্যার কঠিনতা বিচার করবেন — সেই গণিতটা আর অচেনা লাগবে না। এটি একটি সমাপ্তি নয়, একটি ভিত্তি — DSA কোর্সে এখন আপনি প্রতিটি অ্যালগরিদমের "কেন এটি কাজ করে, কেন এটি এত দ্রুত/ধীর" প্রশ্নের উত্তর নিজে থেকেই বুঝতে পারবেন। অভিনন্দন, এবং শুভকামনা আপনার পরবর্তী ধাপের জন্য।

পূর্ববর্তী পাঠ
অ্যালগরিদম তুলনা — বাস্তব বেঞ্চমার্কিং