THEORY OF COMPUTATION / 2. NFA & REGULAR EXPRESSIONS
NFA & Regular Expressions
Nondeterminism, epsilon transitions, and the regex you use every day
EXPLANATION
NFA (Nondeterministic Finite Automaton) — same as DFA but with two key differences: ① For a given (state, symbol), there can be ZERO, ONE, or MANY possible next states ② Epsilon (ε) transitions: the NFA can change state without reading any input An NFA accepts a string if there EXISTS at least one path through the transitions that leads to an accept state. It's like the machine "guesses" the right path. NFAs vs DFAs — power and equivalence: - NFAs are NOT more powerful than DFAs in terms of what languages they recognize - Every NFA can be converted to an equivalent DFA (subset construction algorithm) - But NFAs can be EXPONENTIALLY more concise — an NFA with n states may need a DFA with 2ⁿ states - NFAs are easier to BUILD (especially from regex), DFAs are easier to RUN (deterministic) Subset Construction (NFA → DFA): - Each DFA state = a SET of NFA states (the set of states the NFA could possibly be in) - Start DFA state = ε-closure(q0) (all states reachable from start via ε transitions) - DFA has at most 2^|Q_NFA| states ε-closure: the set of all states reachable from a given state via ε transitions alone (including the state itself). Regular Expressions → NFA (Thompson's Construction): Every regex can be mechanically converted to an NFA: - Single symbol a: two states, one transition - Concatenation r·s: connect NFA(r) end to NFA(s) start via ε - Union r|s: new start with ε to both NFA(r) and NFA(s) starts, both ends ε to new accept - Kleene star r*: new start/accept, ε loop back, ε to skip Then convert NFA → DFA (subset construction) → minimize DFA. This is EXACTLY what Python's re module does when you compile a regex! Regular Expression operators: - a — literal character a - . — any single character - * — zero or more (Kleene star) - + — one or more (= rr*) - ? — zero or one - | — alternation (union) - [] — character class - ^ — start anchor / negation in class - $ — end anchor - () — grouping Languages defined by regex = Regular Languages. They are EXACTLY the class of languages DFAs/NFAs recognize. This is the Kleene theorem.
DIAGRAM
NFA: strings ending in "ab" over {a,b}
(nondeterministic — can "guess" where pattern starts)
→q0 ──a──→ q1 ──b──→ q2 (accept)
↑
a,b (loop: stay in q0 for any char)
q0 has TWO transitions on 'a': stay in q0, OR go to q1
This is the nondeterminism!
NFA accepts "xab" for any x because:
Path 1: q0→q0→q0→q1→q2 ✓ (last two chars are ab)
SUBSET CONSTRUCTION (NFA → DFA):
NFA states: {q0, q1, q2}
DFA state │ a │ b
─────────────┼─────────────┼──────────────
→{q0} │ {q0,q1} │ {q0}
{q0,q1} │ {q0,q1} │ {q0,q2}*
*{q0,q2} │ {q0,q1} │ {q0}
*{q0,q1,q2} │ {q0,q1} │ {q0,q2}*
(* = accept state because contains q2)
THOMPSON'S CONSTRUCTION for (a|b)*ab:
(a|b)*: ε→[a-NFA]→ε ↺
ε→[b-NFA]→ε
Then concatenate with [a]→[b]CODE