আরও NP-কমপ্লিট প্রবলেম
এই পাঠে যা শিখবেন
- NP-কমপ্লিটনেসের পুরো ক্যাটালগ ঐতিহাসিকভাবে কীভাবে একটি রিডাকশন-চেইন দিয়ে তৈরি হলো
- রিডিউসিবিলিটির ট্রানজিটিভ প্রপার্টি এবং কেন এটি এই পুরো কৌশলকে সম্ভব করে
- ৭টি বিখ্যাত, বাস্তবে-প্রাসঙ্গিক NP-কমপ্লিট প্রবলেমের নাম ও তাদের একে অপরের সাথে সম্পর্ক
- SAT থেকে 3-SAT-এর প্রকৃত ক্লজ-স্প্লিটিং রিডাকশন — Python-এ বাস্তবায়ন ও ব্রুট-ফোর্স যাচাই
১ · রিডাকশনের চেইন — ক্যাটালগ কীভাবে তৈরি হলো
L45-এ আমরা পলিনমিয়াল-টাইম রিডাকশনPolynomial-Time Reduction$A \leq_p B$ মানে A-এর যেকোনো ইনস্ট্যান্সকে পলিনমিয়াল সময়ে B-এর একটি ইনস্ট্যান্সে রূপান্তর করা যায়, যেখানে উত্তর অপরিবর্তিত থাকে। সংজ্ঞায়িত করেছিলাম, আর L46-এ কুক-লেভিন থিওরেম দেখিয়েছে SAT NP-কমপ্লিট — অর্থাৎ NP-এর প্রতিটি প্রবলেম SAT-এ রিডিউস হয়। এখন প্রশ্ন হলো — বাকি শত শত পরিচিত NP-কমপ্লিট প্রবলেম কীভাবে প্রমাণিত হয়েছে? প্রতিটির জন্য কি কুক-লেভিনের মতো একটি নতুন, from-scratch প্রমাণ লাগে?
উত্তর — না। রিডিউসিবিলিটি একটি ট্রানজিটিভ সম্পর্ক (relation), যা সরাসরি প্রমাণযোগ্য:
$$A \leq_p B \ \text{এবং}\ B \leq_p C \implies A \leq_p C$$
এর মানে — একবার SAT NP-কমপ্লিট প্রতিষ্ঠিত হয়ে গেলে, যদি কেউ দেখাতে পারে $\text{SAT} \leq_p X$ (এবং X নিজেই NP-তে আছে), তাহলে X-ও NP-কমপ্লিট। আর তারপর $X \leq_p Y$ দেখালে Y-ও NP-কমপ্লিট — এভাবে একটি লম্বা রিডাকশন-চেইন তৈরি হয়, প্রতিটি নতুন প্রবলেম আগের কোনো একটি ইতিমধ্যে-জানা NP-কমপ্লিট প্রবলেম থেকে রিডিউস করে প্রমাণিত হয়। ঐতিহাসিকভাবে ঠিক এভাবেই — SAT থেকে শুরু করে ক্রমাগত রিডাকশনের মাধ্যমে — কয়েক হাজার NP-কমপ্লিট প্রবলেমের ক্যাটালগ তৈরি হয়েছে।
২ · পরিচিত NP-কমপ্লিট প্রবলেমের ক্যাটালগ
SAT-এর একটি বিশেষ রূপ যেখানে প্রতিটি ক্লজে ঠিক ৩টি লিটারেল থাকে — সাধারণ SAT থেকে একটি স্ট্যান্ডার্ড ক্লজ-স্প্লিটিং রূপান্তরের মাধ্যমে রিডিউস হয়, এবং নিজেই প্রায়শই আরও রিডাকশনের জন্য "শুরুর বিন্দু" হিসেবে ব্যবহৃত হয় (সরলতর গঠনের কারণে রিডিউস করা সহজ)।
একই গ্রাফের ঘনিষ্ঠভাবে সম্পর্কিত তিনটি প্রশ্ন (L45-এর ভার্টেক্স-কভার রিডাকশনের সরাসরি সম্প্রসারণ) — G-তে সাইজ k-এর একটি ক্লিক আছে ঠিক তখনই যখন G-এর কমপ্লিমেন্ট গ্রাফে সাইজ k-এর একটি ইনডিপেন্ডেন্ট সেট আছে।
একটি গ্রাফে প্রতিটি ভার্টেক্স ঠিক একবার ভ্রমণ করে এমন পথ/চক্র আছে কি না — আর ট্র্যাভেলিং সেলসম্যান প্রবলেম (TSP) এর একটি ওজনযুক্ত-অপ্টিমাইজেশন সংস্করণ, বাস্তব-জগতে সত্যিই গুরুত্বপূর্ণ (cross-ref
../dsa/)।একটি গ্রাফের ভার্টেক্সগুলোকে k-টি রঙ দিয়ে রঙ করা যায় কি না, যাতে সংলগ্ন (adjacent) কোনো দুটি ভার্টেক্সের রঙ একই না হয়।
একটি সংখ্যার সেট থেকে এমন একটি সাবসেট আছে কি না যার যোগফল ঠিক একটি নির্দিষ্ট লক্ষ্য মানের সমান।
এই তালিকার প্রতিটি রিডাকশন পুরোপুরি বাস্তবায়ন করা এই একটি পাঠের পরিসরের বাইরে — বরং আমরা সবচেয়ে মৌলিক, বাকি সবকিছুর "গেটওয়ে" রিডাকশনটি গভীরভাবে দেখব ও বাস্তবায়ন করব: সাধারণ SAT থেকে 3-SAT। এটি বোঝা গেলে বাকি রিডাকশনগুলোর যুক্তি (একই স্টাইলে গঠনমূলক রূপান্তর) অনুসরণ করা অনেক সহজ হয়ে যায়।
৩ · SAT থেকে 3-SAT — ক্লজ-স্প্লিটিং রূপান্তর
একটি সাধারণ CNF ফর্মুলার যেকোনো ক্লজে ঠিক ৩টির বেশি লিটারেল থাকতে পারে — যেমন $(\ell_1 \vee \ell_2 \vee \ell_3 \vee \ell_4 \vee \ell_5)$। এই ক্লজটিকে সমতুল্য (satisfiability-preserving) একাধিক ৩-লিটারেল ক্লজে ভাঙার স্ট্যান্ডার্ড কৌশল হলো নতুন সহায়ক (auxiliary) ভ্যারিয়েবল $y_1, y_2, \ldots$ যোগ করা:
$$(\ell_1 \vee \ell_2 \vee \ell_3 \vee \cdots \vee \ell_k) \quad\Longrightarrow\quad (\ell_1 \vee \ell_2 \vee y_1) \wedge (\neg y_1 \vee \ell_3 \vee y_2) \wedge \cdots \wedge (\neg y_{k-3} \vee \ell_{k-1} \vee \ell_k)$$
সহজভাবে — $y_i$ "এতক্ষণ পর্যন্ত $\ell_3, \ldots, \ell_{i+2}$-এর মধ্যে অন্তত একটি সত্য হয়েছে" এই তথ্যটি এক ক্লজ থেকে পরের ক্লজে বহন করে নিয়ে যায়। মূল ক্লজ সিদ্ধযোগ্য (satisfiable) হলে এই চেইনের প্রতিটি $y_i$-কে সঠিক মান বসিয়ে পুরো চেইন সিদ্ধযোগ্য করা যায় — আর মূল ক্লজের সব লিটারেল মিথ্যা হলে, $y_i$-এর যেকোনো মান দিয়েই চেইনের কোনো না কোনো ক্লজ ব্যর্থ হবে। ফলে মূল ফর্মুলা সিদ্ধযোগ্য হলে এবং শুধু তখনই রূপান্তরিত 3-SAT ফর্মুলা সিদ্ধযোগ্য।
# SAT (যেকোনো ক্লজ-দৈর্ঘ্য) থেকে 3-SAT-এ ক্লজ-স্প্লিটিং রিডাকশন -- বাস্তব, কার্যকর কোড
# লিটারেল প্রতিনিধিত্ব: স্ট্রিং, নেগেশনের জন্য সামনে '-' -- যেমন "x1" বা "-x1"
import itertools
def neg(lit):
return lit[1:] if lit.startswith('-') else '-' + lit
def var_of(lit):
return lit[1:] if lit.startswith('-') else lit
def all_vars(formula):
vs = set()
for clause in formula:
for lit in clause:
vs.add(var_of(lit))
return sorted(vs)
def eval_formula(formula, assignment):
# ফর্মুলা সত্য হয় শুধু তখনই যখন প্রতিটি ক্লজ সত্য (AND অফ ক্লজ)
for clause in formula:
clause_true = False
for lit in clause:
val = assignment[var_of(lit)]
if lit.startswith('-'):
val = not val
if val:
clause_true = True
break
if not clause_true:
return False
return True
def brute_force_sat(formula):
# সব ভ্যারিয়েবলের সব সম্ভাব্য True/False কম্বিনেশন চেষ্টা করে সিদ্ধযোগ্যতা যাচাই
vs = all_vars(formula)
for bits in itertools.product([False, True], repeat=len(vs)):
assignment = dict(zip(vs, bits))
if eval_formula(formula, assignment):
return True, assignment
return False, None
def reduce_sat_to_3sat(formula):
# প্রতিটি লম্বা ক্লজকে নতুন সহায়ক ভ্যারিয়েবল দিয়ে ৩-লিটারেল ক্লজের চেইনে ভাঙা হয়
new_formula = []
counter = [0]
def fresh():
counter[0] += 1
return f"y{counter[0]}"
for clause in formula:
k = len(clause)
if k <= 3:
new_formula.append(list(clause))
continue
prev_aux = fresh()
new_formula.append([clause[0], clause[1], prev_aux])
for i in range(2, k - 2):
new_aux = fresh()
new_formula.append([neg(prev_aux), clause[i], new_aux])
prev_aux = new_aux
new_formula.append([neg(prev_aux), clause[k - 2], clause[k - 1]])
return new_formula
def is_3cnf(formula):
return all(len(clause) <= 3 for clause in formula)
# একটি ৫-লিটারেল ক্লজসহ একটি ফর্মুলা
formula = [
["x1", "x2", "x3", "x4", "x5"], # দৈর্ঘ্য ৫ -- সাধারণ CNF-এ বৈধ, 3-SAT-এ নয়
["-x1"], ["-x2"], ["-x3"], ["-x4"],
]
sat_before, assign_before = brute_force_sat(formula)
print("মূল ফর্মুলা (সাধারণ CNF) সিদ্ধযোগ্য:", sat_before, "|", assign_before)
reduced = reduce_sat_to_3sat(formula)
print("\nরূপান্তরিত 3-SAT ক্লজসমূহ:")
for c in reduced:
print(" ", c)
print("প্রতিটি ক্লজ <=3 লিটারেল:", is_3cnf(reduced))
sat_after, assign_after = brute_force_sat(reduced)
print("\nরূপান্তরিত ফর্মুলা সিদ্ধযোগ্য:", sat_after, "|", assign_after)
print("\n>> মূল ও রূপান্তরিত ফর্মুলার সিদ্ধযোগ্যতা মিলছে:", sat_before == sat_after)
# একটি UNSATISFIABLE কেসও যাচাই -- '-x5' যোগ করলে x5=True বাধ্যতাও ভেঙে যায়
unsat_formula = formula + [["-x5"]]
sat_u_before, _ = brute_force_sat(unsat_formula)
sat_u_after, _ = brute_force_sat(reduce_sat_to_3sat(unsat_formula))
print("UNSAT কেস -- মূল:", sat_u_before, "| রূপান্তরিত:", sat_u_after, "| মিলছে:", sat_u_before == sat_u_after)
reduce_sat_to_3sat কোনো সিদ্ধযোগ্যতা নিজে সমাধান করে না — এটি শুধু ফর্মুলার আকৃতি
বদলায় (একটি পলিনমিয়াল-টাইম রূপান্তর, ক্লজপ্রতি O(k) নতুন ক্লজ ও ভ্যারিয়েবল)। মূল ও রূপান্তরিত ফর্মুলার
সিদ্ধযোগ্যতা যে সবসময় একই থাকে — সেটাই এই রিডাকশনের সঠিকতার (correctness) মূল দাবি, যা উপরের কোড উভয় ফর্মুলাতেই
স্বাধীন ব্রুট-ফোর্স চালিয়ে সরাসরি যাচাই করেছে।
একবার SAT NP-কমপ্লিট প্রতিষ্ঠিত হলে, ট্রানজিটিভ রিডাকশনের একটি চেইন — SAT → 3-SAT → ভার্টেক্স কভার/ইনডিপেন্ডেন্ট সেট/ক্লিক → হ্যামিল্টনিয়ান পাথ/TSP → গ্রাফ কালারিং → সাবসেট সাম, এবং আরও অনেক — পুরো ক্যাটালগ তৈরি করে। প্রতিটি নতুন রিডাকশনের জন্য শুধু একটি জিনিস দরকার: একটি ইতিমধ্যে-জানা NP-কমপ্লিট প্রবলেম থেকে নতুন প্রবলেমে একটি পলিনমিয়াল-টাইম, সিদ্ধযোগ্যতা-সংরক্ষণকারী রূপান্তর।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ রিডিউসিবিলিটি ট্রানজিটিভ না হলে কী সমস্যা হতো?
ট্রানজিটিভিটি ছাড়া, প্রতিটি নতুন প্রবলেম NP-কমপ্লিট প্রমাণ করতে সরাসরি কুক-লেভিনের মতো "NP-এর প্রতিটি প্রবলেম এখানে রিডিউস হয়" — এই from-scratch, অনেক জটিল প্রমাণ আবার নতুন করে লিখতে হতো। ট্রানজিটিভিটি এই কাজ একবারই (SAT-এর জন্য) করে, তারপর প্রতিটি নতুন প্রবলেমের জন্য শুধু "এটি একটি ইতিমধ্যে-জানা NP-কমপ্লিট প্রবলেম থেকে রিডিউস হয়" — এই তুলনামূলক সহজ ধাপটুকু দেখালেই যথেষ্ট, যা এই পুরো ক্যাটালগ তৈরি করা ব্যবহারিকভাবে সম্ভব করেছে।
প্র ০২ 3-SAT কেন প্রায়শই "শুরুর বিন্দু" হিসেবে ব্যবহৃত হয়, সরাসরি SAT নয়?
3-SAT-এর প্রতিটি ক্লজের গঠন সুনির্দিষ্ট ও সীমাবদ্ধ (ঠিক ৩ লিটারেল) — এই কাঠামোগত নিয়মিততা একটি নির্দিষ্ট, সীমাবদ্ধ "গ্যাজেট" (gadget) ডিজাইন করে অন্য প্রবলেমে রিডিউস করা সহজ করে তোলে, যেখানে সাধারণ SAT-এ ক্লজের দৈর্ঘ্য যেকোনো হতে পারায় গ্যাজেট ডিজাইন অনেক বেশি জটিল হয়ে যায়। তাই SAT → 3-SAT রিডাকশন একবার প্রতিষ্ঠিত হওয়ার পর, বাকি প্রায় সব বিখ্যাত রিডাকশন সরাসরি 3-SAT থেকেই শুরু হয়।
প্র ০৩ রূপান্তরিত 3-SAT ফর্মুলায় নতুন ভ্যারিয়েবল $y_i$ যোগ হওয়ার পরও কেন বলা যায় এটি একই "উত্তর" দেয়?
কারণ প্রশ্নটা "$x_1, \ldots, x_n$-এর কোনো অ্যাসাইনমেন্ট আছে কি যা মূল ফর্মুলা সিদ্ধ করে" — এই প্রশ্নের উত্তর "হ্যাঁ/না" অপরিবর্তিত থাকে যখন আমরা জিজ্ঞেস করি "$x_1,\ldots,x_n,y_1,\ldots,y_m$-এর কোনো অ্যাসাইনমেন্ট আছে কি যা রূপান্তরিত ফর্মুলা সিদ্ধ করে" — নতুন $y_i$ ভ্যারিয়েবলগুলো শুধু "সহায়ক বহনকারী" (carrier), মূল প্রশ্নের যুক্তিতে কোনো নতুন স্বাধীনতা যোগ করে না, শুধু ক্লজের আকৃতি ভাঙে। উপরের কোড ঠিক এটাই ব্রুট-ফোর্স দিয়ে যাচাই করেছে — উভয় ফর্মুলার সিদ্ধযোগ্যতা সবসময় মিলে যায়।
অনুশীলন
-
চিন্তা করুন: ক্লিক ও ইনডিপেন্ডেন্ট সেট-এর মধ্যে সম্পর্কটি ("G-তে সাইজ k-এর ক্লিক আছে ঠিক তখনই যখন G-এর কমপ্লিমেন্ট গ্রাফে সাইজ k-এর ইনডিপেন্ডেন্ট সেট আছে") হাতে-আঁকা একটি ছোট ৪-ভার্টেক্স গ্রাফে যাচাই করুন — গ্রাফটি ও তার কমপ্লিমেন্ট দুটোই এঁকে দেখুন সম্পর্কটি সত্যিই ধরে কি না।
ধরুন G-তে ভার্টেক্স {A,B,C,D} এবং এজ {AB, AC, BC} (A,B,C একটি ত্রিভুজ/ক্লিক সাইজ ৩, D বিচ্ছিন্ন)। G-এর কমপ্লিমেন্টে এজ হবে {AD, BD, CD} (যা G-তে নেই) — এখানে {A,B,C} কমপ্লিমেন্টে একে অপরের সাথে সংযুক্ত নয় (কোনো এজ AB, AC, BC কমপ্লিমেন্টে নেই), তাই {A,B,C} কমপ্লিমেন্টে একটি ইনডিপেন্ডেন্ট সেট সাইজ ৩ — ঠিক G-এর ক্লিকের সাইজের সমান, সম্পর্কটি এই উদাহরণে সঠিকভাবে মিলে যায়।
-
পরীক্ষা করুন: উপরের কোড সেলে
formula-তে একটি ৬-লিটারেল ক্লজ (যেমন["x1","x2","x3","x4","x5","x6"]) যোগ/পরিবর্তন করে Run চাপুন — কতগুলো নতুন সহায়ক ভ্যারিয়েবল ও ক্লজ তৈরি হয় গুনে দেখুন, এবংis_3cnfসত্যিইTrueফেরত দেয় কি না নিশ্চিত করুন।k=৬ লিটারেলের একটি ক্লজে $k-3=3$টি সহায়ক ভ্যারিয়েবল ($y_1,y_2,y_3$) এবং $k-2=4$টি নতুন ৩-লিটারেল ক্লজ তৈরি হওয়ার কথা — কোড চালিয়ে ঠিক এই সংখ্যাগুলো মিলছে কি না গুনে যাচাই করুন। প্রতিটি নতুন ক্লজের দৈর্ঘ্য ঠিক ৩ (বা তার কম, ছোট ক্লজের ক্ষেত্রে) হওয়ায়
is_3cnfসবসময়Trueফেরত দেওয়ার কথা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — স্পেস কমপ্লেক্সিটি ও PSPACE — টাইমের বদলে মেমোরি দিয়ে জটিলতা মাপা।
- Data Structures & Algorithms কোর্স সঙ্গী কোর্স TSP ও গ্রাফ-ভিত্তিক অপ্টিমাইজেশন প্রবলেমের অ্যালগরিদমিক দিক DSA কোর্সে দেখুন — এই পাঠ তাদের গাণিতিক জটিলতার প্রমাণ দেয়।
- পাঠ ৪৬ — কুক-লেভিন থিওরেম পূর্ববর্তী পাঠ SAT কেন প্রথম, ভিত্তিমূলক NP-কমপ্লিট প্রবলেম — এই পাঠের রিডাকশন-চেইনের শুরুর বিন্দু।