DFA মিনিমাইজেশন ও ডিসিশন প্রপার্টি
এই পাঠে যা শিখবেন
- L15-এর ইকুইভ্যালেন্স ক্লাস থেকে মিনিমাল DFA-র ইউনিকনেস — কেন এটি একটি অনিবার্য সিদ্ধান্ত
- এমপ্টিনেস, ফাইনাইটনেস, ইকুইভ্যালেন্স — তিনটি ডিসিশন প্রপার্টি এবং প্রতিটি কেন ডিসাইডেবল
- M9-এর টুরিং মেশিনের বিপরীতে এই "সহজে ডিসাইডেবল" ব্যাপারটির গুরুত্ব — প্রথম প্রিভিউ
- Python-এ real
is_empty— BFS রিচেবিলিটি চেক - Python-এ real
are_equivalent— সিমেট্রিক ডিফারেন্স DFA বানিয়ে এমপ্টিনেস চেক
১ · ইউনিক মিনিমাল DFA — L15-এর সরাসরি payoff
L15-এর মাইহিল-নেরোড থিওরেম থেকে সরাসরি অনুসৃত — প্রতিটি রেগুলার ল্যাঙ্গুয়েজের একটি ইউনিক (up to state renaming) মিনিমাল DFA থাকে, যার স্টেট-সংখ্যা ঠিক $\equiv_L$ রিলেশনের ইকুইভ্যালেন্স ক্লাস-সংখ্যার সমান। Programming Languages & Compiler Design কোর্সের M4/L19-এ ইতিমধ্যে প্র্যাকটিক্যাল পার্টিশন-রিফাইনমেন্টPartition Refinementএকটি DFA-র স্টেটগুলোকে ধাপে ধাপে ছোট, সমতুল্য গ্রুপে ভাগ করে মিনিমাইজেশন করার স্ট্যান্ডার্ড অ্যালগরিদম — লেক্সার দ্রুততর করার জন্য সেই কোর্সে ব্যবহারিকভাবে ইমপ্লিমেন্ট করা হয়েছে। মিনিমাইজেশন অ্যালগরিদম দেখানো হয়েছে (লেক্সার দ্রুততর করার উদ্দেশ্যে) — এই পাঠ পুনরায় সেই অ্যালগরিদম ইমপ্লিমেন্ট করছে না, বরং দেখাচ্ছে কেন এটি সঠিক এবং কেন একটি ইউনিক ন্যূনতম অস্তিত্ব করে — এই নিশ্চয়তাটাই L15-এর থিওরেম দেয়।
২ · তিনটি ডিসিশন প্রপার্টি
DFA মেশিনারি (এবং তার সাথে L12-এর ক্লোজার কনস্ট্রাকশন) থাকায় রেগুলার ল্যাঙ্গুয়েজ নিয়ে বেশ কিছু গুরুত্বপূর্ণ প্রশ্নের উত্তর অ্যালগরিদমিকভাবে — একটি নির্দিষ্ট, সবসময়-শেষ-হওয়া প্রসিডিউর দিয়ে — নিশ্চিতভাবে বের করা যায়।
ডিসাইডেবল। $L(M)$ খালি নয় ঠিক তখনই যখন start state থেকে কোনো accept state-এ পৌঁছানো
যায় — এটি একটি সাধারণ গ্রাফ রিচেবিলিটিGraph Reachabilityএকটি গ্রাফে একটি নোড থেকে আরেকটি নোডে পৌঁছানো যায় কি না তা BFS/DFS দিয়ে যাচাই — ../dsa/ কোর্সে বিস্তারিত।
প্রশ্ন (../dsa/-এর গ্রাফ ট্র্যাভার্সাল অ্যালগরিদম সরাসরি প্রযোজ্য), যা BFS/DFS দিয়ে সবসময়
ফাইনাইট সময়ে ($O(|Q|+|Q|\cdot|\Sigma|)$) নিশ্চিতভাবে সমাধানযোগ্য।
ডিসাইডেবল। $L(M)$ অসীম ঠিক তখনই যখন এমন একটি রিচেবল সাইকেল আছে যা থেকে কোনো accept state-এ পৌঁছানো যায় (সাইকেল মানে যতবার ইচ্ছা "লুপ" করে চিরকাল ভিন্ন-দৈর্ঘ্যের নতুন স্ট্রিং বানানো যায়) — একটি সাইকেল-ডিটেকশন গ্রাফ অ্যালগরিদম দিয়ে সমাধানযোগ্য।
ডিসাইডেবল। $L(M_1) = L(M_2) \iff L(M_1) \triangle L(M_2) = \emptyset$ (সিমেট্রিক ডিফারেন্স খালি) — L12-এর ক্লোজার কনস্ট্রাকশন ব্যবহার করে সিমেট্রিক-ডিফারেন্স DFA বানিয়ে, তারপর তার এমপ্টিনেস চেক করলেই হয়। তিনটি ইতিমধ্যে-প্রমাণিত টেকনিক (ক্লোজার, এমপ্টিনেস) একসাথে জুড়েই এই তৃতীয় প্রশ্নের সমাধান — একটি সুন্দর উদাহরণ কীভাবে সাধারণ বিল্ডিং ব্লক দিয়ে জটিল প্রশ্নের সমাধান বানানো যায়।
৩ · কোডে যাচাই — is_empty ও are_equivalent
নিচের কোড সেলে real is_empty(dfa) (BFS রিচেবিলিটি) ও are_equivalent(dfa1, dfa2)
(সিমেট্রিক-ডিফারেন্স DFA + এমপ্টিনেস) লেখা হয়েছে। is_empty টেস্ট করা হচ্ছে একটি স্বাভাবিক DFA
(নন-এম্পটি) আর একটি ইচ্ছাকৃতভাবে অপ্রাপ্য accept state-সহ DFA (এম্পটি) দিয়ে।
are_equivalent টেস্ট করা হচ্ছে দুটো ভিন্ন-গঠনের কিন্তু একই ভাষার DFA (একটি
মিনিমাল ২-স্টেট, আরেকটি একটি অপ্রয়োজনীয় অতিরিক্ত বিট ট্র্যাক করা ৪-স্টেট) — এবং একটি সম্পূর্ণ ভিন্ন ভাষার
DFA দিয়ে।
from collections import deque
class DFA:
def __init__(self, states, alphabet, transitions, start, accept_states):
self.states = set(states)
self.alphabet = set(alphabet)
self.transitions = transitions
self.start = start
self.accept_states = set(accept_states)
for q in self.states:
for a in self.alphabet:
assert (q, a) in self.transitions
def run(self, s):
state = self.start
for ch in s:
state = self.transitions[(state, ch)]
return state
def accepts(self, s):
return self.run(s) in self.accept_states
def is_empty(dfa):
# L(dfa) খালি <=> start থেকে BFS-এ কোনো accept state-এ পৌঁছানো যায় না (../dsa/-স্টাইল গ্রাফ রিচেবিলিটি)
visited = {dfa.start}
queue = deque([dfa.start])
while queue:
q = queue.popleft()
if q in dfa.accept_states:
return False
for a in dfa.alphabet:
nxt = dfa.transitions[(q, a)]
if nxt not in visited:
visited.add(nxt)
queue.append(nxt)
return True
def symmetric_difference_dfa(dfa1, dfa2):
# L12-এর প্রোডাক্ট-কনস্ট্রাকশন স্টাইল -- accept হয় ঠিক তখনই যখন দুই DFA-এর সিদ্ধান্ত ভিন্ন (XOR)
assert dfa1.alphabet == dfa2.alphabet
states = [(q1, q2) for q1 in dfa1.states for q2 in dfa2.states]
transitions = {}
for (q1, q2) in states:
for a in dfa1.alphabet:
transitions[((q1, q2), a)] = (dfa1.transitions[(q1, a)], dfa2.transitions[(q2, a)])
start = (dfa1.start, dfa2.start)
accept_states = [(q1, q2) for (q1, q2) in states
if (q1 in dfa1.accept_states) != (q2 in dfa2.accept_states)]
return DFA(states, dfa1.alphabet, transitions, start, accept_states)
def are_equivalent(dfa1, dfa2):
# L(dfa1) == L(dfa2) <=> তাদের সিমেট্রিক ডিফারেন্স খালি
return is_empty(symmetric_difference_dfa(dfa1, dfa2))
# --- is_empty টেস্ট ---
reachable_accept = DFA(
states={"s0", "s1"}, alphabet={"0", "1"},
transitions={("s0","0"):"s1", ("s0","1"):"s1", ("s1","0"):"s1", ("s1","1"):"s1"},
start="s0", accept_states={"s1"},
)
print("স্বাভাবিক DFA (accept state reachable) is_empty:", is_empty(reachable_accept), " (প্রত্যাশিত False)")
unreachable_accept = DFA(
states={"s0", "s1", "dead"}, alphabet={"0", "1"},
transitions={("s0","0"):"s0", ("s0","1"):"s0", ("s1","0"):"s1", ("s1","1"):"s1",
("dead","0"):"dead", ("dead","1"):"dead"},
start="s0", accept_states={"dead"}, # s0 চিরকাল সেল্ফ-লুপ করে, dead/s1 কখনো পৌঁছানো যায় না
)
print("ইচ্ছাকৃতভাবে-অপ্রাপ্য-accept DFA is_empty:", is_empty(unreachable_accept), " (প্রত্যাশিত True)")
# --- are_equivalent টেস্ট ---
minimal_even_ones = DFA(
states=["q0", "q1"], alphabet={"0", "1"},
transitions={("q0","0"):"q0", ("q0","1"):"q1", ("q1","0"):"q1", ("q1","1"):"q0"},
start="q0", accept_states={"q0"},
)
def flip(x):
return "B" if x == "A" else "A"
# একই ভাষার একটি রিডানডেন্ট ৪-স্টেট DFA -- একটি অপ্রয়োজনীয় "extra" বিট ট্র্যাক করে যা accept-এ প্রভাব ফেলে না
redundant_states = [(p, e) for p in (0, 1) for e in ("A", "B")]
redundant_trans = {}
for (p, e) in redundant_states:
for c in ("0", "1"):
redundant_trans[((p, e), c)] = (p ^ (1 if c == "1" else 0), flip(e))
redundant_even_ones = DFA(
states=redundant_states, alphabet={"0", "1"}, transitions=redundant_trans,
start=(0, "A"), accept_states=[(0, "A"), (0, "B")],
)
odd_ones = DFA(
states=["r0", "r1"], alphabet={"0", "1"},
transitions={("r0","0"):"r0", ("r0","1"):"r1", ("r1","0"):"r1", ("r1","1"):"r0"},
start="r0", accept_states={"r1"},
)
# প্রথমে সরাসরি নিশ্চিত করা -- রিডানডেন্ট DFA সত্যিই একই ভাষা recognize করে কি না
test_strings = ["", "0", "1", "00", "01", "10", "11", "000", "111", "0101010", "110011"]
for s in test_strings:
assert minimal_even_ones.accepts(s) == redundant_even_ones.accepts(s)
print()
print("রিডানডেন্ট ৪-স্টেট DFA সব টেস্ট স্ট্রিং-এ মিনিমাল ২-স্টেট DFA-র সাথে একমত: OK")
print("minimal(2-state) vs redundant(4-state), একই ভাষা, are_equivalent:",
are_equivalent(minimal_even_ones, redundant_even_ones), " (প্রত্যাশিত True)")
print("minimal(even-1s) vs odd-1s DFA, ভিন্ন ভাষা, are_equivalent:",
are_equivalent(minimal_even_ones, odd_ones), " (প্রত্যাশিত False)")
BFS/DFS রিচেবিলিটি — accept state-এ পৌঁছানো যায় কি না।
রিচেবল সাইকেল ডিটেকশন — অসীম স্ট্রিং জেনারেট করা সম্ভব কি না।
সিমেট্রিক ডিফারেন্স + এমপ্টিনেস — ইতিমধ্যে প্রমাণিত টেকনিকের কম্পোজিশন।
L15-এর মাইহিল-নেরোড থিওরেম ইউনিক মিনিমাল DFA-র নিশ্চয়তা দেয়, আর DFA-র ফাইনাইট, সম্পূর্ণভাবে-পরিদর্শনযোগ্য কাঠামো এমপ্টিনেস, ফাইনাইটনেস ও ইকুইভ্যালেন্সকে অ্যালগরিদমিকভাবে ডিসাইডেবল করে তোলে — সবগুলোই ইতিমধ্যে M2-M3-এ প্রতিষ্ঠিত টেকনিক (গ্রাফ ট্র্যাভার্সাল, ক্লোজার কনস্ট্রাকশন) দিয়ে সমাধানযোগ্য। M9-এ দেখা যাবে — টুরিং মেশিনের অসীম টেপ এই "ফাইনাইট, সম্পূর্ণভাবে-পরিদর্শনযোগ্য" গ্যারান্টি ভেঙে দেয়, ঠিক এই একই প্রশ্নগুলোকে আনডিসাইডেবল করে তোলে — M3-এর "সহজ" জগৎ আর M9-এর "কঠিন" জগতের মধ্যে পার্থক্যের মূল উৎস এটিই।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "মিনিমাল DFA ইউনিক" — এই দাবিটি ঠিক কী অর্থে "ইউনিক"? দুটো মিনিমাল DFA কি একদম একই রকম দেখতে হতে হবে?
"ইউনিক আপ টু স্টেট রিনেমিং (isomorphism)" — অর্থাৎ দুটো মিনিমাল DFA-র স্টেটের নাম ভিন্ন হতে পারে (একটিতে "q0, q1" আরেকটিতে "s_even, s_odd"), কিন্তু তাদের কাঠামো (কয়টা স্টেট, কোন স্টেট থেকে কোন স্টেটে কোন সিম্বলে যায়, কোনগুলো accepting) হুবহু একই রকম হতে হবে, শুধু নাম বদলে। এটি সরাসরি L15-এর ফলাফল — যেহেতু ইকুইভ্যালেন্স ক্লাসগুলো ভাষা $L$ দ্বারা সম্পূর্ণভাবে নির্ধারিত (কোনো নির্দিষ্ট DFA কনস্ট্রাকশনের উপর নির্ভর করে না), যেকোনো সঠিক মিনিমাইজেশন অ্যালগরিদম একই ক্লাস-স্ট্রাকচারে পৌঁছাবে, শুধু স্টেটের নাম ভিন্ন হতে পারে।
প্র ০২ এমপ্টিনেস চেক করার সময় accept states নিজেই খালি সেট হলে (কোনো accept state-ই নেই) কী হয়?
is_empty এখনও সঠিকভাবে True রিটার্ন করবে — BFS লুপ প্রতিটি রিচেবল স্টেট
পরীক্ষা করবে "এটি কি accept_states-এ আছে?", আর যেহেতু accept_states খালি, কোনো স্টেটই
এই শর্ত মেলাবে না, লুপ শেষ পর্যন্ত চলে True রিটার্ন করবে। এটি সঠিক আচরণ — যদি কোনো
accepting state-ই সংজ্ঞায়িত না থাকে, DFA-টি অবশ্যই কোনো স্ট্রিং accept করতে পারে না, তাই $L(M)$
অবশ্যই খালি।
প্র ০৩ সিমেট্রিক ডিফারেন্স DFA-র accept রুল ($XOR$) কেন ইকুইভ্যালেন্স-চেকের জন্য সঠিক পছন্দ?
$L_1 \triangle L_2$ (সিমেট্রিক ডিফারেন্স) ঠিক সেই স্ট্রিংগুলো ধারণ করে যেগুলো দুটো ভাষার মধ্যে ভিন্ন সিদ্ধান্তে পড়ে — $L_1$-এ আছে কিন্তু $L_2$-এ নেই, অথবা উল্টো। যদি এই সেট খালি হয়, তার মানে এমন কোনো স্ট্রিং নেই যেখানে দুটো ভাষা দ্বিমত করে — অর্থাৎ তারা প্রতিটি স্ট্রিং-এই একমত, তাই $L_1 = L_2$। XOR রুল ($q_1 \in F_1$ ঠিক তখনই $q_2 \notin F_2$ বা উল্টো) সঠিকভাবে এই "দ্বিমত" শর্তটিই এনকোড করে — L12-এর প্রোডাক্ট কনস্ট্রাকশনের একটি সরাসরি ভ্যারিয়েশন, শুধু accept রুল বদলে।
অনুশীলন
-
চিন্তা করুন: "ফাইনাইটনেস" ডিসিশন প্রপার্টির জন্য শুধু "একটি সাইকেল আছে কি না" চেক করলেই
কি যথেষ্ট, নাকি "রিচেবল এবং accept-এ পৌঁছাতে-পারা" সাইকেল দরকার? পার্থক্যটা একটি উদাহরণ দিয়ে ব্যাখ্যা করুন।
শুধু "একটি সাইকেল আছে" যথেষ্ট নয় — সাইকেলটি অবশ্যই (ক) start state থেকে রিচেবল হতে হবে, এবং (খ) সেই সাইকেল থেকে কোনো accept state-এ পৌঁছানো সম্ভব হতে হবে। উদাহরণ: একটি DFA-তে একটি সাইকেল থাকতে পারে যা সম্পূর্ণভাবে non-accepting স্টেটের মধ্যে আটকে আছে এবং কখনো কোনো accept state-এ যায় না — তাহলে সেই সাইকেল "লুপ করে" শুধু আরও আরও reject স্ট্রিং বানাচ্ছে, $L(M)$-কে অসীম করছে না। তাই সঠিক অ্যালগরিদম শুধু সাইকেল ডিটেকশন নয়, বরং "রিচেবল সাইকেল যা থেকে একটি accept state-এ পৌঁছানো যায়" — দুটো শর্তই লাগবে।
-
পরীক্ষা করুন: উপরের কোড সেলে
odd_ones-এর accept_states-কে{"r0", "r1"}(উভয় স্টেট accepting) করে দিন — এখন এই নতুন DFA $\Sigma^*$ (সব স্ট্রিং) accept করে। এটি কিminimal_even_ones-এর সাথে equivalent হবে?না, equivalent হবে না — $\Sigma^*$ (সব স্ট্রিং accept) আর "even number of 1s" (শুধু নির্দিষ্ট কিছু স্ট্রিং accept, যেমন "1" reject হওয়া উচিত) ভিন্ন ভাষা।
are_equivalentফাংশন এটি সঠিকভাবে ধরে ফেলবে — সিমেট্রিক ডিফারেন্স DFA-তে এমন স্টেট থাকবে যেখানে একটি DFA accept করছে আর অন্যটি করছে না (যেমন "1" স্ট্রিং-এ নতুন DFA accept করবে কিন্তুminimal_even_onesকরবে না), তাইis_emptyসেই সিমেট্রিক-ডিফারেন্স DFA-তেFalseরিটার্ন করবে — যথাযথভাবে "not equivalent" শনাক্ত করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M3 শেষ — পরবর্তী মডিউল (M4) কনটেক্সট-ফ্রি গ্রামারের ফরমাল ডেফিনিশন ও ডেরিভেশন দিয়ে শুরু হবে।
- L15 · মাইহিল-নেরোড থিওরেম পূর্ববর্তী ভিত্তি এই পাঠের ইউনিক-মিনিমাল-DFA দাবির সম্পূর্ণ গাণিতিক ভিত্তি সেই পাঠেই প্রতিষ্ঠিত হয়েছে।
- Programming Languages & Compiler Design কোর্স সঙ্গী কোর্স প্র্যাকটিক্যাল পার্টিশন-রিফাইনমেন্ট মিনিমাইজেশন অ্যালগরিদম (লেক্সার দ্রুততর করতে) সেই কোর্সের M4/L19-এ।
- সব 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 — সব এক জায়গায়।