পাঠ ৫৫ · ৫৬-এর মধ্যে · মডিউল ১৩
Home / Courses / Formal Language & Automata Theory / Theory of Computation / কেস স্টাডি: আনডিসাইডেবল ও NP-কমপ্লিট

কেস স্টাডি: বিখ্যাত আনডিসাইডেবল ও NP-কমপ্লিট প্রবলেম

Case study: famous undecidable & NP-complete problems
৯ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • তিনটি বিখ্যাত আনডিসাইডেবল সমস্যা এবং তাদের প্রতিটির বাস্তব সফটওয়্যার-ইঞ্জিনিয়ারিং তাৎপর্য
  • তিনটি বিখ্যাত NP-কমপ্লিট সমস্যা এবং তারা কোন বাস্তব ডোমেইনে ছদ্মবেশে হাজির হয়
  • কেন স্ট্যাটিক-অ্যানালাইসিস/লিন্টার টুল কখনো সম্পূর্ণ নিখুঁত হতে পারে না — এবং তাই টেস্টিং কেন এখনো অপরিহার্য
  • একটি বাস্তব, কোড-ভেরিফায়েড গ্রাফ-কালারিং সলভার — exam scheduling সমস্যায় প্রয়োগ করে

১ · তিনটি বিখ্যাত আনডিসাইডেবল সমস্যা

M9-এর পুরো মডিউল আনডিসাইডেবিলিটি নিয়ে ছিল। এই পাঠে সেই তত্ত্বকে তিনটি সবচেয়ে বিখ্যাত, ঐতিহাসিকভাবে গুরুত্বপূর্ণ উদাহরণে সংশ্লেষণ করছি —

হল্টিং প্রবলেম (M9/L39)
একটি প্রোগ্রাম ও ইনপুট দেওয়া থাকলে, প্রোগ্রামটি কখনো থামবে কি না তা সাধারণভাবে নির্ণয় করা অসম্ভব — ভিত্তিমূলক উদাহরণ, ডায়াগোনালাইজেশন দিয়ে প্রমাণিত।
রাইসের থিওরেম-ভিত্তিক প্রশ্ন (M9/L41)
"এই প্রোগ্রাম কি কখনো একটি নির্দিষ্ট এরর মেসেজ প্রিন্ট করবে?" — একটি সত্যিকারের, প্রাসঙ্গিক সফটওয়্যার-ইঞ্জিনিয়ারিং প্রশ্ন, যা প্রোভেবলি সম্পূর্ণভাবে উত্তরযোগ্য নয়।
পোস্ট করেসপন্ডেন্স প্রবলেম (M9/L42)
দুই সেট স্ট্রিং দিয়ে একই ক্রম তৈরি করা সম্ভব কি না — একটি বিশুদ্ধ কম্বিনেটোরিয়াল আনডিসাইডেবল সমস্যা, কম্পাইলার তত্ত্বেও ব্যবহৃত হয়।
কেন লিন্টার/স্ট্যাটিক-অ্যানালাইসিস টুল কখনো সম্পূর্ণ নিখুঁত নয়

"এই কোড কি কখনো একটি নির্দিষ্ট এরর মেসেজ প্রিন্ট করবে?" — এই প্রশ্নটি সম্পূর্ণ, সাধারণ নির্ভুলতার সাথে উত্তর দেওয়া সম্ভব নয় (রাইসের থিওরেম, M9/L41)। এই কারণেই কম্পাইলার ওয়ার্নিং, লিন্টার, ও অন্যান্য স্ট্যাটিক-অ্যানালাইসিস টুল মৌলিকভাবে আনুমানিক (approximate) — তারা কিছু বাগ ধরতে পারে, কিন্তু কখনো সব বাগ ধরার গ্যারান্টি দিতে পারে না, প্রযুক্তি যতই উন্নত হোক না কেন। এই কারণেই ../software-engineering-git/-এর টেস্টিং ও কোড-রিভিউ মডিউল এখনো অপরিহার্য — কিছু বাগ যেকোনো সাধারণ অ্যালগরিদম দিয়ে প্রোভেবলি শনাক্তযোগ্য নয়, শুধু মানুষের বিচার-বিবেচনা বা নির্দিষ্ট টেস্ট কেস দিয়েই ধরা সম্ভব।

২ · তিনটি বিখ্যাত NP-কমপ্লিট সমস্যা

M10-M11-এ আমরা NP-কমপ্লিটনেসের তত্ত্ব তৈরি করেছিলাম। এখন সেই তত্ত্বকে তিনটি সবচেয়ে বহুল-উদ্ধৃত বাস্তব উদাহরণে সংশ্লেষণ করছি —

SAT / 3-SAT (M10/L44, L46, L47)
প্রথম প্রমাণিত NP-কমপ্লিট সমস্যা (কুক-লেভিন থিওরেম) — বুলিয়ান সূত্রকে সন্তুষ্ট করা যায় কি না।
ট্র্যাভেলিং সেলসম্যান প্রবলেম (M10/L47, cross-ref ../dsa/)
সব শহর একবার ভ্রমণ করে সর্বনিম্ন খরচে ফিরে আসার রুট — লজিস্টিক্স ও রাউটিং-এ সরাসরি প্রাসঙ্গিক।
গ্রাফ কালারিং (M10/L47, cross-ref ../computer-architecture/-এর M12/L52 রেজিস্টার-অ্যালোকেশন)
নোডগুলোকে এমনভাবে রং করা যাতে সংযুক্ত নোড একই রং না পায় — এক্সাম/মিটিং শিডিউলিং, রেজিস্টার অ্যালোকেশনে ব্যবহৃত।

লক্ষ্যণীয় — কম্পিউটার আর্কিটেকচার কোর্সের রেজিস্টার অ্যালোকেশন (একটি কম্পাইলার কোন ভ্যারিয়েবলকে কোন CPU রেজিস্টারে রাখবে তা ঠিক করা) নিজেই একটি বাস্তব গ্রাফ-কালারিং ইনস্ট্যান্স — এই কোর্সের বিমূর্ত তত্ত্ব সরাসরি সেই ব্যবহারিক সিদ্ধান্তের ভিত্তি।

৩ · একটি সংশ্লেষিত রেফারেন্স টেবিল

নিচের কোড সেলে উপরের ছয়টি সমস্যাকে একটি একক দিক্‌শনারিতে সংগঠিত করে একটি সম্পূর্ণ রেফারেন্স টেবিল প্রিন্ট করা হয়েছে — কোন মডিউল/পাঠে কভার করা হয়েছে, কোন বাস্তব ডোমেইনে দেখা যায়, এবং ডিসাইডেবল/NP-কমপ্লিট স্ট্যাটাস।

Python
# সংশ্লেষিত রেফারেন্স টেবিল -- বিখ্যাত আনডিসাইডেবল ও NP-কমপ্লিট প্রবলেম

problems = {
    "হল্টিং প্রবলেম": {
        "module_lesson": "M9/L39",
        "domain": "প্রোগ্রাম অ্যানালাইসিস, কম্পাইলার ওয়ার্নিং",
        "status": "আনডিসাইডেবল",
    },
    "রাইসের থিওরেম -- এরর-মেসেজ প্রশ্ন": {
        "module_lesson": "M9/L41",
        "domain": "স্ট্যাটিক অ্যানালাইসিস, লিন্টার, ম্যালওয়্যার ডিটেকশন",
        "status": "আনডিসাইডেবল",
    },
    "পোস্ট করেসপন্ডেন্স প্রবলেম": {
        "module_lesson": "M9/L42",
        "domain": "কম্পাইলার তত্ত্ব, স্ট্রিং-রিরাইটিং সিস্টেম",
        "status": "আনডিসাইডেবল",
    },
    "SAT / 3-SAT": {
        "module_lesson": "M10/L44, L46, L47",
        "domain": "সার্কিট ডিজাইন, শিডিউল ভেরিফিকেশন, AI প্ল্যানিং",
        "status": "NP-কমপ্লিট",
    },
    "ট্র্যাভেলিং সেলসম্যান প্রবলেম": {
        "module_lesson": "M10/L47",
        "domain": "লজিস্টিক্স, ডেলিভারি রাউটিং, সার্কিট-বোর্ড ড্রিলিং",
        "status": "NP-কমপ্লিট",
    },
    "গ্রাফ কালারিং": {
        "module_lesson": "M10/L47",
        "domain": "এক্সাম/মিটিং শিডিউলিং, রেজিস্টার অ্যালোকেশন",
        "status": "NP-কমপ্লিট",
    },
}

print(f"{'সমস্যা':>32} | {'মডিউল/পাঠ':>16} | {'স্ট্যাটাস':>14}")
print("-" * 72)
for name, info in problems.items():
    print(f"{name:>32} | {info['module_lesson']:>16} | {info['status']:>14}")
    print(f"{'':>32} | বাস্তব ডোমেইন: {info['domain']}")

    

৪ · একটি বাস্তব উদাহরণ — গ্রাফ কালারিং দিয়ে exam scheduling

এবার গ্রাফ কালারিংGraph Coloringএকটি গ্রাফের নোডগুলোকে এমনভাবে রং করা যাতে সংযুক্ত (adjacent) কোনো দুটি নোড একই রং না পায় — একটি ক্লাসিক NP-কমপ্লিট সমস্যা।-কে একটি বাস্তব সমস্যায় প্রয়োগ করে দেখি। ধরুন একটি বিশ্ববিদ্যালয়ে কয়েকটি পরীক্ষা শিডিউল করতে হবে — দুটি পরীক্ষা একই সময়ে হতে পারবে না যদি কোনো একজন শিক্ষার্থী দুটোতেই বসে। এটি ঠিক গ্রাফ কালারিং — পরীক্ষাগুলো নোড, দুটি পরীক্ষার মধ্যে একজন শিক্ষার্থী শেয়ার হলে একটি এজ, আর "রং" মানে টাইম-স্লট।

Python
# exam scheduling as graph coloring -- ছোট আকারে brute-force-feasible backtracking সলভার

def graph_coloring(vertices, edges, num_colors):
    adjacency = {v: set() for v in vertices}
    for a, b in edges:
        adjacency[a].add(b)
        adjacency[b].add(a)

    assignment = {}

    def backtrack(index):
        if index == len(vertices):
            return True
        v = vertices[index]
        for color in range(num_colors):
            if all(assignment.get(nb) != color for nb in adjacency[v]):
                assignment[v] = color
                if backtrack(index + 1):
                    return True
                del assignment[v]
        return False

    return dict(assignment) if backtrack(0) else None

# পরীক্ষা নোড; এজ = দুই পরীক্ষায় কমন শিক্ষার্থী আছে, তাই একই স্লটে রাখা যাবে না
exams = ["Math", "Physics", "Chemistry", "Biology", "History", "Art"]
conflicts = [
    ("Math", "Physics"),
    ("Math", "Chemistry"),
    ("Math", "Biology"),      # Math-Physics-Biology একটি ট্রায়াঙ্গেল গঠন করে -> কমপক্ষে ৩ রং লাগবে
    ("Physics", "Biology"),
    ("Chemistry", "Biology"),
    ("Chemistry", "History"),
    ("Biology", "Art"),
    ("History", "Art"),
]

for k in range(1, len(exams) + 1):
    result = graph_coloring(exams, conflicts, k)
    if result is not None:
        print(f"সর্বনিম্ন প্রয়োজনীয় টাইম-স্লট: {k}")
        break

schedule = graph_coloring(exams, conflicts, k)
print(f"{'পরীক্ষা':>10} : টাইম-স্লট")
for e in exams:
    print(f"{e:>10} : {schedule[e]}")

# ভেরিফাই: কোনো দুই সংঘর্ষপূর্ণ (adjacent) পরীক্ষা একই স্লট শেয়ার করছে না
violations = [(a, b) for a, b in conflicts if schedule[a] == schedule[b]]
assert not violations, f"সংঘর্ষ পাওয়া গেছে: {violations}"
print("ভেরিফাইড: কোনো দুই সংঘর্ষপূর্ণ পরীক্ষা একই টাইম-স্লট শেয়ার করছে না")

    
লক্ষ্য করুন কোডের শেষে assert not violations সরাসরি প্রমাণ করে সলভারের আউটপুট বৈধ — প্রতিটি এজ $(a, b)$ পরীক্ষা করে নিশ্চিত করা হয়েছে schedule[a] != schedule[b]। এই ছোট গ্রাফের জন্য brute-force backtracking যথেষ্ট দ্রুত, কিন্তু গ্রাফের আকার (পরীক্ষা ও সংযোগের সংখ্যা) বাড়ার সাথে সাথে এই সলভার এক্সপোনেনশিয়াল সময় নেবে — ঠিক যা আমরা আশা করি একটি NP-কমপ্লিট সমস্যা থেকে (M10/L45-L47)।
মূল কথা · Key takeaway

M9-M10-এর বিমূর্ত তত্ত্ব — হল্টিং প্রবলেম থেকে গ্রাফ কালারিং পর্যন্ত — সবই বাস্তব, প্রতিদিনের সফটওয়্যার-ইঞ্জিনিয়ারিং প্রশ্নের সাথে সরাসরি যুক্ত। যখন আপনি একটি বাস্তব সমস্যাকে এই পরিচিত প্যাটার্নগুলোর একটির সাথে মেলাতে পারবেন, তখনই আপনি জানবেন সেই সমস্যার তাত্ত্বিক সীমা কোথায় — এবং সেই অনুযায়ী আপনার প্রকৌশল-কৌশল ঠিক করতে পারবেন।

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

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

প্র ০১ "স্ট্যাটিক-অ্যানালাইসিস টুল প্রোভেবলি অসম্পূর্ণ" — তাহলে কি বাগ ধরার জন্য টেস্টিং একেবারে অকেজো স্ট্যাটিক-অ্যানালাইসিস প্রতিস্থাপন করবে?

না, উল্টো — ঠিক এই কারণেই দুটোই একসাথে প্রয়োজন। স্ট্যাটিক অ্যানালাইসিস দ্রুত, ব্যাপক (কিন্তু অসম্পূর্ণ) কভারেজ দেয়, আনুমানিকভাবে সাধারণ ভুল ধরে। টেস্টিং (cross-ref ../software-engineering-git/) নির্দিষ্ট, কংক্রিট সিনারিওতে আচরণ যাচাই করে, যা রাইসের থিওরেমের সীমাবদ্ধতার বাইরে কাজ করে (যেহেতু এটি একটি নির্দিষ্ট ইনপুটে চালিয়ে দেখা, সাধারণ সব-ইনপুটের প্রশ্নের উত্তর দেওয়া নয়)। কোনো একক টুল/টেকনিক কখনো "সম্পূর্ণ সঠিকতার" গ্যারান্টি দিতে পারে না — এটিই এই তত্ত্বের সবচেয়ে ব্যবহারিক শিক্ষা।

প্র ০২ SAT সমস্যাটি "প্রথম প্রমাণিত NP-কমপ্লিট সমস্যা" (কুক-লেভিন থিওরেম) কেন এত গুরুত্বপূর্ণ, শুধু SAT-এর জন্যই নয়?

কারণ একবার SAT NP-কমপ্লিট প্রমাণিত হওয়ার পর (M10/L46), অন্য যেকোনো NP সমস্যাকে NP-কমপ্লিট প্রমাণ করার জন্য শুধু SAT (বা অন্য কোনো ইতিমধ্যে-প্রমাণিত NP-কমপ্লিট সমস্যা) থেকে একটি পলিনমিয়াল-টাইম রিডাকশন দেখাতে হয় (M10/L45) — সরাসরি Cook-Levin-এর জটিল মূল প্রমাণ পুনরাবৃত্তি করার প্রয়োজন নেই। এই কারণেই আজ শত শত সমস্যা NP-কমপ্লিট প্রমাণিত — প্রতিটি একটি "রিডাকশন-চেইন" এর মাধ্যমে সরাসরি বা পরোক্ষভাবে SAT-এর সাথে যুক্ত।

প্র ০৩ উপরের exam-scheduling উদাহরণে কেন কমপক্ষে ৩টি টাইম-স্লট প্রয়োজন, ২টি নয়?

কারণ conflicts তালিকায় Math-Physics, Math-Biology, ও Physics-Biology — এই তিনটি এজ একসাথে একটি ট্রায়াঙ্গেল (triangle) গঠন করে। একটি গ্রাফ থিওরির মৌলিক ফলাফল: যেকোনো গ্রাফে যদি একটি ট্রায়াঙ্গেল থাকে, তাহলে সেই তিনটি নোডকে বৈধভাবে রং করতে কমপক্ষে ৩টি ভিন্ন রং প্রয়োজন (যেকোনো দুটি নোডই একে অপরের সাথে সংযুক্ত, তাই কোনো দুটিই একই রং শেয়ার করতে পারবে না) — তাই ২টি স্লট দিয়ে এই নির্দিষ্ট গ্রাফের বৈধ শিডিউল তৈরি করা গাণিতিকভাবেই অসম্ভব।

অনুশীলন

  1. চিন্তা করুন: "একটি প্রোগ্রাম মেমরি লিক করবে কি না" প্রশ্নটি কি ডিসাইডেবল, না আনডিসাইডেবল? আপনার যুক্তি লিখুন।

    সাধারণভাবে আনডিসাইডেবল। "মেমরি লিক করা" প্রোগ্রামের একটি নন-ট্রিভিয়াল সিমান্টিক প্রপার্টি (কিছু প্রোগ্রাম লিক করে, কিছু করে না, এবং এটি প্রোগ্রামের ইনপুট-আউটপুট আচরণের একটি বৈশিষ্ট্য) — তাই রাইসের থিওরেম (M9/L41) সরাসরি প্রযোজ্য। এই কারণেই বাস্তব মেমরি-লিক ডিটেক্টর (যেমন Valgrind) সম্পূর্ণ নিশ্চিততার সাথে নয়, বরং রানটাইম মনিটরিং ও হেউরিস্টিক দিয়ে কাজ করে — কিছু লিক মিস হতে পারে, কিছু মিথ্যা পজিটিভ আসতে পারে।

  2. পরীক্ষা করুন: উপরের গ্রাফ-কালারিং কোড সেলে conflicts-এ একটি নতুন এজ ("History", "Math") যোগ করে Run চেপে দেখুন সর্বনিম্ন প্রয়োজনীয় স্লট-সংখ্যা বদলায় কি না।

    এই নির্দিষ্ট গ্রাফে ("History", "Math") যোগ করলে নতুন কোনো বড় ট্রায়াঙ্গেল বা আরও জটিল সংঘর্ষ কাঠামো তৈরি হয় কি না তার উপর নির্ভর করে ফলাফল বদলাতে পারে বা নাও পারে — কোড চালিয়ে সরাসরি দেখুন সলভার নতুন সর্বনিম্ন সংখ্যা বের করে এবং assert not violations এখনও পাস করে কি না, যা প্রমাণ করে সমাধানটি এখনও বৈধ।

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

আগের পাঠ
কেস স্টাডি: বাস্তব রেগেক্স ইঞ্জিন ও তাদের সীমাবদ্ধতা