Foundations to FrontierPart IV: Algorithms and systemsBezhig

C16: Automata, decidability, reductions, and complexity classes

5 exercises, each with a complete solution. 1 figure. 1 code listing.

On this page
  1. A. Prerequisites and observable goals
  2. B. The central question: which problems can a computational model solve?
  3. C. Finite memory has limits
  4. D. Deciding differs from recognizing
  5. E. A reduction transfers a solution method in one direction
  6. F. Equivalence by exploring a product graph
  7. G. Independent practice
  8. H. Complete solutions: C16
  9. I. Retention and readiness

A. Prerequisites and observable goals#

Before

C04 logic and proof; C05 graphs and induction; C07 algorithm analysis; C15 grammars.

After

Construct small finite-state recognizers, explain a halting contradiction, use reduction direction correctly, and distinguish P, NP, NP-hardness, and NP-completeness.

Level: Degree core introduction; formal theory requires further proof practice beyond this module.

B. The central question: which problems can a computational model solve?#

A language is a set of finite strings over an alphabet. A recognizer decides or attempts to recognize membership under a specified model. A deterministic finite automaton (DFA) has finitely many states, a start state, accepting states, and a transition for each state and alphabet symbol. Its state summarizes all information about the prefix that matters for future acceptance.

For strings over {a}\{a\} containing an even number of symbols, two states suffice: even and odd. Start in even, toggle on every aa, and accept only even. The empty string is accepted because zero is even. An invariant proves correctness: after reading kk symbols, the state is even exactly when kk is even. Initialization holds at k=0k=0, and each transition toggles parity.

The double circle means accepting, not “more important.” This finite machine remembers parity, not the exact count. A DFA cannot remember an unbounded integer in a fixed finite set of states.

C. Finite memory has limits#

Regular languages are exactly those recognized by finite automata and, under the classical definition, regular expressions. Some programming-library “regex” features, such as backreferences, go beyond classical regular expressions. Do not transfer a theorem about finite automata to every library feature sharing a name.

Consider L={anbn:n≥0}L=\{a^n b^n:n\ge0\}. Suppose a DFA with mm states recognized it. Among the m+1m+1 prefixes a0,a1,…,ama^0,a^1,\ldots,a^m, two distinct lengths i<ji<j reach the same state by the pigeonhole principle. Appending bib^i makes the first string belong to LL and the second not belong. Starting from the same state and reading the same suffix cannot produce different acceptance decisions, a contradiction. Thus no DFA recognizes this language.

A context-free grammar can generate it using S→aSb∣ϵS\to aSb\mid\epsilon. A pushdown automaton adds a stack and can match unbounded nested structure. Balanced parentheses are a similar motivating example. “More expressive” does not mean “always better”: finite-state models can be easier to verify, serialize, and exhaustively explore for protocol control states.

D. Deciding differs from recognizing#

A decider halts on every input with a yes or no answer. A recognizer for a language must accept strings in the language, but may run forever on strings outside it. An algorithm that eventually finds a solution when one exists is not necessarily a decision procedure for whether a solution exists.

The halting problem asks whether an arbitrary program halts on an arbitrary input. Assume a total program H(p,x)H(p,x) answers this correctly. Construct D(p)D(p) to loop forever when H(p,p)H(p,p) says “halts,” and halt when it says “does not halt.” Ask what D(D)D(D) does. If H(D,D)H(D,D) says halts, construction makes it loop; if it says does not halt, construction makes it halt. Both contradict correctness. Therefore no such total general halting decider exists for a computationally universal model.

This does not imply that no individual program’s termination can be proved. C02’s decreasing interval and C05’s induction prove termination for specific algorithms. Undecidability concerns a single algorithm that answers every instance of the general problem. It is not an excuse to stop analyzing a bounded program.

E. A reduction transfers a solution method in one direction#

A many-one reduction from decision problem AA to BB transforms an instance xx into f(x)f(x) such that x∈Ax\in A exactly when f(x)∈Bf(x)\in B. Then a solver for BB, combined with the transformation, solves AA. To show BB is at least as hard as a known hard AA, reduce AA to BB, not the reverse. The transformation’s computational cost matters when discussing complexity classes.

P is the class of decision problems solvable in polynomial time by a deterministic algorithm under a standard discrete computational model. NP is the class with polynomial-size certificates verifiable in polynomial time, equivalently nondeterministic polynomial-time decidability. NP does not stand for “not polynomial.” Every problem in P is in NP: a verifier can simply decide the instance without needing a certificate.

A problem is NP-hard if every problem in NP reduces to it by the specified polynomial-time reduction. It is NP-complete if it is both NP-hard and in NP. NP-hard problems need not themselves be decision problems in NP; optimization problems are often called NP-hard through associated reductions. Boolean satisfiability is a foundational NP-complete decision problem. The P-versus-NP question remains unresolved in the authoritative problem statement checked for this edition; this book neither assumes P=NPP=NP nor assumes a proof of P≠NPP\ne NP. [PNP]

An exponential-looking implementation does not prove a problem requires exponential time. Conversely, a polynomial bound can still be impractical because of its degree, constants, or input representation. Distinguish worst-case classification, approximation guarantees, special-case structure, and measured performance.

F. Equivalence by exploring a product graph#

Two DFAs over the same alphabet are equivalent if they accept exactly the same strings. Form a product state (u,v)(u,v) recording both current states. Start at their start-state pair. A pair is distinguishing if exactly one component is accepting. Each symbol advances both machines. Breadth-first search finds a shortest word leading to a distinguishing pair, or exhausts all reachable pairs and proves equivalence under the supplied total transition tables.

There are at most ∣QA∣∣QB∣|Q_A||Q_B| product states. A graph implementation with parent links needs O(∣QA∣∣QB∣∣Σ∣)O(|Q_A||Q_B||\Sigma|) transition work plus reconstruction. Our readable lab stores a word with each queued state, so copying strings can add overhead proportional to witness length; do not silently claim the optimal memory bound for this convenient representation. The algorithm’s finiteness, not an arbitrary maximum string length, establishes that exhaustion is meaningful.

G. Independent practice#

F foundation C standard course work T transfer S optional stretch L runnable lab or project. Attempt the complete set before opening its solutions.

C16.01FSolution

For the even-aa DFA, determine acceptance of the empty string, a, and aaaa. State its prefix invariant.

C16.02CSolution

Explain why seeing no counterexample among all strings of length at most 100 does not by itself prove two arbitrary recognizers equivalent. Contrast this with exhaustive reachability of a finite DFA product.

C16.03CSolution

A student says: “NP means no polynomial algorithm exists; an NP-hard problem must be in NP.” Correct both statements and explain what must be shown to call a decision problem NP-complete.

C16.04TSolution

Suppose a total procedure could decide whether an arbitrary program on a given input ever prints the symbol X. Show how it would solve the halting problem by constructing a wrapper program. State the contradiction and the reduction direction.

C16.05LabSolution

Implement starters/automata_lab.py. Evaluate words in a DFA and use product BFS to return a shortest distinguishing word, or None for equivalent machines. Assume finite, valid, total transition tables over the declared alphabet; reject words using symbols outside that alphabet. Test a witness and verify it is accepted by exactly one machine.

Optional small hints

For C16.04, make the wrapper print only after the simulated program finishes. For C16.05, the empty string can itself be a distinguishing word; it is different from None.

H. Complete solutions: C16#

Compare the first step where your work differs. A corrected answer is not yet an independent success.

C16.01Show solution

Parity is the stored sufficient information

The empty string is accepted, a is rejected, and aaaa is accepted. After kk symbols, the machine is in the even state exactly when kk is even. It starts correctly at zero and toggles after each additional aa, so induction establishes the invariant. Acceptance tests the invariant at end-of-input, not at some earlier accepting prefix.

Back to C16.01
C16.02Show solution

A bounded test and an exhaustive state argument are different

A recognizer might distinguish only a string of length 101 or much longer. A finite set of tested strings does not cover arbitrary program behavior. A DFA product has a known finite state space; once every reachable pair has been explored, every possible future string follows transitions among those explored states. If no reachable pair differs in acceptance, no distinguishing string exists. The proof uses the model’s state bound and reachability closure, not the comforting size of a test count.

Back to C16.02
C16.03Show solution

Separate verification from hardness

NP describes polynomial-time verification of a polynomial-size certificate, not a proved absence of polynomial-time solution algorithms. P is contained in NP. NP-hardness means all NP problems reduce to the target under the chosen polynomial reduction; it does not require that the target belong to NP. To show NP-completeness, prove both membership in NP and NP-hardness. Usually membership is shown by specifying a certificate and verifier, while hardness uses a reduction from an already established NP-complete problem. The transformation must preserve yes/no answers and run in polynomial time.

Back to C16.03
C16.04Show solution

Reduce halting to eventual printing

Given program pp and input xx, construct a wrapper that simulates p(x)p(x) and, only after that simulation halts, prints X. Suppress or redirect the simulated program’s own output so it cannot independently print the distinguished symbol. The wrapper prints X exactly when p(x)p(x) halts. A total eventual-printing decider would therefore decide halting, contradicting the diagonal argument. The reduction is from halting to eventual printing, showing the latter cannot have the assumed total decider. This does not prevent deciding printing behavior for a restricted finite-state language.

Back to C16.04
C16.05Show solution

Complete finite-state comparison

BFS checks the start pair before reading any symbol, so it correctly returns the empty string when start-state acceptance differs. A visited set prevents cycling forever. Transition tables are trusted inputs under the lab specification; a production API accepting arbitrary machine descriptions would first validate states and totality. The reference compares an even-length machine with an all-strings machine and returns 'a'.

code/solutions/automata_lab.pyPython
"""C16.05: deterministic finite-state machines with total transitions."""
from collections import deque

def accepts(start, finals: set, delta: dict, alphabet: set, word: str) -> bool:
    state = start
    for symbol in word:
        if symbol not in alphabet:
            raise ValueError("symbol outside alphabet")
        state = delta[state, symbol]
    return state in finals

def distinguishing_word(a, b, alphabet: set[str]) -> str | None:
    """Each machine is (start, final_states, transition_dictionary)."""
    start = (a[0], b[0])
    todo = deque([(start, "")])
    seen = {start}
    while todo:
        (u, v), word = todo.popleft()
        if (u in a[1]) != (v in b[1]):
            return word
        for c in sorted(alphabet):
            pair = (a[2][u, c], b[2][v, c])
            if pair not in seen:
                seen.add(pair)
                todo.append((pair, word + c))
    return None

if __name__ == "__main__":
    even = (0, {0}, {(0, "a"): 1, (1, "a"): 0})
    all_words = (0, {0}, {(0, "a"): 0})
    print(repr(distinguishing_word(even, all_words, {"a"})))
Back to C16.05

I. Retention and readiness#

Retrieve the pigeonhole principle, BFS, and proof by contradiction. Advance when you can explain why a reduction’s arrow points toward the hypothetical solver and why finite-state equivalence is decidable while arbitrary program equivalence is much harder. Continue with a formal automata and computability course, then complexity and reductions with full proofs.