English
Computation is something everyone does, something machines do, and something that had no precise definition until 1936. Before that year, mathematicians spoke confidently of “effective procedures” and “mechanical methods,” but what they meant was informal — roughly, a procedure that requires no ingenuity, only patience. The theory of computation is the subject that made this informal notion precise, discovered what that precision implied, and found results that nobody had anticipated.
The subject is organized into three layers, each asking a question the previous layer makes possible. The first is the machine model: what is a computation? This layer produces formal models — Turing machines, lambda calculus, register machines — and the central fact that they all define the same class of computable functions. The second layer is computability: what can be computed, regardless of resource cost? This layer produces the impossibility results — the halting problem, Rice’s theorem, the undecidability of arithmetic — that establish what no algorithm can do. The third layer is complexity: what can be computed within bounded resources? This layer produces the complexity classes — P, NP, PSPACE — and the relationships among them that organize contemporary algorithm design.
These three layers are not independent topics assembled for convenience. Each layer’s questions are expressible only because the previous layer exists. “Can this function be computed?” requires a definition of computation. “Is this problem in P?” requires a definition of computability first. The full subject is the integration of all three, and the integration is what gives it intellectual coherence.
The theory of computation sits at the conceptual center of Chapter 3. Complexity theory (§3.3) extends the third layer at depth. Logic (§3.1) is its closest neighbor: undecidability results and logical incompleteness are the same phenomenon in different languages. Algorithms (§2.6) are what practitioners use when the answer to “is this tractable?” is yes. Cryptography (§4.5) is built on complexity-theoretic hardness assumptions. Understanding why those assumptions are believed — rather than just that they are — requires this subject.
Prerequisites: Discrete mathematics and proof (§2.2), particularly induction and diagonalization arguments. Logic (§3.1) enriches the material but is not strictly required to begin.
What Happened When Mathematics Tried to Mechanize Itself
The story begins with a failure that proved more valuable than any success could have.
In 1900, David Hilbert presented to the International Congress of Mathematicians a list of twenty-three open problems that he believed would define the next century of mathematics. Two of them concern the theory of computation directly. The second asked for a proof that the axioms of arithmetic are consistent — that they do not lead to contradictions. The tenth asked for an algorithm to determine whether any given polynomial equation with integer coefficients has integer solutions. Both problems assumed that mathematics could, in principle, answer its own foundational questions by mechanical means. Both assumptions turned out to be wrong.
The second problem fell to Kurt Gödel in 1931. His incompleteness theorems showed that any consistent formal system strong enough to express arithmetic contains statements that are true but unprovable within the system, and cannot prove its own consistency. The first theorem demolished Hilbert’s hope that formal proof would eventually capture all mathematical truth. The second demolished his hope that consistency could be established from within. The proofs used a technique of self-reference — encoding statements about the formal system inside the system itself — that would become the central method of computability theory.
The tenth problem fell to Yuri Matiyasevich in 1970, completing work begun by Martin Davis, Hilary Putnam, and Julia Robinson over the preceding two decades. But the question that mattered for computer science was the one that fell in 1936: Hilbert’s Entscheidungsproblem, the decision problem. Was there a mechanical procedure that, given any mathematical statement, would determine in finite time whether the statement was provable? Hilbert believed yes. In 1936, Alonzo Church and Alan Turing — working independently, on different sides of the Atlantic, using different formalisms — proved no.
Church’s approach was the lambda calculus, a notation for defining functions through abstraction and application that he had been developing since 1932. Turing’s approach was the machine that now bears his name: an infinite tape, a read-write head, and a finite set of states governing what to do next based on what the head reads. Turing’s paper “On Computable Numbers, with an Application to the Entscheidungsproblem” was the founding document of computer science. It did three things simultaneously: defined a formal model of computation, proved the existence of functions that the model could not compute, and used that proof to show that no algorithm could solve the Entscheidungsproblem. The technique was diagonalization — constructing, for any purported decision procedure, an input on which the procedure fails. Gödel had used the same technique in 1931; Cantor had used it in 1891 to show that the real numbers were uncountable. Diagonalization was already a known proof method, but Turing’s application of it to computation gave it a new scope.
The immediate consequence was the Church-Turing thesis: all reasonable formalizations of effective computation define the same class of functions. Lambda calculus, Turing machines, register machines, recursive functions — each had been developed independently, and each turned out to compute the same functions. This convergence was not a theorem but an empirical fact about the space of formal models, and it has been confirmed every time a new model has been proposed: cellular automata, DNA computation, analog computation — every model computes either less than or exactly as much as a Turing machine, and no serious candidate has computed more. The thesis is still a thesis, not a proof, because it relates the formal notion of Turing computability to the informal notion of effective computation. But it has held for nearly ninety years, and the community has come to regard it as a deep truth about the nature of computation.
The most important single result in computability theory — the one that reveals what non-computability actually means in practice — is the undecidability of the halting problem. The halting problem asks: given an arbitrary program and an arbitrary input, does the program eventually halt? Turing showed in his 1936 paper that no algorithm can answer this question for all programs and inputs. The proof is diagonalization: assume such an algorithm H exists; construct a program D that runs H on itself and does the opposite of what H predicts; ask what H says about D when D is given D as input — either answer leads to a contradiction.
The halting problem’s undecidability is not a curiosity but a structural fact about computation with wide consequences. Rice’s theorem, proved by Henry Gordon Rice in 1953, generalizes it: any non-trivial semantic property of programs — a property that holds for some programs and not others and depends on their behavior rather than their syntax — is undecidable. This means that most questions one wants to ask about programs in general are unanswerable by any algorithm. Does this program terminate on all inputs? Does it ever output a specific string? Does it compute the same function as another program? All undecidable, all provably so. The practical consequence is that no general-purpose static analysis tool can give perfectly accurate answers to these questions; every such tool either misses some true bugs (false negatives) or reports some false alarms (false positives), and the tradeoff is unavoidable in principle, not just difficult in practice.
The complexity layer arrived formally with Stephen Cook’s 1971 paper “The Complexity of Theorem Proving Procedures.” Cook had been studying the theoretical foundations of automated reasoning, and he identified a question that had been lurking: among the problems that are computable, which ones can be computed efficiently — in time polynomial in the input size? The answer would determine what was practically feasible rather than merely theoretically possible.
Cook proved that Boolean satisfiability — deciding whether a propositional formula has a satisfying truth assignment — had a remarkable property: every problem in NP could be reduced to it in polynomial time. NP is the class of problems whose solutions can be verified efficiently; P is the class that can be solved efficiently. If SAT had a polynomial-time algorithm, then P = NP and every problem with efficiently verifiable solutions would also have efficiently findable ones. Cook’s paper posed P vs. NP explicitly and showed that SAT was the hardest problem in NP.
Richard Karp’s 1972 paper extended this immediately by showing that twenty-one other natural combinatorial problems — vertex cover, independent set, Hamiltonian cycle, graph coloring, bin packing, traveling salesman — were each reducible to SAT and thus each NP-complete. These problems had been studied independently; nobody had suspected they were related. Karp’s paper revealed that they were the same problem in different formulations, and that solving any one of them efficiently would solve all of them. The discovery was intellectually startling: the apparent diversity of hard problems concealed a deep structural unity.
P vs. NP has remained open since 1971, and the failure to resolve it in either direction for over fifty years is itself informative. Nearly every researcher believes P ≠ NP — that efficient algorithms do not exist for NP-complete problems — but the belief is based on intuition and the failure of enormous effort, not on proof. The Clay Mathematics Institute designated it one of seven Millennium Prize Problems in 2000. The difficulty of the problem reflects something genuine: existing mathematical techniques are not sufficient to prove lower bounds on computational resources for explicit problems, and new techniques would be needed. The search for such techniques is a central activity of contemporary complexity theory.
From the foundational decade of the 1970s, complexity theory developed along several fronts that are now standard. The polynomial hierarchy — a hierarchy of complexity classes above NP defined by alternating quantifiers — gave a finer classification of problems harder than NP but not as hard as PSPACE. Randomized complexity classes (BPP, RP, coRP) captured problems solvable efficiently when randomness is allowed. Interactive proof systems (IP = PSPACE, shown by Shamir in 1992) revealed that probabilistic verification with interaction is surprisingly powerful. The PCP theorem (Arora, Lund, Motwani, Sudan, Szegedy, 1992), showing that NP proofs can be verified by reading a constant number of bits, had immediate consequences for inapproximability: many NP-hard problems are hard not just to solve exactly but to approximate within any constant factor.
The contemporary research frontier has developed in directions the founders of the field could not have anticipated. Fine-grained complexity asks not whether problems are polynomial-time but how their polynomial scaling compares: is the quadratic lower bound for edit distance conditional on SETH (the Strong Exponential Time Hypothesis) improvable? Parameterized complexity identifies structural parameters that, when bounded, make otherwise intractable problems tractable — the theory of FPT (fixed-parameter tractable) algorithms. Average-case complexity studies when problems are hard for typical inputs rather than worst-case inputs — motivated by cryptography, which needs problems that are hard not just in worst case but for inputs drawn from natural distributions. Quantum complexity, with classes like BQP and QMA, extends the framework to quantum computation, where Shor’s algorithm for factoring in polynomial time shows that some classically hard problems are quantum-tractable. The field has never been static; its foundational results are permanent, but the questions it pursues continue to evolve.
Models, Undecidability, and Complexity
The Machine Models and What They Establish
A Turing machine is a finite-state automaton with read-write access to an infinite tape. The machine starts in an initial state with the input written on the tape, reads symbols one at a time, writes new symbols, moves the head left or right, and transitions between states according to a transition function. This model, deliberately minimal, captures the essential features of computation: finite control (the program), unbounded memory (the tape), and sequential operation.
The minimalism is the point. A Turing machine is simple enough to reason about mathematically, and powerful enough — as the Church-Turing thesis asserts — to compute everything any other reasonable model can compute. The equivalence has concrete consequences. When we prove that the halting problem is undecidable for Turing machines, we prove it for all equivalent models simultaneously: Java programs, Python scripts, register machines, cellular automata. Undecidability is not an artifact of the Turing machine model; it is a property of computation itself.
The universal Turing machine — a specific Turing machine that takes as input the description of any other Turing machine plus an input, and simulates the described machine on that input — establishes that computation is self-referential. A machine can read another machine’s description and execute it; computation can be about computation. This is the foundation of all universal programming: a general-purpose computer is a physical implementation of a universal Turing machine, and the ability to write programs that process other programs (compilers, interpreters, debuggers, theorem provers) rests on universality. Without the universal Turing machine theorem, none of these would be possible in principle.
Finite automata and pushdown automata are weaker models restricted to limited memory. A finite automaton has no tape at all — only the finite-state controller — and recognizes exactly the regular languages, those describable by regular expressions. A pushdown automaton adds a stack, and recognizes exactly the context-free languages, those describable by context-free grammars. The Chomsky hierarchy organizes these models: regular languages ⊂ context-free languages ⊂ recursive languages ⊂ recursively enumerable languages. Each class corresponds to a model of computation, and each inclusion is strict — there are context-free languages that are not regular, recursive languages that are not context-free, and so on.
These restricted models matter beyond their role in the Chomsky hierarchy. Regular expressions appear in lexical analysis (the first phase of compilation), in text search (grep, sed), and in network packet filtering. Context-free grammars describe the syntax of programming languages; parsing — determining whether a string is a valid program and extracting its structure — is context-free language recognition. Understanding these connections requires the formal theory; using the tools in practice does not, but understanding them does.
The Technique of Diagonalization
Diagonalization is the proof technique that underlies most undecidability results, and it is worth understanding as a technique in its own right rather than as a specific proof.
The idea traces to Cantor’s 1891 proof that the real numbers are uncountable: assume you have a list of all real numbers between 0 and 1; construct a new number that differs from the n-th number in the list in the n-th decimal place; the new number is not in the list, contradicting the assumption. The constructed number is defined by diagonal means — by referring to the list and differing from it systematically.
Gödel’s encoding technique, applied to formal systems, allows statements about provability to be encoded as arithmetic statements within the system. This makes self-reference possible in arithmetic: one can construct a statement that says, in effect, “I am not provable.” If the statement is provable, the system is inconsistent; if not, the statement is true but unprovable. The incompleteness theorem follows.
Turing’s proof that the halting problem is undecidable uses the same pattern. Assume algorithm H decides halting; construct program D that asks H whether D(D) halts, then does the opposite; run D on its own description; contradiction. The program D is defined by diagonal means — by referring to H and systematically reversing its output.
The pattern generalizes: whenever you want to prove that no algorithm can decide a property P, assume an algorithm A for P, use A to construct a new object by diagonal means that violates the assumption A is based on, and derive a contradiction. The construction varies across different undecidability proofs, but the structure is the same. Once recognized, this structure makes undecidability proofs readable at a level where the details are secondary to the pattern.
Complexity Classes and the P vs. NP Question
Time complexity classifies problems by how the number of steps needed to solve them grows with input size. P is the class of problems solvable in polynomial time by a deterministic machine — intuitively, the class of tractable problems. NP is the class of problems whose solutions can be verified in polynomial time: given a solution, checking it takes only polynomial time, even if finding the solution might take exponential time. The question whether P = NP asks whether efficient verification implies efficient solution.
The structural fact about NP is NP-completeness: there exist problems in NP to which every problem in NP reduces in polynomial time. If any NP-complete problem is in P, then P = NP. If any NP-complete problem is not in P, then P ≠ NP. The major combinatorial problems of algorithm design — satisfiability, graph coloring, traveling salesman, integer programming — are NP-complete. The NP-hardness of these problems is the reason that exact algorithms for them run in exponential time in the worst case and why approximation and heuristic methods are the practical tools.
The proof that a problem is NP-complete has two parts: showing it is in NP (easy: exhibit a polynomial-time verifier), and showing every NP problem reduces to it in polynomial time (hard: requires a reduction from a known NP-complete problem). The first NP-complete problem was SAT, proved by Cook — he had no prior NP-complete problems to reduce from, so he reduced directly from the definition of NP. Every subsequent NP-completeness proof reduces from SAT or from a problem already known to be NP-complete.
Beyond NP, the complexity zoo includes PSPACE (problems solvable with polynomial space, regardless of time), EXPTIME (solvable in exponential time), and many other classes. The polynomial hierarchy PH generalizes NP: Σ₁ᵖ = NP, Π₁ᵖ = co-NP, Σ₂ᵖ = NP^NP, and so on. Randomized classes BPP (bounded-error probabilistic polynomial time) and RP capture problems solvable efficiently with randomness. Interactive proof classes IP and MIP extend computation with interaction between a prover and a verifier; IP = PSPACE and MIP* = RE (recursively enumerable, all of computability) are among the deepest results in complexity theory.
What Studying This Changes
The theory of computation produces intellectual effects that no other CS subject provides in the same form.
The most direct is the instinct for impossibility. After studying undecidability and NP-completeness, a practitioner develops the habit of asking, when a problem resists all approaches, whether it resists for structural reasons. The ability to recognize the shape of an undecidable problem — reduction from the halting problem, Rice’s theorem, a self-referential structure — or the shape of an NP-complete problem — a search over exponentially many configurations with no shortcut — saves enormous effort. The undecidability of program analysis problems explains why no static analysis tool can be both sound and complete. The NP-hardness of scheduling problems explains why all scheduling heuristics make compromises. Knowing these impossibility results changes the practitioner’s response from “try harder” to “consider what is achievable instead.”
The second effect is fluency in reduction. Reduction is the central technique of complexity theory and one of the most powerful tools in algorithm design. Showing that a new problem is NP-hard by reducing from an existing NP-hard problem is the standard proof method for establishing intractability. Showing that a new problem is solvable by reducing it to an existing algorithm is the standard design method for exploiting known solutions. The technique is initially counterintuitive — it requires reasoning about transformations between problems rather than about the problems themselves — but once internalized it becomes a natural first approach to any new computational problem. This fluency is what separates theoretically trained practitioners from those who have only studied algorithms.
The third effect is a calibrated view of the P vs. NP question. Most practitioners who have studied the subject believe P ≠ NP without being able to prove it, and this belief is itself practically useful: it grounds the decision to use approximation algorithms for NP-hard problems rather than seeking an exact efficient solution that almost certainly does not exist. Without theoretical training, the failure to find an efficient algorithm for an NP-hard problem looks like personal failure; with it, the failure looks like structural truth about the problem. The calibration extends to approximation: knowing which NP-hard problems have good approximation algorithms (vertex cover: 2-approximation; TSP with triangle inequality: 1.5-approximation) and which provably cannot be approximated well (clique, assuming P ≠ NP) is actionable knowledge that shapes engineering decisions.
Resources
Books and Texts
The canonical entry is Sipser’s Introduction to the Theory of Computation (3rd ed., 2012). No other book has matched it for clarity, pacing, and the sense of building toward results that matter. Sipser treats the halting problem and NP-completeness as the dramatic culminations of careful preparation, and the structure reflects genuine pedagogical care: the proofs are complete, the examples are well-chosen, and the exercises build the skills systematically. For self-study, Sipser is the right primary text without competition. The third edition is the current one; the second is also fine.
Boaz Barak’s Introduction to Theoretical Computer Science (free at introtcs.org) is a modern open companion that emphasizes computational phenomena, universality, code-as-data, randomness, cryptography, and complexity with supplementary code notebooks. It is not a replacement for Sipser’s automata-to-computability progression, but it gives contemporary framing and computational examples that many learners find more alive than the traditional syllabus.
For the foundational and logical dimension, Boolos, Burgess, and Jeffrey’s Computability and Logic (5th ed., 2007) is the alternative entry. Where Sipser emphasizes complexity, Boolos-Burgess-Jeffrey emphasizes computability and its connections to Gödel’s incompleteness theorems and modal logic. A reader who wants to understand Gödel’s results as results in the theory of computation — not just as results in mathematical logic — should start here. The two books are genuinely complementary: Sipser for complexity, Boolos-Burgess-Jeffrey for foundational depth.
For complexity theory at depth, Arora and Barak’s Computational Complexity: A Modern Approach (2009, free draft available online) is the graduate standard. It covers the polynomial hierarchy, randomized complexity, interactive proofs and the PCP theorem, circuit complexity, derandomization, communication complexity, and the connections between complexity and cryptography with complete rigor. The PCP theorem — that any NP proof can be verified by reading a constant number of bits — is one of the deepest results in the subject, and Arora-Barak’s treatment is the right place to engage with it seriously. Goldreich’s Computational Complexity: A Conceptual Perspective (2008) covers similar material organized around conceptual commitments rather than results; reading both produces a more complete picture than either alone.
For computability at depth, Soare’s Turing Computability: Theory and Applications (2016) is the current standard for classical recursion theory — Turing degrees, recursively enumerable sets, oracle computation, and the fine structure of undecidability. This material goes well beyond what Sipser covers and is appropriate only for readers planning serious engagement with the mathematical logic side.
The original papers remain valuable primary sources. Turing’s 1936 paper “On Computable Numbers” is short, readable, and important enough that every serious student of the subject should read it. Cook’s 1971 paper and Karp’s 1972 paper are the founding documents of NP-completeness theory and are both directly accessible. Davis’s collection The Undecidable assembles the major papers of the founding decade in a single volume.
For the connection to logic, the automata-and-grammars treatment in Hopcroft, Motwani, and Ullman’s Introduction to Automata Theory, Languages, and Computation (3rd ed., 2006) goes deeper than Sipser on the formal language side — useful for readers who need the formal language theory for compiler work or formal methods.
| Book | Role | Type |
|---|---|---|
| Sipser, Introduction to the Theory of Computation (3rd ed.) | Canonical entry; clear and complete | Entry |
| Barak, Introduction to Theoretical Computer Science (free) | Modern open companion with code notebooks | Auxiliary |
| Boolos, Burgess & Jeffrey, Computability and Logic (5th ed.) | Foundationally oriented alternative entry | Entry |
| Soare, Turing Computability | Classical recursion theory at depth | Depth |
| Hopcroft, Motwani & Ullman, Intro to Automata Theory (3rd ed.) | Formal language theory | Reference |
| Turing (1936), Cook (1971), Karp (1972) primary papers | Founding documents | Reference |
| Complexity theory bridge: Arora & Barak, Computational Complexity (free draft) | Bridge forward to §3.3 complexity theory | Auxiliary |
| Complexity theory bridge: Goldreich, Computational Complexity: A Conceptual Perspective | Alternative advanced bridge to §3.3 | Auxiliary |
| Davis, ed., The Undecidable | Original papers collected | Auxiliary |
Courses and Lectures
MIT 6.045J / 18.404 (Theory of Computation, free on MIT OCW and YouTube) is the most polished free course on the subject, using Sipser as textbook. Michael Sipser has taught the course himself, and the lectures match the book in quality. Complete lecture videos, problem sets, and exams are available. For self-learners, this is the right companion to the textbook.
Scott Aaronson’s course materials for his quantum complexity and complexity theory courses (available on his website) are unusually good for readers who want to engage with the research frontier or the quantum dimension. Aaronson writes with both technical precision and genuine enthusiasm for the material.
MIT 6.046 (Design and Analysis of Algorithms, free on MIT OCW) covers algorithms at the level where complexity theory becomes relevant — NP-completeness reductions, approximation algorithms, randomized algorithms. It complements 6.045 and is the natural next course for readers who want to connect the theory to algorithm design.
| Course | Platform | Type |
|---|---|---|
| MIT 6.045J / 18.404 Theory of Computation (free) | MIT OCW / YouTube | Entry |
| MIT 6.046 Design and Analysis of Algorithms (free) | MIT OCW | Depth |
| Aaronson, complexity and quantum complexity materials (free) | scottaaronson.com | Depth |
Practice, Tools, and Current Sources
JFLAP (jflap.org, free) is an interactive tool for building and testing finite automata, pushdown automata, Turing machines, and grammars. Designing a Turing machine from scratch in JFLAP — configuring transitions, tracing execution on inputs, debugging — makes the formalism concrete in a way that reading descriptions cannot. Building a universal Turing machine in JFLAP, even a simple one, is one of the most instructive exercises in the subject.
Automata Tutor v3 (free online) adds an exercise-and-feedback layer for automata and formal languages, including finite automata, regular expressions, context-free grammars, pushdown automata, Turing machines, and conversions between models. It is valuable because automatic feedback makes this part of the subject practiceable at volume; it should supplement, not replace, proofs done by hand.
Implementing a simulated Turing machine in a language of your choice (Python is simplest) takes about 50 lines and forces all the formal definitions to become operational. Extending the simulation to handle the halting problem — and observing that you cannot — makes the undecidability proof intuitive in a way that the abstract argument does not.
Scott Aaronson’s “Who Can Name the Bigger Number?” and “Why Philosophers Should Care About Computational Complexity” (both free on his website) are short essays that engage with the philosophical and mathematical significance of computability and complexity results without requiring technical preparation. Reading them alongside Sipser provides context for why the technical results matter.
The Complexity Zoo (complexityzoo.net, free) is a comprehensive reference for complexity classes and their relationships. As a reference during study, it is invaluable; as a starting point, it is overwhelming. Use it to look up specific classes as you encounter them.
| Resource | Platform | Type |
|---|---|---|
| JFLAP (free) | jflap.org | Practice |
| Automata Tutor v3 (free) | automata-tutor.model.in.tum.de | Practice |
| Complexity-class lookup via Complexity Zoo (free) | complexityzoo.net | Reference |
| Aaronson essays (free) | scottaaronson.com | Depth |
| Simulated Turing machine implementation | Local | Practice |
Traps
| Trap | Why it misleads | Better response |
|---|---|---|
| Treating the subject as automata theory | Many curricula front-load finite automata and formal grammars so heavily that learners finish the subject believing it is primarily about these models. Automata and grammars are entry points to the formal language theory that completes in Turing machines; the substance of the subject is computability and complexity. A learner who knows regular expressions and context-free grammars but does not understand the halting problem or NP-completeness has learned the introduction but not the subject. | Treat automata as scaffolding leading to Turing machines, undecidability, and NP-completeness — which are the intellectual payoff. If a course covers more automata than complexity, supplement with the Sipser chapters on complexity. The test: can you prove a new problem is NP-complete by reduction? If not, the core skill has not been acquired. |
| Confusing undecidability with intractability | Undecidable problems cannot be solved by any algorithm, regardless of resources. Intractable problems can be solved, but the resources required are prohibitive. These are categorically different: one is about computability, one is about efficiency. Conflating them produces confusion in both directions — treating NP-hard problems as if no algorithm exists, or treating undecidable problems as if “we just haven’t found the right algorithm yet.” | Always track which layer you are in. When the halting problem comes up, ask: is this a computability result or a complexity result? The answer is computability — no algorithm exists, full stop. When 3SAT comes up: complexity — algorithms exist, but none known to be efficient. The distinction matters for how you respond to difficult problems in practice. |
| Skipping the formal definitions | Reductions — the central proof technique — require precise definitions of what a problem is, what a reduction does, and what NP-completeness means. Learners who engage with the intuition without the formal definitions cannot write correct reductions because they have no precise target to aim at. Correctness of a reduction requires showing that yes-instances map to yes-instances and no-instances map to no-instances; this cannot be verified without knowing formally what the problems are. | Work through the formal definitions carefully before attempting reductions. Write out the formal definition of polynomial-time reduction. Practice on problems where the right reduction is given — trace through why it is correct — before attempting to construct reductions independently. |
| Memorizing reductions rather than constructing them | A learner who memorizes the reduction from 3-SAT to independent set can reproduce it but has not learned how to do reductions. The skill is recognizing, when given a new problem, which known problem to reduce from, and constructing the reduction from scratch. This skill is built only through practice — doing many exercises where the reduction is not given. | Do all the reduction exercises in Sipser, constructing each reduction from scratch before checking the solution. When a reduction is given in a proof, cover the proof, attempt it independently, then compare. The cognitive effort of attempting before seeing is what builds the skill. |
| Overextending impossibility results | After learning undecidability and NP-completeness, some learners conclude that nothing useful can be done about hard problems. This is wrong. Many undecidable problems have decidable special cases or useful approximations. Many NP-hard problems have efficient algorithms for practical instances. The impossibility results constrain what is achievable in general; they do not preclude useful work on the specific cases that arise in practice. | Study approximation algorithms, parameterized complexity, and average-case analysis alongside impossibility results. The question after establishing NP-hardness is not “give up” but “what can be achieved?” — approximation, restriction, heuristics with guarantees. Vazirani’s Approximation Algorithms and the FPT framework in Cygan et al.’s Parameterized Algorithms provide the constructive counterpart to the impossibility theory. |
| Missing the connection to practice | The theory of computation is often taught and learned as self-contained mathematics with no connection to working code. This is partly accurate — the results are mathematical — but it obscures the practical implications. Undecidability explains why static analysis tools are necessarily incomplete. Complexity explains why no scheduling algorithm is universally efficient. The PCP theorem underlies hardness of approximation results that shape what approximation algorithms we look for. | Make a habit of asking, for each major result, what it implies for software that is built and deployed. When you next write a static analysis pass, remember Rice’s theorem. When you next tune a scheduling heuristic, remember that exact solutions are NP-hard. The theory earns its keep when it informs practice. |
中文
计算是每个人都会做的事,是机器会做的事,也是在 1936 年之前没有精确定义的事。在那一年之前,数学家们会自信地谈论“有效过程”和“机械方法”,但他们所指的东西是非形式的——大致说,是一种不需要巧思、只需要耐心的过程。计算理论正是这样一门学科:它使这个非形式概念变得精确,发现这种精确性意味着什么,并得到了一些此前无人预料的结果。
这个主题组织为三层,每一层都会提出上一层使之成为可能的问题。第一层是机器模型:什么是计算?这一层产生形式模型——图灵机、λ 演算、寄存器机——以及一个核心事实:它们都定义了同一类可计算函数。第二层是可计算性:不考虑资源成本,什么可以被计算?这一层产生不可能性结果——停机问题、Rice 定理、算术不可判定性——确立了没有算法能够做到什么。第三层是复杂性:在受限资源内,什么可以被计算?这一层产生复杂性类——P、NP、PSPACE——以及组织当代算法设计的这些类之间的关系。
这三层并不是为了方便而拼在一起的独立主题。每一层的问题,都只有在上一层存在之后才能被表达。“这个函数能被计算吗?”要求先有计算定义。“这个问题在 P 中吗?”要求先有可计算性的定义。完整主题是三者的整合,而这种整合正是它的智识连贯性所在。
计算理论位于第 3 章的概念中心。复杂性理论(§3.3)会深入展开第三层。逻辑(§3.1)是它最接近的邻居:不可判定性结果和逻辑不完备性,是同一现象在不同语言中的表达。算法(§2.6)是当“这是否可处理?”的答案为“是”时,实践者所使用的东西。密码学(§4.5)建立在复杂性理论的困难性假设上。理解为什么这些假设被相信——而不只是知道它们被相信——需要学习这个主题。
前置知识:离散数学与证明(§2.2),尤其是归纳和对角线论证。逻辑(§3.1)会丰富理解,但并不是开始学习的严格前提。
当数学试图机械化自身时发生了什么
故事始于一次失败,而这次失败比任何成功都更有价值。
1900 年,David Hilbert 在国际数学家大会上提出了二十三个开放问题,他相信这些问题会定义下一世纪的数学。其中两个问题直接关系到计算理论。第二个问题要求证明算术公理是一致的——也就是不会导出矛盾。第十个问题要求找到一种算法,判断任意给定的整数系数多项式方程是否存在整数解。这两个问题都预设:数学原则上能够用机械方式回答自己的基础问题。两个预设后来都被证明是错误的。
第二个问题在 1931 年被 Kurt Gödel 解决。他的不完备性定理表明,任何足够表达算术的一致形式系统,都包含在该系统内部为真但不可证明的命题,并且不能证明自身一致。第一定理摧毁了 Hilbert 关于形式证明最终会捕捉全部数学真理的希望。第二定理摧毁了他关于一致性可以从系统内部建立的希望。这些证明使用了一种自指技术——把关于形式系统自身的陈述编码到系统内部——这种技术后来成为可计算性理论的核心方法。
第十个问题在 1970 年由 Yuri Matiyasevich 解决,完成了 Martin Davis、Hilary Putnam 和 Julia Robinson 在此前二十年中开始的工作。但对计算机科学来说,真正关键的是 1936 年被解决的问题:Hilbert 的 Entscheidungsproblem,也就是判定问题。是否存在一种机械过程,能够在有限时间内判断任意给定数学陈述是否可证明?Hilbert 相信答案是肯定的。1936 年,Alonzo Church 和 Alan Turing——彼此独立、身处大西洋两岸、使用不同形式系统——证明答案是否定的。
Church 的方法是 λ 演算,这是他自 1932 年起一直在发展的、通过抽象和应用来定义函数的记号系统。Turing 的方法则是后来以他命名的机器:一条无限纸带,一个读写头,以及一组有限状态,根据读写头读到的内容决定下一步做什么。Turing 的论文 “On Computable Numbers, with an Application to the Entscheidungsproblem” 是计算机科学的奠基文献。它同时做了三件事:定义了计算的形式模型,证明存在该模型不能计算的函数,并用这个证明说明没有算法能够解决 Entscheidungsproblem。所用技术是对角线法——对于任何声称存在的判定过程,构造一个会让该过程失败的输入。Gödel 在 1931 年使用过同样技术;Cantor 在 1891 年用它证明实数不可数。对角线法已经是一种已知证明方法,但 Turing 将其应用于计算,赋予了它新的范围。
直接后果是 Church-Turing 论题:所有合理的有效计算形式化,都定义同一类函数。λ 演算、图灵机、寄存器机、递归函数——它们各自独立发展出来,却都被证明计算同一批函数。这种汇合不是定理,而是关于形式模型空间的经验事实;此后每当新模型被提出,它都会再次得到确认:元胞自动机、DNA 计算、模拟计算——每一种模型要么计算能力弱于图灵机,要么恰好等同于图灵机,没有任何严肃候选模型计算得更多。这个论题仍然是论题,而不是证明,因为它把图灵可计算性这一形式概念与有效计算这一非形式概念连接起来。但它已经经受了近九十年考验,学界已经把它视为关于计算本质的深层真理。
可计算性理论中最重要的单一结果——也是最能揭示不可计算性在实践中意味着什么的结果——是停机问题的不可判定性。停机问题问的是:给定任意程序和任意输入,这个程序最终会停机吗?Turing 在 1936 年的论文中证明,不存在算法能够对所有程序和输入回答这个问题。证明使用对角线法:假设存在这样一个算法 H;构造一个程序 D,让它把 H 作用到自身上,然后做与 H 预测相反的事情;再问 H 对输入为 D 的 D 会说什么——无论回答如何都会导致矛盾。
停机问题的不可判定性不是一个奇观,而是关于计算的结构性事实,并有广泛后果。Henry Gordon Rice 在 1953 年证明的 Rice 定理将其推广:任何非平凡的程序语义性质——也就是某些程序具备、某些程序不具备,并且依赖其行为而非语法的性质——都是不可判定的。这意味着,人们通常想一般性询问程序的大多数问题,都无法由任何算法回答。这个程序是否在所有输入上终止?它是否曾经输出某个特定字符串?它是否与另一个程序计算同一个函数?全部不可判定,而且可以被证明为不可判定。实践后果是,没有任何通用静态分析工具可以对这些问题给出完美准确答案;每一种这样的工具要么会漏掉一些真实 bug(假阴性),要么会报告一些误报(假阳性),而这种取舍在原则上不可避免,不只是实践中困难。
复杂性层正式到来于 Stephen Cook 1971 年的论文 “The Complexity of Theorem Proving Procedures”。Cook 一直在研究自动推理的理论基础,并识别出一个已经潜伏的问题:在那些可计算的问题中,哪些能够被 高效 计算——也就是以输入规模的多项式时间完成?这个答案会决定什么在实践上可行,而不只是理论上可能。
Cook 证明,布尔可满足性——判断一个命题公式是否存在满足它的真值赋值——具有一种非凡性质:NP 中每个问题都可以在多项式时间内归约到它。NP 是解可以被高效验证的问题类;P 是可以被高效 求解 的问题类。如果 SAT 有多项式时间算法,那么 P = NP,每个具有高效可验证解的问题,也都会有高效可找到的解。Cook 的论文明确提出了 P vs. NP,并证明 SAT 是 NP 中最难的问题。
Richard Karp 1972 年的论文立即扩展了这一点,证明另外二十一个自然组合问题——顶点覆盖、独立集、哈密顿环、图着色、装箱、旅行商问题——都可以归约到 SAT,因此都是 NP 完全的。这些问题此前被独立研究;没有人怀疑它们相关。Karp 的论文揭示,它们是同一个问题的不同形式;高效解决其中任何一个,就能高效解决所有问题。这个发现具有智识上的震撼力:表面上多样的困难问题,隐藏着深层结构统一性。
P vs. NP 自 1971 年以来一直悬而未决,五十多年无法在任何方向上解决,这本身也具有信息量。几乎每位研究者都相信 P ≠ NP——即 NP 完全问题不存在高效算法——但这种信念建立在直觉和巨大努力的失败之上,而不是证明之上。Clay Mathematics Institute 在 2000 年将它列为七个千禧年大奖难题之一。这个问题的困难反映了某种真实东西:现有数学技术不足以为显式问题证明计算资源下界,需要新的技术。寻找这些技术,是当代复杂性理论的核心活动之一。
从 1970 年代的奠基十年开始,复杂性理论沿着几个如今已成标准的方向发展。多项式层级——由交替量词定义、位于 NP 之上的复杂性类层级——对那些比 NP 更难但还没有 PSPACE 那么难的问题给出了更精细的分类。随机复杂性类(BPP、RP、coRP)捕捉了允许随机性时可以高效求解的问题。交互式证明系统(Shamir 在 1992 年证明 IP = PSPACE)揭示了带交互的概率验证具有令人惊讶的强大能力。PCP 定理(Arora、Lund、Motwani、Sudan、Szegedy,1992)表明,NP 证明可以通过读取常数个比特来验证;它立即带来了不可近似性后果:许多 NP-hard 问题不仅难以精确求解,而且在任何常数因子内近似也很难。
当代研究前沿已经发展到该领域奠基者无法预料的方向。细粒度复杂性不问问题是否为多项式时间,而是比较它们多项式尺度的差异:编辑距离的二次下界在 SETH(强指数时间假设)条件下能否改进?参数化复杂性识别结构参数:当这些参数有界时,原本不可处理的问题会变得可处理——这就是 FPT(固定参数可处理)算法理论。平均情况复杂性研究问题在典型输入而非最坏情况输入上什么时候困难——这是由密码学推动的,因为密码学需要的不只是最坏情况下困难的问题,而是对来自自然分布的输入也困难的问题。量子复杂性以 BQP 和 QMA 等类,把框架扩展到量子计算;Shor 的多项式时间整数分解算法表明,一些经典上困难的问题在量子上可处理。这个领域从未静止;其基础结果是永久的,但它追问的问题仍在持续演化。
模型、不可判定性与复杂性
机器模型及其确立的东西
图灵机是一个有限状态自动机,并拥有对无限纸带的读写访问。机器从初始状态开始,输入写在纸带上;它一次读取一个符号,写入新符号,向左或向右移动读写头,并根据转移函数在状态之间转换。这个模型刻意保持极简,却捕捉了计算的基本特征:有限控制(程序)、无界内存(纸带)和顺序操作。
极简性正是重点。图灵机足够简单,可以被数学推理;又足够强大——正如 Church-Turing 论题所断言的——能够计算任何其他合理模型能够计算的东西。这种等价性有具体后果。当我们证明停机问题对图灵机不可判定时,我们同时也证明它对所有等价模型不可判定:Java 程序、Python 脚本、寄存器机、元胞自动机。不可判定性不是图灵机模型的伪影;它是计算本身的性质。
通用图灵机——一种特定图灵机,输入为任何其他图灵机的描述及其输入,并在该输入上模拟被描述机器——确立了计算具有 自指性。一台机器可以读取另一台机器的描述并执行它;计算可以以计算自身为对象。这是一切通用编程的基础:通用计算机是通用图灵机的物理实现,而编写处理其他程序的程序(编译器、解释器、调试器、定理证明器)则建立在通用性之上。如果没有通用图灵机定理,这些东西原则上都不可能存在。
有限自动机和下推自动机是受限内存下的较弱模型。有限自动机完全没有纸带——只有有限状态控制器——并且恰好识别正则语言,也就是可以由正则表达式描述的语言。下推自动机增加一个栈,并且恰好识别上下文无关语言,也就是可以由上下文无关文法描述的语言。Chomsky 层级组织了这些模型:正则语言 ⊂ 上下文无关语言 ⊂ 递归语言 ⊂ 递归可枚举语言。每一类都对应一种计算模型,而且每个包含关系都是严格的——存在不是正则的上下文无关语言,存在不是上下文无关的递归语言,等等。
这些受限模型的重要性不只在于它们属于 Chomsky 层级。正则表达式出现在词法分析中,也就是编译的第一阶段;也出现在文本搜索(grep、sed)和网络包过滤中。上下文无关文法描述编程语言语法;解析——判断一个字符串是否为合法程序并提取其结构——就是上下文无关语言识别。理解这些连接需要形式理论;实践中使用工具不一定需要,但理解工具则需要。
对角线法
对角线法是大多数不可判定性结果背后的证明技术,值得作为一种独立技术来理解,而不只是理解某一个特定证明。
这个思想可以追溯到 Cantor 1891 年关于实数不可数的证明:假设你有一张列出了 0 到 1 之间所有实数的列表;构造一个新数,使其在第 n 位小数上不同于列表中的第 n 个数;于是这个新数不在列表中,与假设矛盾。这个构造出的数是通过 对角线方式 定义的——它引用这张列表,并系统性地与其中元素不同。
Gödel 的编码技术应用于形式系统,使关于可证明性的陈述可以被编码为系统内部的算术陈述。这使算术中的自指成为可能:人们可以构造一个陈述,其效果相当于说“我不可证明”。如果该陈述可证明,系统就不一致;如果不可证明,那么该陈述为真但不可证明。不完备性定理由此推出。
Turing 对停机问题不可判定性的证明使用同样模式。假设算法 H 能判定停机;构造程序 D,询问 H:D(D) 是否停机,然后做相反的事;再在 D 自己的描述上运行 D;得到矛盾。程序 D 是通过对角线方式定义的——它引用 H,并系统性地反转其输出。
这个模式可以推广:每当你想证明不存在算法能够判定某个性质 P 时,可以假设存在一个判定 P 的算法 A,再利用 A 通过对角线方式构造一个新对象,使其违反 A 所依赖的假设,并推出矛盾。不同不可判定性证明中的构造会变化,但结构相同。一旦识别出这个结构,不可判定性证明就会在一种层次上变得可读,其中细节相对于模式是次要的。
复杂性类与 P vs. NP 问题
时间复杂性按照求解问题所需步数如何随输入规模增长来分类问题。P 是确定性机器可以在多项式时间内求解的问题类——直觉上,就是可处理问题类。NP 是解可以在多项式时间内被 验证 的问题类:给定一个解,检查它只需要多项式时间,即使找到这个解可能需要指数时间。P = NP 的问题问的是,高效验证是否蕴含高效求解。
关于 NP 的结构性事实,是 NP 完全性:NP 中存在一些问题,使 NP 中每个问题都可以在多项式时间内归约到它们。如果任何一个 NP 完全问题在 P 中,那么 P = NP。如果任何一个 NP 完全问题不在 P 中,那么 P ≠ NP。算法设计中的主要组合问题——可满足性、图着色、旅行商、整数规划——都是 NP 完全的。这些问题的 NP-hard 性,解释了为什么它们的精确算法在最坏情况下以指数时间运行,也解释了为什么近似方法和启发式方法是实践工具。
证明一个问题是 NP 完全的,有两部分:证明它在 NP 中(较容易:给出多项式时间验证器),以及证明每个 NP 问题都可以在多项式时间内归约到它(较难:需要从已知 NP 完全问题归约)。第一个 NP 完全问题是 SAT,由 Cook 证明——当时没有已有 NP 完全问题可用于归约,所以他直接从 NP 的定义出发进行归约。之后每个 NP 完全性证明,都从 SAT 或某个已经知道是 NP 完全的问题开始归约。
在 NP 之外,复杂性动物园还包括 PSPACE(可以用多项式空间求解的问题,不考虑时间)、EXPTIME(可以在指数时间内求解的问题)以及许多其他类。多项式层级 PH 推广了 NP:Σ₁ᵖ = NP,Π₁ᵖ = co-NP,Σ₂ᵖ = NP^NP,如此继续。随机类 BPP(有界错误概率多项式时间)和 RP 捕捉了允许随机性时可以高效求解的问题。交互式证明类 IP 和 MIP 则把计算扩展为证明者与验证者之间的交互;IP = PSPACE 和 MIP* = RE(递归可枚举,即全部可计算性范围)是复杂性理论中最深刻的结果之一。
学习这个主题会改变什么
计算理论会产生其他 CS 主题无法以同样形式提供的智识效果。
最直接的是对不可能性的直觉。学习不可判定性和 NP 完全性之后,实践者在一个问题抵抗所有方法时,会养成提问习惯:它是否因为结构性原因而抵抗?识别不可判定问题的形状——从停机问题归约、Rice 定理、自指结构——或识别 NP 完全问题的形状——在指数数量配置中搜索且没有捷径——可以节省巨大努力。程序分析问题的不可判定性解释了为什么没有静态分析工具可以同时可靠且完备。调度问题的 NP-hard 性解释了为什么所有调度启发式都必须妥协。知道这些不可能性结果,会让实践者的反应从“再努力一点”转向“考虑什么是可实现的”。
第二个效果,是归约能力。归约是复杂性理论的核心技术,也是算法设计中最强大的工具之一。通过从已有 NP-hard 问题归约来证明新问题 NP-hard,是确立不可处理性的标准证明方法。通过把新问题归约到已有算法来证明新问题可解,是利用已知解法的标准设计方法。这个技术一开始反直觉——它要求对问题之间的变换进行推理,而不是只对问题本身推理——但一旦内化,它会成为面对任何新计算问题时自然的第一反应。这种流利度,区分了受过理论训练的实践者和只学过算法的实践者。
第三个效果,是对 P vs. NP 问题形成经过校准的看法。多数学习过这个主题的实践者相信 P ≠ NP,虽然不能证明它;而这种信念本身在实践中有用:它支撑了对 NP-hard 问题使用近似算法,而不是寻找几乎肯定不存在的精确高效解法。没有理论训练时,找不到一个 NP-hard 问题的高效算法,看起来像个人失败;有了理论训练,它看起来像这个问题的结构性真相。这种校准也延伸到近似:知道哪些 NP-hard 问题有良好近似算法(顶点覆盖:2-近似;满足三角不等式的 TSP:1.5-近似),哪些在假设 P ≠ NP 时可以证明不能良好近似(团问题),是一种会塑造工程决策的可行动知识。
资源
书籍与文本
经典入口是 Sipser 的 Introduction to the Theory of Computation(第 3 版,2012)。在清晰度、节奏,以及逐步构建到真正重要结果的感觉上,没有其他书能与它匹敌。Sipser 把停机问题和 NP 完全性处理为经过精心准备后的戏剧性高潮,其结构体现了真正的教学用心:证明完整,例子选择良好,习题系统地建立能力。对于自学者来说,Sipser 是毫无竞争的主教材。第三版是当前版本;第二版也可以。
Boaz Barak 的 Introduction to Theoretical Computer Science(introtcs.org 免费)是一本现代开放配套教材,强调计算现象、通用性、代码即数据、随机性、密码学和复杂性,并配有代码 notebook。它不能替代 Sipser 从自动机到可计算性的推进路线,但它提供了当代框架和计算例子,许多学习者会觉得它比传统大纲更鲜活。
对于基础和逻辑维度,Boolos、Burgess 和 Jeffrey 的 Computability and Logic(第 5 版,2007)是另一种入口。Sipser 更强调复杂性;Boolos-Burgess-Jeffrey 则强调可计算性及其与 Gödel 不完备性定理和模态逻辑的联系。想把 Gödel 结果理解为计算理论中的结果——而不只是数理逻辑中的结果——的读者,可以从这里开始。两本书是真正互补的:Sipser 用于复杂性,Boolos-Burgess-Jeffrey 用于基础深度。
对于 复杂性理论的深入学习,Arora 和 Barak 的 Computational Complexity: A Modern Approach(2009,网上有免费草稿)是研究生标准教材。它以完整严格性覆盖多项式层级、随机复杂性、交互式证明和 PCP 定理、电路复杂性、去随机化、通信复杂性,以及复杂性和密码学之间的连接。PCP 定理——任何 NP 证明都可以通过读取常数个比特来验证——是这个主题中最深刻的结果之一;Arora-Barak 的处理是认真进入它的合适位置。Goldreich 的 Computational Complexity: A Conceptual Perspective(2008)覆盖类似材料,但围绕概念承诺而不是结果组织;两者一起阅读,会比单独阅读任一本获得更完整图景。
对于 可计算性的深入学习,Soare 的 Turing Computability: Theory and Applications(2016)是当前经典递归论的标准文本——图灵度、递归可枚举集、oracle 计算,以及不可判定性的精细结构。这些内容远远超出 Sipser 的覆盖,适合计划认真进入数理逻辑侧面的读者。
原始论文仍然是有价值的一手来源。Turing 1936 年的 “On Computable Numbers” 篇幅短、可读,而且重要到每个严肃学习者都应该阅读。Cook 1971 年的论文和 Karp 1972 年的论文是 NP 完全性理论的奠基文献,也都可以直接阅读。Davis 编的 The Undecidable 把奠基十年的主要论文收录在一卷中。
关于与逻辑的连接,Hopcroft、Motwani 和 Ullman 的 Introduction to Automata Theory, Languages, and Computation(第 3 版,2006)在自动机和文法方面比 Sipser 更深入——适合需要为了编译器工作或形式化方法而掌握形式语言理论的读者。
| 书籍 | 作用 | 类型 |
|---|---|---|
| Sipser,Introduction to the Theory of Computation(第 3 版) | 经典入口;清晰且完整 | 入门 |
| Barak,Introduction to Theoretical Computer Science(免费) | 带代码 notebook 的现代开放配套 | 辅助 |
| Boolos、Burgess & Jeffrey,Computability and Logic(第 5 版) | 偏基础的另一种入口 | 入门 |
| Soare,Turing Computability | 经典递归论深入处理 | 深入 |
| Hopcroft、Motwani & Ullman,Intro to Automata Theory(第 3 版) | 形式语言理论 | 参考 |
| Turing(1936)、Cook(1971)、Karp(1972)原始论文 | 奠基文献 | 参考 |
| 复杂性理论桥梁:Arora & Barak,Computational Complexity(免费草稿) | 通往 §3.3 复杂性理论的进阶桥梁 | 辅助 |
| 复杂性理论桥梁:Goldreich,Computational Complexity: A Conceptual Perspective | 通往 §3.3 的另一种高级桥梁 | 辅助 |
| Davis 编,The Undecidable | 原始论文合集 | 辅助 |
课程与讲座
MIT 6.045J / 18.404(Theory of Computation,MIT OCW 和 YouTube 免费)是这个主题上最精致的免费课程,使用 Sipser 作为教材。Michael Sipser 本人也教授过这门课,讲座质量与书相匹配。完整视频、习题和考试都可以获取。对于自学者来说,这是主教材的正确配套。
Scott Aaronson 的课程材料,包括量子复杂性和复杂性理论课程材料(可在其个人网站获取),特别适合想进入研究前沿或量子维度的读者。Aaronson 的写作同时具有技术精确性和对材料的真实热情。
MIT 6.046(Design and Analysis of Algorithms,MIT OCW 免费)覆盖算法设计中复杂性理论开始变得相关的层级——NP 完全性归约、近似算法、随机算法。它补充 6.045,并且是希望把理论连接到算法设计的读者的自然下一门课。
| 课程 | 平台 | 类型 |
|---|---|---|
| MIT 6.045J / 18.404 Theory of Computation(免费) | MIT OCW / YouTube | 入门 |
| MIT 6.046 Design and Analysis of Algorithms(免费) | MIT OCW | 深入 |
| Aaronson 复杂性与量子复杂性材料(免费) | scottaaronson.com | 深入 |
实践、工具与当前来源
JFLAP(jflap.org,免费)是构建和测试有限自动机、下推自动机、图灵机和文法的交互工具。亲自在 JFLAP 中从零设计一台图灵机——配置转移,在输入上追踪执行,进行调试——会以单纯阅读描述无法做到的方式让形式系统变得具体。在 JFLAP 中构建一台通用图灵机,哪怕是简单版本,也是这个主题中最有教育意义的练习之一。
Automata Tutor v3(在线免费)为自动机和形式语言添加了练习与反馈层,包括有限自动机、正则表达式、上下文无关文法、下推自动机、图灵机,以及模型之间的转换。它有价值,是因为自动反馈让这个主题能够进行大量练习;它应该补充手写证明,而不是替代手写证明。
用自己选择的语言实现一个模拟图灵机(Python 最简单)大约只需要 50 行代码,却会迫使所有形式定义变成可操作的东西。进一步扩展这个模拟器,让它处理停机问题——并观察到你无法做到——会使不可判定性证明以抽象论证无法达到的方式变得直觉化。
Scott Aaronson 的 “Who Can Name the Bigger Number?” 和 “Why Philosophers Should Care About Computational Complexity”(均可在其网站免费获取)是两篇短文,不需要技术准备,却能讨论可计算性和复杂性结果在哲学和数学上的意义。与 Sipser 一起阅读,可以提供这些技术结果为什么重要的语境。
Complexity Zoo(complexityzoo.net,免费)是复杂性类及其关系的综合参考。在学习过程中作为参考使用,它非常宝贵;但作为起点,它会令人不知所措。应在遇到具体复杂性类时用它查询。
| 资源 | 平台 | 类型 |
|---|---|---|
| JFLAP(免费) | jflap.org | 实践 |
| Automata Tutor v3(免费) | automata-tutor.model.in.tum.de | 实践 |
| 通过 Complexity Zoo 查询复杂性类(免费) | complexityzoo.net | 参考 |
| Aaronson 文章(免费) | scottaaronson.com | 深入 |
| 模拟图灵机实现 | 本地 | 实践 |
陷阱
| 陷阱 | 为什么会误导 | 更好的回应 |
|---|---|---|
| 把这个主题当成自动机理论 | 许多课程过度前置有限自动机和形式文法,导致学习者学完后以为这个主题主要就是这些模型。自动机和文法是进入形式语言理论的入口,而形式语言理论最终通向图灵机;这个主题的实质是可计算性和复杂性。一个只懂正则表达式和上下文无关文法,却不理解停机问题或 NP 完全性的学习者,只学到了导论,而没有学到主题本身。 | 把自动机视为通向图灵机、不可判定性和 NP 完全性的脚手架——后者才是智识回报所在。如果一门课覆盖自动机多于复杂性,就用 Sipser 关于复杂性的章节补充。检验标准是:你能否通过归约证明一个新问题是 NP 完全的?如果不能,核心能力还没有获得。 |
| 混淆不可判定性与不可处理性 | 不可判定问题无法由任何算法解决,不管资源有多少。不可处理问题可以被解决,但所需资源高到无法接受。二者属于不同范畴:一个关乎可计算性,一个关乎效率。混淆它们会在两个方向上制造混乱——把 NP-hard 问题当成没有算法存在,或者把不可判定问题当成“我们只是还没找到正确算法”。 | 始终追踪自己处在哪一层。当停机问题出现时,问:这是可计算性结果还是复杂性结果?答案是可计算性——没有算法存在,到此为止。当 3SAT 出现时:复杂性——算法存在,但没有已知高效算法。这个区别决定了你在实践中如何回应困难问题。 |
| 跳过形式定义 | 归约——核心证明技术——要求精确定义什么是问题、归约做什么、NP 完全性意味着什么。只接触直觉、不掌握形式定义的学习者,无法写出正确归约,因为他们没有精确目标。归约正确性要求证明 yes 实例映射到 yes 实例,no 实例映射到 no 实例;如果不知道问题的形式定义,就无法验证这一点。 | 在尝试归约之前,仔细掌握形式定义。写出多项式时间归约的形式定义。先在已经给出正确归约的问题上练习——逐步追踪为什么它正确——然后再尝试独立构造归约。 |
| 记忆归约,而不是构造归约 | 一个记住 3-SAT 到独立集归约的学习者,可以复现它,但并没有学会如何做归约。真正能力在于:面对一个新问题时,识别应该从哪个已知问题归约,并从零构造归约。这种能力只能通过练习建立——做大量没有给出归约的习题。 | 做完 Sipser 中所有归约习题,并在查看答案前从零构造每个归约。当证明中给出归约时,先遮住证明,自己尝试,再比较。先努力尝试再看到答案所产生的认知劳动,才会建立能力。 |
| 过度扩展不可能性结果 | 学完不可判定性和 NP 完全性之后,一些学习者会得出结论:面对困难问题,没有任何有用事情可做。这是错误的。许多不可判定问题有可判定的特殊情况或有用近似。许多 NP-hard 问题在实践实例上有高效算法。不可能性结果约束的是一般情况下可实现的东西;它们并不排除对实践中具体情况做有用工作。 | 在学习不可能性结果的同时,学习近似算法、参数化复杂性和平均情况分析。确立 NP-hard 之后的问题不是“放弃”,而是“还能实现什么?”——近似、限制、带保证的启发式。Vazirani 的 Approximation Algorithms 和 Cygan 等人的 Parameterized Algorithms 中的 FPT 框架,提供了不可能性理论的建设性对应物。 |
| 错过与实践的连接 | 计算理论常被当作自足数学来教和学,与实际代码没有联系。这部分是准确的——这些结果确实是数学结果——但它遮蔽了实践含义。不可判定性解释了为什么静态分析工具必然不完备。复杂性解释了为什么不存在普遍高效的调度算法。PCP 定理支撑不可近似性结果,而这些结果塑造我们寻找哪些近似算法。 | 对每个主要结果,养成追问它对被构建和部署的软件意味着什么的习惯。下次写静态分析 pass 时,记住 Rice 定理。下次调整调度启发式时,记住精确解是 NP-hard 的。理论在指导实践时,才真正发挥价值。 |