The Cook–Levin Theorem
We have a class, \mathsf{NP}, holding thousands of problems that all share the
"easy to check, seemingly hard to solve" flavour. A natural suspicion forms: maybe they are hard for the
same underlying reason — maybe there is one problem so central that solving it fast would solve
every one of them fast. In 1971 Stephen Cook (and independently Leonid Levin) proved exactly that, and
named the culprit: Boolean satisfiability, SAT.
The Cook–Levin theorem is the founding result of NP-completeness. It shows that SAT is a
universal \mathsf{NP} problem — every other problem in the class
quietly reduces to it. It is the bedrock every later
reduction
stands on.
Two definitions: NP-hard and NP-complete
Before the theorem we need the vocabulary of "hardest problems in a class", built from polynomial-time
mapping reductions. Write A \le_p B ("A
reduces to B") when there is a polynomial-time function mapping every instance
of A to an instance of B with the same yes/no
answer. Intuitively: "if I could solve B, I could solve
A", so B is at least as hard as
A.
- B is NP-hard if every
A \in \mathsf{NP} satisfies A \le_p B. It is at
least as hard as everything in \mathsf{NP} — but need not itself be in
\mathsf{NP} (it might be far harder).
- B is NP-complete if it is NP-hard and
B \in \mathsf{NP}. These are the hardest problems that still live
inside \mathsf{NP} — the summit of the class.
- The payoff: if any NP-complete problem is in
\mathsf{P}, then \mathsf{P} = \mathsf{NP}. They
stand or fall together.
This is a wonderful lever. To show \mathsf{P} = \mathsf{NP} you would only need
one fast algorithm for one NP-complete problem. And to believe a problem is
intractable, showing it NP-complete is the strongest practical evidence we have.
The theorem
- SAT is NP-complete. Every problem in \mathsf{NP}
reduces in polynomial time to deciding whether a Boolean formula (in conjunctive normal form) has a
satisfying assignment.
- CIRCUIT-SAT is NP-complete too — deciding whether a Boolean circuit's inputs can be
set to make the output 1. It is often the cleaner starting point, because
a computation is naturally a circuit.
The hard part is the word every. Membership (\text{SAT} \in \mathsf{NP})
is the easy half — a satisfying assignment is a certificate, checked in linear time. The genius is the
NP-hardness: a single construction that works for all
\mathsf{NP} problems at once, whatever they happen to be about.
The proof idea: a computation is a formula
Here is the trick that makes universality possible. Any problem A \in \mathsf{NP}
has, by definition, a nondeterministic
Turing machine
M that decides it in p(n) steps. We don't need to
know what M computes — only that its run is a mechanical, local
process. So we build a Boolean formula \Phi_x that is satisfiable
exactly when M has an accepting computation on input
x. A satisfying assignment is an accepting run.
Picture the computation as a tableau: a grid with one row per time step and one column
per tape cell. In a p(n)-step computation the head never travels further than
p(n) cells, so the grid is (p(n)+1) \times p(n) —
polynomial in size. Each cell records what is written there, whether the head is present, and (if so) the
machine's state.
Now introduce Boolean variables for the contents of every cell — one bunch of variables per grid square,
encoding its symbol/head/state. The whole formula \Phi_x is a conjunction of
four kinds of clauses, each purely local:
- Well-formed: each cell holds exactly one symbol; at most one cell per row has the
head. (No cell is in two states at once.)
- Correct start: row 0 spells out the initial
configuration — input x on the tape, head at the left, start state.
- Valid transitions: the killer clauses. Every
2 \times 3 window of cells (two consecutive rows, three adjacent columns)
must be consistent with M's transition rules. Because a Turing machine only
touches the cell under its head, checking every little window is enough to force the entire
next row to follow legally from the current one.
- Accepting end: some cell somewhere in the grid is in the accept state.
Each family has only polynomially many clauses, each of constant size, and the map from
x to \Phi_x is computable in polynomial time. And
\Phi_x is satisfiable \iff a legal accepting tableau
exists \iff M accepts x
\iff x \in A. That is a reduction from an
arbitrary A to SAT — so SAT is NP-hard.
Why locality is the whole game
The reason a global fact ("this is a valid computation of a complex machine") can be pinned down by a
pile of tiny, independent clauses is that computation is local. Between one step and the
next, almost nothing changes: only the cell under the head, and its immediate neighbours, can be affected.
So "row t+1 legally follows row t" is really just
"every little window is legal" — a conjunction of local constraints. Boolean formulas are built for
exactly this: expressing a big AND of small local conditions. That correspondence, computation ↔ formula,
is the engine of the entire theory.
SAT is not chosen for elegance — it is chosen because a Boolean formula is the most direct language for a
local, step-by-step process, and a Turing-machine computation is exactly that. CIRCUIT-SAT is
arguably even more natural: a bounded computation literally unrolls into a Boolean circuit, so
"does this circuit output 1?" is barely a translation at all. Once SAT is nailed down as NP-complete, we
almost never redo this heavy machinery again. Every other NP-completeness proof reduces
from an already-known NP-complete problem, which is far easier than reducing from all of
\mathsf{NP}. Cook and Levin paid the one-time cost so nobody else has to.
NP-hard means "at least as hard as everything in \mathsf{NP}" — it
says nothing about whether the problem is itself in \mathsf{NP}. Plenty of
NP-hard problems are far harder: the halting problem is NP-hard yet undecidable, and general
optimization or counting problems can be NP-hard while sitting well outside
\mathsf{NP}. NP-complete is the conjunction of two things: NP-hard
and a member of \mathsf{NP} (has a poly-time verifier). When
you claim a problem is NP-complete, you owe both halves — people forget the membership half
constantly, especially for optimization problems whose decision version is what actually lives in
\mathsf{NP}.