Skip to content
The Lumenary
Advanced undergraduateUndergraduate 120 min

Computational complexity

Some problems are easy to check but seem hard to solve. P, NP, reductions and NP-completeness — and the most famous open question in theoretical computer science.

Formally verified · 11 LeanComputationally tested · 2 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review
MotivationH

Easy to check, hard to find

A school in Nairobi has to schedule 40 examinations into a few time slots so that no student sits two exams at once. Draw a graph with one vertex per exam and an edge whenever some student takes both: a timetable with kk slots is exactly a colouring of the vertices with kk colours in which adjacent vertices differ. If someone hands you a proposed timetable, checking it is quick — look at every edge once. Finding one, or proving that none exists with three slots, seems to need a search through up to 340≈1.2×10193^{40} \approx 1.2 \times 10^{19} possibilities.

Compare two puzzles on the same graph. Euler's problem — is there a closed walk using every edge exactly once? — is answered instantly by checking that the graph is connected and every degree is even. Hamilton's problem — is there a cycle through every vertex exactly once? — looks almost the same, yet nobody knows an algorithm that is fundamentally faster than trying exponentially many possibilities.

Computability drew the line between what algorithms can and cannot do in principle. Complexity theory draws a finer line, between what they can do efficiently and what (we believe) they cannot. Its central question was asked by Kurt Gödel in a 1956 letter to John von Neumann, and in its modern form — does P equal NP? — it is one of the seven Millennium Prize Problems. This lesson assumes algorithm design and analysis and basic graph theory.

IntuitionH

Polynomial versus exponential

Measure the cost of an algorithm by the number of elementary steps it takes, as a function of the length nn of its input. An algorithm taking n2n^2 or n3n^3 steps slows down gracefully: doubling the input multiplies the time by 44 or 88. An algorithm taking 2n2^n steps does not: each extra bit of input doubles the time, and 21002^{100} steps would outlast the age of the universe on any conceivable computer.

Polynomial versus exponential growth

Raise k: every polynomial is eventually overtaken by 2^n.

  • n^k
  • 2^n
  • n^k: y = 1
  • 2^n: y = 2

Try k=3k = 3: the cubic is ahead until about n=10n = 10, after which 2n2^n leaves it behind for good. For every kk there is a crossover, because nk/2n→0n^k / 2^n \to 0.

The working hypothesis of the subject — the Cobham–Edmonds thesis — is that "efficiently solvable" means "solvable in polynomial time". Like the Church–Turing thesis it is not a theorem, and it is cruder: an n100n^{100} algorithm is useless and a 1.0001n1.0001^n algorithm may be excellent. But polynomial time has two great virtues. It is robust: Turing machines, random-access machines and real programming languages simulate each other with polynomial overhead, so the class of polynomial-time problems does not depend on the model. And it is closed under composition: a polynomial-time algorithm that calls a polynomial-time subroutine is still polynomial-time.

The second idea is the asymmetry of the timetable. For many problems a "yes" answer has a short certificate — a colouring, a cycle, a satisfying assignment — that can be checked quickly, even when finding it seems hard. NP is the class of such problems.

DefinitionsH

Definitions

A decision problem is a set LL of finite strings (over {0,1}\{0, 1\}, say): the instances whose answer is "yes". Mathematical objects are encoded as strings in any reasonable way — a graph by its adjacency matrix, a number in binary — and ∣x∣|x| is the length of the encoding. (Numbers in unary would be an unreasonable encoding: it is exponentially longer than binary and makes some hard problems look easy.)

Definition— running time, P

A Turing machine MM (see computability) runs in time T:N→NT : \N \to \N if it halts within T(∣x∣)T(|x|) steps on every input xx. TIME(T)\mathrm{TIME}(T) is the class of problems decided by some machine running in time O(T)O(T). The class P is

P=⋃k≥1TIME(nk),\mathrm{P} = \bigcup_{k \ge 1} \mathrm{TIME}(n^k),

the problems decidable in polynomial time.

Definition— NP

A problem LL is in NP if there are a polynomial qq and a polynomial-time machine VV, the verifier, such that for every string xx

x∈L  ⟺  there is a string y with ∣y∣≤q(∣x∣) and V(x,y)=1.x \in L \iff \text{there is a string } y \text{ with } |y| \le q(|x|) \text{ and } V(x, y) = 1.

The string yy is a certificate (or witness) for xx. Equivalently, NP is the class of problems decided in polynomial time by a nondeterministic Turing machine, one that may branch at each step and accepts if some branch accepts — hence the name: Nondeterministic Polynomial time.

Definition— coNP, PSPACE, EXPTIME

coNP is the class of complements of problems in NP: those whose "no" instances have short, efficiently checkable certificates. PSPACE is the class of problems decidable by a machine that uses at most polynomially many tape cells; EXPTIME =⋃kTIME(2nk)= \bigcup_k \mathrm{TIME}(2^{n^k}).

Definition— polynomial-time reduction, NP-complete

AA reduces to BB in polynomial time, A≤pBA \le_p B, if there is a polynomial-time computable function ff with x∈A  ⟺  f(x)∈Bx \in A \iff f(x) \in B for all xx (a Karp reduction). A problem BB is NP-hard if A≤pBA \le_p B for every A∈NPA \in \mathrm{NP}, and NP-complete if moreover B∈NPB \in \mathrm{NP}.

An NP-complete problem is a hardest problem in NP: a polynomial-time algorithm for it would give one for every problem in NP.

The problems we will use. Formulas are in conjunctive normal form (CNF): an AND of clauses, each an OR of literals xix_i or ¬xi\neg x_i.

  • SAT: is a given CNF formula satisfiable? 3-SAT: the same, for formulas with exactly three literals per clause.
  • INDEPENDENT SET: given a graph GG and kk, does GG have kk pairwise non-adjacent vertices?
  • VERTEX COVER: given GG and kk, is there a set of at most kk vertices touching every edge?
  • CLIQUE: given GG and kk, does GG have kk pairwise adjacent vertices?
  • 3-COL: is a given graph 3-colourable? HAM-CYCLE: does it have a Hamiltonian cycle?
Worked examplesH

Where problems live

Example— Problems in P

  • Reachability: is there a path from ss to tt? Breadth-first search, time linear in the size of the graph.
  • 2-colourability: colour greedily along a breadth-first search; the graph is 2-colourable iff no edge joins two vertices of the same colour (iff it has no odd cycle).
  • Euler circuits (connected, all degrees even), shortest paths (Dijkstra), minimum spanning trees (Kruskal), 2-SAT (strongly connected components of the implication graph).
  • Perfect matching in an arbitrary graph — Edmonds's blossom algorithm (1965), the paper in which he argued that polynomial time is the right notion of a "good" algorithm.
  • Linear programming — Khachiyan's ellipsoid method (1979).
  • Primality — the AKS algorithm of Agrawal, Kayal and Saxena (2002): deciding whether an nn-digit number is prime takes time polynomial in nn, not in the number itself.

Example— Problems in NP, with their certificates

  • SAT: a satisfying assignment; the verifier evaluates the formula.
  • 3-COL: a colouring; check every edge.
  • HAM-CYCLE: the cycle, as a list of vertices; check it visits each vertex once along edges.
  • CLIQUE, INDEPENDENT SET, VERTEX COVER: the set of vertices.
  • COMPOSITE: given NN, is it composite? A factor 1<d<N1 < d < N is the certificate; division checks it in polynomial time.

All of these except COMPOSITE are NP-complete. COMPOSITE is in fact in P (by AKS), and the factoring problem in its decision form — does NN have a factor at most kk? — is in NP∩coNP\mathrm{NP} \cap \mathrm{coNP} (a "no" certificate is the full prime factorisation together with proofs of primality) and is not believed to be NP-complete.

Example— coNP, PSPACE and beyond

  • TAUT — is a propositional formula true under every assignment? — is coNP-complete. A "yes" certificate would be a short proof; whether every tautology has a polynomial-size proof in some proof system is exactly the question NP=coNP\mathrm{NP} = \mathrm{coNP} (Cook and Reckhow, 1979).
  • TQBF — is a fully quantified Boolean formula ∀x1∃x2∀x3⋯φ\forall x_1 \exists x_2 \forall x_3 \cdots \varphi true? — is PSPACE-complete, and so are many two-player games, such as generalised geography (played on a directed graph) and Hex on an n×nn \times n board.
  • Generalised chess on an n×nn \times n board (deciding whether a position is a forced win) is EXPTIME-complete (Fraenkel and Lichtenstein, 1981), so it is provably not in P.

Example— A non-example: a misleading encoding

SUBSET-SUM — given integers a1,…,ama_1, \dots, a_m and tt, does some subset sum to tt? — is NP-complete. Dynamic programming solves it in about m⋅tm \cdot t steps. That is not polynomial time: the input length is about mlog⁡tm \log t bits, and tt is exponential in log⁡t\log t. Such "pseudo-polynomial" algorithms are fast only when the numbers are small.

ProofH

Inclusions, NP-completeness and the hierarchy theorem

The basic inclusions

Theorem— Theorem 1

P⊆NP⊆PSPACE⊆EXPTIME\mathrm{P} \subseteq \mathrm{NP} \subseteq \mathrm{PSPACE} \subseteq \mathrm{EXPTIME}.

Proof

P ⊆ NP. If a polynomial-time machine MM decides LL, take V(x,y)=M(x)V(x, y) = M(x), ignoring the certificate (the empty string will do).

NP ⊆ PSPACE. Let LL have verifier VV, running in time p(n)p(n), and certificates of length at most q(n)q(n). On input xx, go through all strings yy of length at most q(∣x∣)q(|x|) one at a time, in a fixed order, run V(x,y)V(x, y), and accept if some run accepts. The current yy takes q(n)q(n) cells; each run of VV uses at most p(n)p(n) cells (a machine cannot visit more cells than it takes steps), and the space is reused for the next yy. Total space O(p(n)+q(n))O(p(n) + q(n)), a polynomial — although the time is exponential.

PSPACE ⊆ EXPTIME. A machine with state set QQ and tape alphabet Γ\Gamma that uses at most s(n)s(n) cells has at most ∣Q∣⋅s(n)⋅∣Γ∣s(n)=2O(s(n))|Q| \cdot s(n) \cdot |\Gamma|^{s(n)} = 2^{O(s(n))} distinct configurations (state, head position, tape contents). A deterministic machine that halts never repeats a configuration — otherwise it would cycle forever — so it halts within 2O(s(n))2^{O(s(n))} steps. For polynomial ss this is 2O(nk)2^{O(n^k)}.

∎

At least one of these three inclusions is strict, because P≠EXPTIME\mathrm{P} \ne \mathrm{EXPTIME} (Theorem 7 below) — but no one knows which.

Reductions

Theorem— Lemma 2

(a) If A≤pBA \le_p B and B≤pCB \le_p C then A≤pCA \le_p C. (b) If A≤pBA \le_p B and B∈PB \in \mathrm{P} then A∈PA \in \mathrm{P}. (c) If AA is NP-complete, B∈NPB \in \mathrm{NP} and A≤pBA \le_p B, then BB is NP-complete.

Proof

(a) Compose the reductions: if ff runs in time p(n)p(n) and gg in time q(n)q(n), then f(x)f(x) has length at most p(∣x∣)p(|x|), so g(f(x))g(f(x)) is computed in time p(n)+q(p(n))p(n) + q(p(n)), a polynomial. (b) Decide x∈Ax \in A by computing f(x)f(x) and running the polynomial-time algorithm for BB; the same estimate applies. (c) Every L∈NPL \in \mathrm{NP} satisfies L≤pAL \le_p A, hence L≤pBL \le_p B by (a).

∎

Part (c) is how NP-completeness spreads: once one NP-complete problem is known, each new one needs only a single reduction. The first one requires a direct argument.

Theorem— Theorem 3 (Cook 1971, Levin 1973)

SAT is NP-complete.

Proof

Sketch. SAT is in NP: the certificate is an assignment. Now let L∈NPL \in \mathrm{NP}, with verifier VV running in time T=p(n)T = p(n) and certificates of length q(n)q(n). Given xx of length nn, we build in polynomial time a CNF formula φx\varphi_x that is satisfiable iff some certificate makes VV accept xx.

The computation of VV on (x,y)(x, y) is a tableau: a (T+1)×(2T+1)(T + 1) \times (2T + 1) grid whose row tt is the configuration at time tt (only cells within distance TT of the start can be visited). Introduce Boolean variables saying "at time tt, cell ii holds symbol ss", "at time tt the head is at cell ii", "at time tt the state is qq" — O(T2)O(T^2) variables in all, since QQ and Γ\Gamma are fixed. Add clauses expressing:

  1. each cell holds exactly one symbol, there is exactly one head position and exactly one state, at each time;
  2. row 00 is the start configuration with xx written in place, while the certificate cells are left free — the variables for those cells are the certificate;
  3. each row follows from the previous one by δ\delta. The key point is locality: in one step only the cell under the head changes and the head moves one cell, so it suffices to require that every 2×32 \times 3 window of the tableau is consistent with δ\delta — a constant number of clauses of constant size per window;
  4. the state at time TT is accepting (after halting, a machine is taken to repeat its final configuration).

There are O(T2)O(T^2) clauses of bounded size, written down in polynomial time. A satisfying assignment describes an accepting computation of VV on (x,y)(x, y) for the certificate yy read off row 00; conversely an accepting computation gives a satisfying assignment. So x∈L  ⟺  φx∈SATx \in L \iff \varphi_x \in \mathrm{SAT}.

∎

Theorem— Theorem 4

SAT≤p3-SAT\mathrm{SAT} \le_p \mathrm{3\text{-}SAT}, so 3-SAT is NP-complete.

Proof

Replace each clause by an equisatisfiable set of 3-literal clauses, using fresh variables for each clause. A clause (ℓ1∨⋯∨ℓk)(\ell_1 \vee \cdots \vee \ell_k) with k≥4k \ge 4 becomes

(ℓ1∨ℓ2∨z1)∧(¬z1∨ℓ3∨z2)∧(¬z2∨ℓ4∨z3)∧⋯∧(¬zk−3∨ℓk−1∨ℓk),(\ell_1 \vee \ell_2 \vee z_1) \wedge (\neg z_1 \vee \ell_3 \vee z_2) \wedge (\neg z_2 \vee \ell_4 \vee z_3) \wedge \cdots \wedge (\neg z_{k-3} \vee \ell_{k-1} \vee \ell_k),

k−2k - 2 clauses. If some ℓj\ell_j is true, set ziz_i true for i≤j−2i \le j - 2 and false for i≥j−1i \ge j - 1: every new clause is satisfied. If all ℓj\ell_j are false, the chain forces z1z_1 true, then z2z_2 true, …, then zk−3z_{k-3} true, and the last clause fails. Short clauses are padded: (ℓ1∨ℓ2)(\ell_1 \vee \ell_2) becomes (ℓ1∨ℓ2∨w)∧(ℓ1∨ℓ2∨¬w)(\ell_1 \vee \ell_2 \vee w) \wedge (\ell_1 \vee \ell_2 \vee \neg w), and (ℓ1)(\ell_1) becomes four clauses with two fresh variables. The new formula is satisfiable iff the old one is, and it is built in linear time. 3-SAT is in NP, so Lemma 2(c) applies.

∎

Theorem— Theorem 5 (Karp 1972: 3-SAT reduces to INDEPENDENT SET)

Given a 3-CNF formula φ\varphi with clauses C1,…,CmC_1, \dots, C_m, let GφG_\varphi have a vertex (i,j)(i, j) for the jj-th literal of CiC_i (1≤j≤31 \le j \le 3), with an edge between two vertices of the same clause, and an edge between any two vertices labelled by complementary literals xx and ¬x\neg x. Then φ\varphi is satisfiable if and only if GφG_\varphi has an independent set of size mm. Hence INDEPENDENT SET is NP-complete.

Proof

(⇒\Rightarrow) Fix a satisfying assignment and choose, in each clause, one literal that it makes true. The mm chosen vertices lie in different clauses, so no triangle edge joins two of them; and two of them cannot be complementary, since both are true. So they form an independent set of size mm.

(⇐\Leftarrow) Let SS be independent with ∣S∣=m|S| = m. Each clause's three vertices are pairwise adjacent, so SS contains at most one of them; since there are mm clauses and ∣S∣=m|S| = m, it contains exactly one vertex from each clause. No two literals in SS are complementary (they would be adjacent), so we may set every literal in SS to true consistently, and give the remaining variables any values. Each clause then contains a true literal, so φ\varphi is satisfied.

The graph has 3m3m vertices and fewer than (3m)2(3m)^2 edges, and is built in polynomial time; INDEPENDENT SET is in NP (the certificate is the set). By Theorem 4 and Lemma 2(c), it is NP-complete.

∎

Theorem— Corollary 6

VERTEX COVER and CLIQUE are NP-complete.

Proof

SS is independent in GG iff its complement V∖SV \setminus S is a vertex cover: an edge with no endpoint in V∖SV \setminus S would have both endpoints in SS. So (G,k)↦(G,∣V∣−k)(G, k) \mapsto (G, |V| - k) reduces INDEPENDENT SET to VERTEX COVER. And SS is independent in GG iff it is a clique in the complement graph G‾\overline{G} (same vertices, edges exactly between distinct non-adjacent pairs), so (G,k)↦(G‾,k)(G, k) \mapsto (\overline{G}, k) reduces INDEPENDENT SET to CLIQUE. Both problems are in NP.

∎

Thousands of problems have been shown NP-complete in this way: 3-COL (by a gadget reduction from 3-SAT; Garey, Johnson and Stockmeyer, 1976), HAM-CYCLE and the travelling salesperson problem (Karp, 1972), SUBSET-SUM, scheduling, protein folding in simple lattice models, Sudoku and Minesweeper generalised to n×nn \times n boards. A remarkable dichotomy is visible already among the examples: 2-SAT and 2-COL are in P, while 3-SAT and 3-COL are NP-complete.

The hierarchy theorem

Theorem— Theorem 7 (time hierarchy; Hartmanis and Stearns 1965, sharpened by Hennie and Stearns 1966)

If gg is time-constructible and f(n)log⁡f(n)=o(g(n))f(n) \log f(n) = o(g(n)), then TIME(f)⊊TIME(g)\mathrm{TIME}(f) \subsetneq \mathrm{TIME}(g). In particular P⊊EXPTIME\mathrm{P} \subsetneq \mathrm{EXPTIME}.

Proof

Sketch. ("Time-constructible" means g(n)g(n) can be computed in time O(g(n))O(g(n)), which holds for all the usual functions.) Diagonalise, as in the halting problem, but with a clock. Let DD be the machine that, on input ww, reads ww as the code of a machine MwM_w (allowing arbitrary padding), runs a universal simulation of MwM_w on ww, stops it after g(∣w∣)g(|w|) of its own steps, and outputs the opposite of MwM_w's answer if the simulation finished (and "no" otherwise). DD runs in time O(g)O(g). Suppose some machine MM decided the same problem as DD in time O(f)O(f). A universal machine simulates tt steps of MM in O(tlog⁡t)O(t \log t) steps, with a constant depending only on MM; since flog⁡f=o(g)f \log f = o(g), for long enough padded codes ww of MM the simulation of MM on ww finishes within g(∣w∣)g(|w|) steps, and then D(w)≠M(w)D(w) \ne M(w) — a contradiction.

For the corollary: every polynomial is eventually below 2n2^n, so P⊆TIME(2n)\mathrm{P} \subseteq \mathrm{TIME}(2^n); and 2n⋅n=o(22n)2^n \cdot n = o(2^{2n}), so TIME(2n)⊊TIME(22n)⊆EXPTIME\mathrm{TIME}(2^n) \subsetneq \mathrm{TIME}(2^{2n}) \subseteq \mathrm{EXPTIME}.

∎

So EXPTIME-complete problems, like generalised chess, provably have no polynomial-time algorithm. The same argument cannot separate P from NP, as the next section explains.

ExplanationH

P versus NP: what is known and what is not

Open. Whether P=NP\mathrm{P} = \mathrm{NP}; whether NP=coNP\mathrm{NP} = \mathrm{coNP}; whether P=PSPACE\mathrm{P} = \mathrm{PSPACE}. None of the inclusions in Theorem 1 is known to be strict. Most researchers believe P≠NP\mathrm{P} \ne \mathrm{NP} — decades of effort have found no polynomial algorithm for any NP-complete problem, and P=NP\mathrm{P} = \mathrm{NP} would make finding proofs about as easy as checking them — but belief is not proof.

Known implications. If any NP-complete problem is in P, then P=NP\mathrm{P} = \mathrm{NP} (Lemma 2). If P=NP\mathrm{P} = \mathrm{NP} then NP=coNP\mathrm{NP} = \mathrm{coNP} (P is closed under complement). If P≠NP\mathrm{P} \ne \mathrm{NP}, then there are problems in NP that are neither in P nor NP-complete (Ladner, 1975); factoring and graph isomorphism are the natural candidates, and Babai (2015) showed graph isomorphism is solvable in quasi-polynomial time 2(log⁡n)O(1)2^{(\log n)^{O(1)}}.

Why it is hard. Three "barrier" theorems explain why familiar techniques cannot settle the question. Diagonalisation, as in Theorem 7, relativises — it works equally well for machines with access to any oracle — but Baker, Gill and Solovay (1975) built oracles relative to which P=NP\mathrm{P} = \mathrm{NP} and others relative to which P≠NP\mathrm{P} \ne \mathrm{NP}. Razborov and Rudich (1994) showed that "natural" circuit lower-bound arguments would break widely believed cryptographic assumptions; Aaronson and Wigderson (2008) found a further barrier, algebrisation. The best proven lower bounds for explicit problems in NP are, for general circuits, only linear in nn.

Cryptography. Public-key cryptography needs functions that are easy to compute and hard to invert. If P=NP\mathrm{P} = \mathrm{NP}, no such one-way functions exist, since inverting is an NP search problem. Conversely, P≠NP\mathrm{P} \ne \mathrm{NP} alone is not known to be enough: cryptography needs problems that are hard on average, for randomly generated keys, not merely in the worst case. So RSA rests on the unproved assumption that factoring products of two random large primes is hard. The general number field sieve factors NN in heuristic time exp⁡(O((log⁡N)1/3(log⁡log⁡N)2/3))\exp\big(O((\log N)^{1/3} (\log \log N)^{2/3})\big), faster than exponential in the number of digits but not polynomial; and Shor's algorithm (1994) factors in polynomial time on a quantum computer, which is why "post-quantum" cryptosystems based on lattice problems are being deployed. See cryptography.

In practice. NP-completeness is a statement about the worst case. Modern SAT solvers routinely decide industrial instances with millions of variables (in hardware and software verification), and SAT solving has settled mathematical questions, such as the Boolean Pythagorean triples problem (Heule, Kullmann and Marek, 2016). Approximation algorithms, parameterised algorithms and heuristics make many NP-hard optimisation problems tractable for the instances that arise.

Formal mathematicsF

Satisfiability and reductions in Lean

Lean functions come without a cost model, so this module formalises what complexity theory says about correctness — verifiers, search spaces and reductions — and leaves running times to the prose. (Mathlib does contain a Turing-machine notion of polynomial-time computability, Turing.TM2ComputableInPolyTime; the Cook–Levin theorem has been machine-checked in Coq, by Gäher and Kunze in 2021, and in Isabelle, by Balbach in 2023, but not in Mathlib.)

The verifier. A CNF formula over nn variables is a list of clauses, each a list of literals; evaluating it under an assignment is the polynomial-time verifier for SAT, and satisfiability is "some certificate passes":

evalLean 4Formally verified
/-- **The verifier for SAT.** A formula is true when every clause is true. Checking a proposed
assignment (a *certificate*) is a single pass over the formula: linear time in its length. -/
def CNF.eval {n : ℕ} (a : Assignment n) (φ : CNF n) : Bool :=
  φ.all (Clause.eval a)
Axioms used: No axiomsformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor
SatisfiableLean 4Formally verified
/-- A formula is satisfiable if some assignment makes it true — "some certificate passes the
verifier". This is the shape of every problem in NP. -/
def Satisfiable {n : ℕ} (φ : CNF n) : Prop :=
  ∃ a : Assignment n, φ.eval a = true
Axioms used: No axiomsformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

Brute force runs the verifier on every assignment. The search space has exactly 2n2^n elements, and the algorithm is correct — so SAT is decidable, and the whole question is speed:

length_allAssignmentsLean 4Proved here from basic factsFormally verified
/-- **The search space doubles with every variable**: there are exactly `2 ^ n` assignments. -/
theorem length_allAssignments (n : ℕ) : (allAssignments n).length = 2 ^ n := by
  induction n with
  | zero => rfl
  | succ n ih =>
    simp only [allAssignments, List.length_flatMap, List.length_cons, List.length_nil]
    rw [List.map_const', List.sum_replicate, ih, smul_eq_mul, pow_succ, Nat.mul_comm]
Axioms used: propext, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor
bruteForceLean 4Formally verified
/-- The exponential-time algorithm: run the verifier on all `2 ^ n` assignments. -/
def bruteForce {n : ℕ} (φ : CNF n) : Bool :=
  (allAssignments n).any fun a => φ.eval a
Axioms used: propextformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor
bruteForce_correctLean 4Proved here from basic factsFormally verified
/-- **Brute force is correct**: it answers `true` exactly for the satisfiable formulas.
So SAT is decidable; the open question is whether it can be decided in polynomial time. -/
theorem bruteForce_correct {n : ℕ} (φ : CNF n) : bruteForce φ = true ↔ Satisfiable φ := by
  unfold bruteForce Satisfiable
  rw [List.any_eq_true]
  constructor
  · rintro ⟨a, -, ha⟩
    exact ⟨a, ha⟩
  · rintro ⟨a, ha⟩
    exact ⟨a, mem_allAssignments n a, ha⟩
Axioms used: propext, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

Running the algorithm proves that three pigeons do not fit in two holes. The pigeonhole formulas are a classic of proof complexity: Haken (1985) showed that resolution — the proof system underlying most SAT solvers — needs proofs of exponential length for them.

php32_unsatLean 4Proved here from basic factsFormally verified
/-- Three pigeons do not fit into two holes: PHP(3, 2) is unsatisfiable. The proof *runs* the
brute-force algorithm (64 assignments, checked by `decide`) and then uses its correctness.
For PHP(n + 1, n) the same method needs `2 ^ (n (n + 1))` assignments, and Haken (1985) proved
that even resolution proofs of these formulas must be exponentially long. -/
theorem php32_unsat : ¬ Satisfiable php32 := by
  rw [← bruteForce_correct]
  decide
Axioms used: propext, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

Reductions. Corollary 6, formalised for Mathlib's SimpleGraph. First the complementation fact, proved here and then quoted from Mathlib:

indepSet_iff_vertexCover_complLean 4Proved here from basic factsFormally verified
/-- **Independent sets and vertex covers are complements.** `s` contains no edge if and only if
every edge has an endpoint outside `s`. -/
theorem indepSet_iff_vertexCover_compl {V : Type*} (G : SimpleGraph V) (s : Set V) :
    G.IsIndepSet s ↔ G.IsVertexCover sᶜ := by
  constructor
  · intro hs v w hadj
    by_contra h
    simp only [Set.mem_compl_iff, not_or, not_not] at h
    exact hs h.1 h.2 hadj.ne hadj
  · intro hc v hv w hw _ hadj
    rcases hc hadj with h | h
    · exact h hv
    · exact h hw
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor
indepSet_iff_vertexCover_compl'Lean 4Applies a library theoremFormally verified
/-- The same fact from Mathlib: `SimpleGraph.isVertexCover_compl`. -/
theorem indepSet_iff_vertexCover_compl' {V : Type*} (G : SimpleGraph V) (s : Set V) :
    G.IsIndepSet s ↔ G.IsVertexCover sᶜ :=
  SimpleGraph.isVertexCover_compl.symm
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

then the reduction with its bookkeeping of sizes, and the passage to the complement graph:

indep_reduces_to_coverLean 4Proved here from basic factsFormally verified
/-- **The reduction INDEPENDENT-SET ≤ₚ VERTEX-COVER.** A graph on `n` vertices has an
independent set of size at least `k` if and only if it has a vertex cover of size at most
`n - k`. The reduction maps `(G, k)` to `(G, n - k)` — clearly polynomial time. -/
theorem indep_reduces_to_cover {V : Type*} [Fintype V] [DecidableEq V] (G : SimpleGraph V)
    (k : ℕ) (hk : k ≤ Fintype.card V) :
    (∃ s : Finset V, k ≤ s.card ∧ G.IsIndepSet (s : Set V)) ↔
      (∃ c : Finset V, c.card ≤ Fintype.card V - k ∧ G.IsVertexCover (c : Set V)) := by
  constructor
  · rintro ⟨s, hks, hs⟩
    refine ⟨sᶜ, ?_, ?_⟩
    · rw [Finset.card_compl]
      omega
    · rw [Finset.coe_compl]
      exact (indepSet_iff_vertexCover_compl G s).1 hs
  · rintro ⟨c, hc, hcov⟩
    refine ⟨cᶜ, ?_, ?_⟩
    · rw [Finset.card_compl]
      omega
    · rw [Finset.coe_compl, indepSet_iff_vertexCover_compl, compl_compl]
      exact hcov
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor
clique_iff_indepSet_complLean 4Proved here from basic factsFormally verified
/-- **The reduction CLIQUE ≤ₚ INDEPENDENT-SET**: `s` is a clique in `G` iff it is independent in
the complement graph `Gᶜ`, whose edges join distinct non-adjacent vertices. -/
theorem clique_iff_indepSet_compl {V : Type*} (G : SimpleGraph V) (s : Set V) :
    G.IsClique s ↔ Gᶜ.IsIndepSet s := by
  constructor
  · intro hs v hv w hw hne hadj
    exact ((SimpleGraph.compl_adj G v w).1 hadj).2 (hs hv hw hne)
  · intro hs v hv w hw hne
    by_contra hnot
    exact hs hv hw hne ((SimpleGraph.compl_adj G v w).2 ⟨hne, hnot⟩)
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

Finally a certificate of non-colourability, for a graph where a short one exists. For general graphs no short certificates of non-3-colourability are expected: that would put an NP-complete problem in coNP, and so make NP=coNP\mathrm{NP} = \mathrm{coNP}.

k4_not_three_colorableLean 4Applies a library theoremFormally verified
/-- `K₄` is not 3-colourable: a clique of size 4 needs 4 colours. This applies Mathlib's
`SimpleGraph.IsClique.card_le_of_colorable` to the whole vertex set. Deciding 3-colourability
of an arbitrary graph is NP-complete, so no short general argument like this one is expected. -/
theorem k4_not_three_colorable : ¬ (⊤ : SimpleGraph (Fin 4)).Colorable 3 := by
  intro h
  have hclique :
      (⊤ : SimpleGraph (Fin 4)).IsClique ((Finset.univ : Finset (Fin 4)) : Set (Fin 4)) := by
    rw [Finset.coe_univ]
    exact SimpleGraph.isClique_univ.2 rfl
  have h43 := hclique.card_le_of_colorable h
  simp at h43
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ComplexityTheory.leanTry it in the Lean web editor

What is not formalised here: running times and the classes P and NP themselves, Cook–Levin, the reduction from 3-SAT to INDEPENDENT SET (tested in Python below instead), and the hierarchy theorem.

ComputationC

Testing a reduction, and looking for hard instances

The first program implements Karp's reduction from Theorem 5. It translates certificates in both directions on an example, then decides 200 formulas twice — by trying all assignments, and by a generic branch-and-bound search for an independent set in the reduction graph — and compares the answers.

Karp's reduction from 3-SAT to independent set, testedPythonExact computationExperimental evidenceComputationally tested
"""Testing Karp's reduction 3-SAT -> INDEPENDENT SET on many formulas.

The reduction turns a 3-CNF formula with m clauses into a graph: one vertex
for each literal occurrence, a triangle on the three vertices of each clause,
and an edge between every two occurrences of complementary literals x, -x.
The theorem in the lesson says: the formula is satisfiable if and only if
the graph has an independent set of size m.

We test this on 200 small formulas, deciding each side by its own exhaustive
search (all assignments; a generic branch-and-bound search for independent
sets). We also translate certificates both ways on an example. Agreement on
these formulas is evidence that the reduction (and our code) is right; the
proof in the lesson is what shows it for every formula.
"""

import itertools
import random

def random_3cnf(n, m, rng):
    """m random clauses, each on 3 distinct variables of 1..n with random signs."""
    formula = []
    for _ in range(m):
        variables = rng.sample(range(1, n + 1), 3)
        formula.append(tuple(v if rng.random() < 0.5 else -v for v in variables))
    return formula

def unsat_core(a, b, c):
    """All 8 clauses on the variables a, b, c: unsatisfiable on its own."""
    return [(sa * a, sb * b, sc * c)
            for sa in (1, -1) for sb in (1, -1) for sc in (1, -1)]

def satisfies(formula, assignment):
    """The verifier: does the assignment (dict var -> bool) satisfy every clause?"""
    return all(any((lit > 0) == assignment[abs(lit)] for lit in clause)
               for clause in formula)

def satisfying_assignment(formula, n):
    """Exhaustive search over all 2^n assignments; return one or None."""
    for bits in itertools.product((False, True), repeat=n):
        assignment = {v: bits[v - 1] for v in range(1, n + 1)}
        if satisfies(formula, assignment):
            return assignment
    return None

def reduction_graph(formula):
    """Vertices (clause, position); return (vertices, neighbour bitmasks)."""
    vertices = [(i, j) for i in range(len(formula)) for j in range(3)]
    masks = [0] * len(vertices)
    for (p, u), (q, v) in itertools.combinations(enumerate(vertices), 2):
        same_clause = u[0] == v[0]
        complementary = formula[u[0]][u[1]] == -formula[v[0]][v[1]]
        if same_clause or complementary:
            masks[p] |= 1 << q
            masks[q] |= 1 << p
    return vertices, masks

def _clique_cover_bound(mask, masks):
    """Greedily split `mask` into cliques: an upper bound for independent sets."""
    count = 0
    while mask:
        low = mask & -mask
        clique, candidates = low, mask & masks[low.bit_length() - 1]
        while candidates:
            w = candidates & -candidates
            clique |= w
            candidates &= masks[w.bit_length() - 1]
        mask &= ~clique
        count += 1
    return count

def independent_set(masks, size):
    """Generic branch and bound: a set of `size` pairwise non-adjacent vertices
    (as a sorted list of indices), or None if there is none."""
    def search(mask, chosen):
        if len(chosen) == size:
            return chosen
        need = size - len(chosen)
        if bin(mask).count("1") < need or _clique_cover_bound(mask, masks) < need:
            return None
        low = mask & -mask
        v = low.bit_length() - 1
        found = search(mask & ~low & ~masks[v], chosen + [v])
        return found if found is not None else search(mask & ~low, chosen)
    return search((1 << len(masks)) - 1, [])

def assignment_to_set(formula, assignment):
    """Satisfying assignment -> independent set: one true literal per clause."""
    picks = []
    for i, clause in enumerate(formula):
        j = next(j for j, lit in enumerate(clause)
                 if (lit > 0) == assignment[abs(lit)])
        picks.append(3 * i + j)
    return picks

def set_to_assignment(formula, picks, n):
    """Independent set of size m -> assignment making every picked literal true."""
    assignment = {v: False for v in range(1, n + 1)}
    for p in picks:
        lit = formula[p // 3][p % 3]
        assignment[abs(lit)] = lit > 0
    return assignment

def is_independent(masks, picks):
    return all(not masks[p] >> q & 1 for p, q in itertools.combinations(picks, 2))

def trial_formulas(count=200, n=4, seed=2024):
    """Seeded test formulas; about 40% contain a planted unsatisfiable core."""
    rng = random.Random(seed)
    formulas = []
    for _ in range(count):
        m = rng.randint(4, 12)
        formula = random_3cnf(n, m, rng)
        if rng.random() < 0.4:
            core = unsat_core(*rng.sample(range(1, n + 1), 3))
            formula = formula[: max(0, m - 8)] + core
            rng.shuffle(formula)
        formulas.append(formula)
    return formulas

def main():
    n = 4
    example = [(1, 2, -3), (-1, 3, 4), (-2, -4, 3)]
    vertices, masks = reduction_graph(example)
    edges = sum(bin(x).count("1") for x in masks) // 2
    print("Example: (x1 v x2 v -x3) & (-x1 v x3 v x4) & (-x2 v -x4 v x3)")
    print(f"  graph: {len(vertices)} vertices, {edges} edges "
          "(3 triangles + complementary pairs)")
    a = satisfying_assignment(example, n)
    picks = assignment_to_set(example, a)
    print(f"  assignment {[int(a[v]) for v in range(1, n + 1)]} -> independent "
          f"set {picks}: independent = {is_independent(masks, picks)}")
    found = independent_set(masks, len(example))
    back = set_to_assignment(example, found, n)
    print(f"  independent set {found} -> assignment "
          f"{[int(back[v]) for v in range(1, n + 1)]}: satisfies = "
          f"{satisfies(example, back)}")
    print()
    rows = []
    for formula in trial_formulas():
        sat = satisfying_assignment(formula, n) is not None
        _, masks = reduction_graph(formula)
        indep = independent_set(masks, len(formula)) is not None
        rows.append((sat, indep, len(formula)))
    agree = sum(s == i for s, i, _ in rows)
    print(f"200 formulas on {n} variables, 4 to 12 clauses:")
    print(f"  satisfiable: {sum(s for s, _, _ in rows)}, "
          f"unsatisfiable: {sum(not s for s, _, _ in rows)}")
    print(f"  'satisfiable' agrees with 'independent set of size m' in "
          f"{agree}/200 cases")
    print(f"  largest graph: {3 * max(m for _, _, m in rows)} vertices")
    print()
    print("Epistemic status: experimental evidence. Both sides were decided by "
          "exact\nexhaustive search, and they agreed on all 200 formulas tried "
          "(not a random\nsample of all formulas: many contain a planted "
          "unsatisfiable core). That\nsupports the reduction theorem and our "
          "implementation of it; only the proof\nin the lesson covers every "
          "formula.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

The second program asks where hard instances live. Random 3-CNF formulas with nn variables and m=rnm = rn clauses are almost always satisfiable when the density rr is small and almost never when it is large; the change happens near r≈4.27r \approx 4.27, a value predicted by statistical physicists (Mézard, Parisi and Zecchina, 2002). A sharp threshold of this kind is a theorem for kk-SAT with kk large (Ding, Sly and Sun, 2015) and remains a conjecture for k=3k = 3. The search effort of a DPLL solver peaks near the threshold.

Random 3-SAT: the satisfiability threshold and the hard regionPythonExperimental evidenceStatistical evidenceComputationally tested
"""Random 3-SAT: the satisfiability threshold and where the hard instances are.

We generate random 3-CNF formulas on n = 20 variables with m = r * n clauses
for several clause densities r, and solve each with a small DPLL solver (unit
propagation plus branching). Two things are measured:

* the fraction of satisfiable formulas, which drops from near 1 to near 0
  around r = 4.3 (a sharp threshold is conjectured for 3-SAT, near 4.27;
  for large k a sharp threshold was proved by Ding, Sly and Sun in 2015);
* the average number of branching nodes, which peaks near the threshold:
  formulas far below it are easy to satisfy, far above it easy to refute.

Every "satisfiable" answer comes with an assignment that we re-check with
the verifier; an "unsatisfiable" answer is trusted only because the search
was exhaustive. That asymmetry is the difference between NP and coNP.
"""

import random

def random_3cnf(n, m, rng):
    """m clauses, each on 3 distinct variables of 1..n with random signs."""
    formula = []
    for _ in range(m):
        variables = rng.sample(range(1, n + 1), 3)
        formula.append(tuple(v if rng.random() < 0.5 else -v for v in variables))
    return formula

def verify(formula, true_literals):
    """The polynomial-time verifier: every clause contains a true literal."""
    return all(any(lit in true_literals for lit in clause) for clause in formula)

def dpll(formula, stats):
    """Return a set of true literals satisfying the formula, or None.

    stats["nodes"] counts the calls (branching nodes of the search tree).
    Variables not mentioned in the returned set may take either value."""
    stats["nodes"] += 1
    assigned = set()
    clauses = [list(c) for c in formula]
    while True:  # unit propagation
        remaining, unit = [], None
        for clause in clauses:
            if any(lit in assigned for lit in clause):
                continue
            rest = [lit for lit in clause if -lit not in assigned]
            if not rest:
                return None
            if len(rest) == 1 and unit is None:
                unit = rest[0]
            remaining.append(rest)
        clauses = remaining
        if unit is None:
            break
        assigned.add(unit)
    if not clauses:
        return assigned
    literal = min(clauses, key=len)[0]
    for choice in (literal, -literal):
        result = dpll(clauses + [[choice]], stats)
        if result is not None:
            return assigned | result
    return None

def complete(true_literals, n):
    """Extend a partial assignment: unmentioned variables become false."""
    full = set(true_literals)
    for v in range(1, n + 1):
        if v not in full and -v not in full:
            full.add(-v)
    return full

def sweep(n=20, ratios=(2, 3, 4, 4.25, 4.5, 5, 6, 8), trials=30, seed=2024):
    """For each ratio return (ratio, m, fraction satisfiable, mean nodes,
    number of certificates that failed verification)."""
    rng = random.Random(seed)
    rows = []
    for r in ratios:
        m = round(r * n)
        sat = nodes = bad = 0
        for _ in range(trials):
            formula = random_3cnf(n, m, rng)
            stats = {"nodes": 0}
            result = dpll(formula, stats)
            nodes += stats["nodes"]
            if result is not None:
                sat += 1
                if not verify(formula, complete(result, n)):
                    bad += 1
        rows.append((r, m, sat / trials, nodes / trials, bad))
    return rows

def main():
    n, trials = 20, 30
    print(f"Random 3-SAT, n = {n} variables, {trials} formulas per density r = m/n")
    print("    r     m   satisfiable   mean DPLL nodes   bad certificates")
    rows = sweep(n=n, trials=trials)
    for r, m, frac, nodes, bad in rows:
        print(f"{r:>5} {m:>5} {frac:>13.2f} {nodes:>17.1f} {bad:>18}")
    hardest = max(rows, key=lambda row: row[3])
    print(f"Hardest density in this run: r = {hardest[0]}. Brute force would "
          f"try 2^{n} = {2 ** n:,}\nassignments for every formula.")
    print()
    print("Epistemic status: statistical evidence from 240 seeded random "
          "formulas at\none small size (n = 20). It illustrates the threshold "
          "and the easy-hard-easy\npattern; it does not locate the threshold "
          "precisely (finite-size effects\nshift it), and it says nothing about "
          "worst-case complexity: P versus NP\nis about all instances, not "
          "typical ones.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Notice the column "bad certificates": every "satisfiable" verdict was confirmed by an independent check of the assignment, whereas every "unsatisfiable" verdict is believed only because the search was complete. Certificates exist for one side but — as far as anyone knows — not for the other. That is the difference between NP and coNP, seen in a spreadsheet.

Explore

Explore: colouring is easy to check

3-colourability is NP-complete. Colour the graphs by hand, ask for a greedy colouring, and then ask for the fewest colours possible — which the widget finds by exhaustive search, feasible only because the graphs are small.

Find a colouring, check a colouring

Checking a proposed colouring takes one look at each edge. Finding the fewest colours is a search.

ABCDEFGHIJ
|V| = 10|E| = 15Click a vertex to change its colour (Delete removes it).

0

colours used

0

clashes

10

uncoloured

Give every vertex a colour so that the two ends of each edge differ.

This graph is not planar (it cannot be drawn without edges crossing), so the Four Colour Theorem does not apply to it.

Things to notice: the widget checks your colouring instantly, edge by edge — that is the verifier. Greedy colouring is fast but carries no guarantee: for a bad order of the vertices it can use far more colours than necessary (on the bipartite "crown" graph, Kn,nK_{n,n} minus a perfect matching, it can be forced to use nn colours), so always compare it with "Fewest possible?". The wheel has an odd rim, so it needs four colours; the Petersen graph, with ten vertices and fifteen edges, needs exactly three.

Exercises

Exercises

1. Computational complexity

Warm-upHuman

Which of these is the definition of the class NP?

Choose one

2. Computational complexity

Warm-upHuman

Which of these problems are known to be in P? (Select all that apply.)

Choose all that apply

3. Computational complexity

Warm-upHuman

Karp's reduction from 3-SAT to INDEPENDENT SET is applied to a 3-CNF formula with 77 clauses on 55 variables. How many vertices does the graph GφG_\varphi have?

4. Computational complexity

CoreHuman

In the reduction from SAT to 3-SAT, a clause (ℓ1∨ℓ2∨⋯∨ℓ7)(\ell_1 \vee \ell_2 \vee \cdots \vee \ell_7) with 77 literals is replaced by a chain of 3-literal clauses using fresh variables z1,z2,…z_1, z_2, \dots. How many 3-literal clauses does it become?

5. Computational complexity

CoreHuman

You want to prove that a new problem B∈NPB \in \mathrm{NP} is NP-complete. Which is enough?

Choose one

6. Computational complexity

StretchHuman

In the proof of Theorem 5 (⇐\Leftarrow direction) we said: an independent set SS of size mm in GφG_\varphi contains exactly one vertex from each clause. Why?

Choose one

7. Computational complexity

StretchHuman

Which of these statements are known theorems (not conjectures)? (Select all that apply.)

Choose all that apply

8. Computational complexity

CoreFormal

The Lean file proves bruteForce_correct : bruteForce φ = true ↔ Satisfiable φ and length_allAssignments : (allAssignments n).length = 2 ^ n. Which conclusion do these two theorems justify?

Choose one

9. Computational complexity

StretchComputational

Implement Karp's reduction. A 3-CNF formula is a list of clauses, each a tuple of three nonzero integers (vv for xvx_v, −v-v for ¬xv\neg x_v). Number the vertices 3i+j3i + j for the jj-th literal (j=0,1,2j = 0, 1, 2) of clause ii. Write karp_graph(formula) returning the set of edges as pairs (p, q) with p < q: the three edges inside each clause, and an edge between every two vertices whose literals are complementary.

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Common misconceptionsH

Common misconceptions

Misconception

"NP stands for 'not polynomial'." It stands for nondeterministic polynomial time. Every problem in P is in NP. Whether NP contains problems that are not in P is precisely the open question.

Misconception

"NP-complete problems cannot be solved in practice." NP-completeness says that no polynomial-time algorithm is known that solves every instance. Industrial SAT solvers handle instances with millions of variables, and many NP-hard optimisation problems have excellent approximation algorithms or are easy on the instances that arise.

Misconception

"Polynomial means fast and exponential means slow." Polynomial time is a robust mathematical idealisation. An n100n^{100} algorithm is useless; the simplex method takes exponential time on specially built examples (Klee and Minty, 1972) yet is one of the most successful algorithms ever. The theory classifies growth rates, not stopwatch times.

Misconception

"To show a problem is hard, reduce it to SAT." That is backwards. A≤pSATA \le_p \mathrm{SAT} shows that AA is no harder than SAT (every problem in NP satisfies it). To show BB is NP-hard, reduce a known NP-hard problem to BB.

Misconception

"A problem in NP has short certificates for both answers." NP asks for short certificates of "yes" only. Short certificates of "no" define coNP. For SAT, a satisfying assignment certifies "yes", but nobody knows a short certificate that a formula is unsatisfiable.

Misconception

"Quantum computers will solve NP-complete problems efficiently." Shor's algorithm factors integers, a problem not believed to be NP-complete. There is no known efficient quantum algorithm for any NP-complete problem, and Grover's search gives only a square-root speed-up over brute force.

ViewpointsH

Many ways of seeing

Logic— Complexity classes as logics

Complexity classes can be defined without machines. Fagin's theorem (1974): a property of finite structures is in NP if and only if it is expressible in existential second-order logic — "there exist relations R1,…,RkR_1, \dots, R_k such that a first-order formula holds". For example, 3-colourability is "there exist three sets R,G,BR, G, B covering the vertices such that no edge has both ends in the same set". On finite ordered structures, the Immerman–Vardi theorem (1982) characterises P as first-order logic with a least-fixed-point operator. So P versus NP becomes a question about the expressive power of logics. On the proof side, Cook and Reckhow (1979) showed that NP=coNP\mathrm{NP} = \mathrm{coNP} if and only if some propositional proof system has polynomial-size proofs of all tautologies; Haken's exponential lower bound for resolution on the pigeonhole formulas was a first step in a long programme. See proof theory and first-order logic.

Geometry— Polytopes and linear programming

Optimisation problems are geometry: maximise a linear function over a convex polytope P={x∈Rd∣Ax≤b}P = \{x \in \R^d \mid Ax \le b\}. The simplex method walks along edges of PP from vertex to vertex; Klee and Minty (1972) deformed the cube so that it visits all 2d2^d vertices, and Spielman and Teng's smoothed analysis (2004) explains why such examples are rare. Khachiyan's ellipsoid method (1979) — shrinking ellipsoids around the feasible region — and Karmarkar's interior-point method (1984) put linear programming in P. For a combinatorial problem one forms the polytope spanned by the solutions: the perfect matchings of a graph span a polytope with an explicit description (Edmonds), whereas for the travelling salesperson problem no tractable description is expected. In fact, Fiorini, Massar, Pokutta, Tiwary and de Wolf (2012) proved unconditionally that the TSP polytope is not the projection of any polytope with polynomially many facets, and Rothvoss (2014) proved the same for the matching polytope — rigorous exponential lower bounds, though for linear-programming formulations, not for all algorithms. See optimisation.

Topology— Certifying knots

Is a given knot diagram a diagram of the unknot? Haken (1961) showed the problem is decidable, using normal surfaces. Hass, Lagarias and Pippenger (1999) showed that it is in NP: if a knot is unknotted, there is a certificate — a normal disc it bounds, encoded compactly — checkable in polynomial time. Lackenby (2021) proved that it is also in coNP: knottedness has short certificates too. So unknot recognition is in NP∩coNP\mathrm{NP} \cap \mathrm{coNP}, where problems are not expected to be NP-complete — and yet no polynomial-time algorithm is known. Related problems are hard: deciding whether a knot in a 3-manifold bounds a surface of genus at most gg is NP-complete (Agol, Hass and Thurston, 2006). Low-dimensional topology has become a rich source of natural problems of intermediate or unknown complexity. See knot theory.

Algebra— Determinant versus permanent

The determinant det⁡(A)=∑σ∈Snsgn(σ)∏iaiσ(i)\det(A) = \sum_{\sigma \in S_n} \mathrm{sgn}(\sigma) \prod_i a_{i\sigma(i)} and the permanent perm(A)=∑σ∈Sn∏iaiσ(i)\mathrm{perm}(A) = \sum_{\sigma \in S_n} \prod_i a_{i\sigma(i)} differ only in signs. Yet the determinant is computed with O(n3)O(n^3) arithmetic operations by Gaussian elimination, while computing the permanent of a 0–1 matrix — which counts the perfect matchings of a bipartite graph — is #P-complete (Valiant, 1979), at least as hard as counting the solutions of any NP problem. Valiant's algebraic version of P versus NP, VP versus VNP, asks whether the permanent can be written as the determinant of a polynomially larger matrix of affine functions; it is open. Mulmuley and Sohoni's geometric complexity theory proposes to attack it with algebraic geometry and representation theory of orbit closures — a research programme, not yet a proof. A related algebraic question: the exponent ω\omega of matrix multiplication satisfies 2≤ω<2.3722 \le \omega < 2.372 by work up to 2024, and whether ω=2\omega = 2 is open. See linear algebra.

Probability— Randomness, proofs and approximation

Randomness is a resource. The class BPP contains problems solvable in polynomial time by algorithms that flip coins and err with probability at most 1/31/3; primality had fast randomised tests (Solovay–Strassen, Miller–Rabin, 1970s) a quarter of a century before AKS made it deterministic, and many believe BPP=P\mathrm{BPP} = \mathrm{P}. The PCP theorem (Arora–Safra; Arora, Lund, Motwani, Sudan and Szegedy, 1992) says every NP statement has a proof that can be checked, with high confidence, by reading only a constant number of randomly chosen bits. Its consequences are sharp: Håstad (2001) proved that, unless P=NP\mathrm{P} = \mathrm{NP}, no polynomial-time algorithm can guarantee to satisfy more than a 7/8+ε7/8 + \varepsilon fraction of the clauses of a satisfiable 3-CNF formula — while a uniformly random assignment satisfies 7/87/8 of them on average, since each clause fails with probability 1/81/8. And random instances, as in the experiment above, have their own probabilistic theory. See probability theory.

Number theory— Primes are easy, factors seem hard

Deciding primality and finding factors look like twin problems, but they have very different complexity. In 2002 Manindra Agrawal and his students Neeraj Kayal and Nitin Saxena at IIT Kanpur proved that PRIMES is in P, using a polynomial identity: for gcd⁡(a,n)=1\gcd(a, n) = 1, nn is prime if and only if (X+a)n≡Xn+a(modn)(X + a)^n \equiv X^n + a \pmod n, checked cleverly modulo Xr−1X^r - 1 for a small rr. Factoring is in NP∩coNP\mathrm{NP} \cap \mathrm{coNP} but no polynomial-time classical algorithm is known, and the security of RSA (Rivest, Shamir and Adleman, 1977; found earlier, in secret, by Clifford Cocks at GCHQ in 1973) depends on that. The discrete logarithm problem in finite fields and on elliptic curves plays the same role for Diffie–Hellman key exchange. Shor's quantum algorithm solves both, by finding the period of x↦ax mod Nx \mapsto a^x \bmod N with a quantum Fourier transform. See elementary number theory.

Physics— Spin glasses and the cavity method

Finding the ground state of an Ising spin glass — spins σi=±1\sigma_i = \pm 1 on a lattice minimising −∑ijJijσiσj-\sum_{ij} J_{ij} \sigma_i \sigma_j with random couplings JijJ_{ij} — is NP-hard for three-dimensional lattices (Barahona, 1982): physics knows NP-hard problems in nature, which is one reason to doubt that physical systems can efficiently solve them. In the other direction, statistical physicists used the non-rigorous cavity method to predict the 3-SAT threshold r≈4.267r \approx 4.267 and a "clustering" of solutions into exponentially many distant groups just below it (Mézard, Parisi and Zecchina, 2002), which inspired the survey-propagation algorithm. Several of these predictions have since been proved for large kk — an instance where physical heuristics guided rigorous mathematics. Quantum computers define the class BQP; it contains factoring, and is believed not to contain the NP-complete problems.

History & global context

History and global context

  • Before the definitions. Mathematicians have long distinguished methods that "work in principle" from methods that work. In the Soviet Union in the 1950s, Sergey Yablonsky and others studied perebor — brute-force search — and asked whether it could be avoided for problems such as minimising Boolean circuits. On 20 March 1956 Kurt Gödel wrote to John von Neumann asking how fast one could decide whether a mathematical statement has a proof of length nn, observing that an algorithm running in linear or quadratic time would have consequences of the greatest importance; the letter came to light only in the late 1980s.
  • Measuring computation (1960s). Michael Rabin and Dana Scott introduced nondeterminism for finite automata (1959). Juris Hartmanis and Richard Stearns defined time complexity classes and proved the hierarchy theorem (1965). Alan Cobham and Jack Edmonds (1965) independently proposed polynomial time as the mathematical notion of feasibility.
  • NP-completeness (1971–73). Stephen Cook, at the University of Toronto, proved that SAT is NP-complete in 1971. Leonid Levin, working in Moscow, independently found the same phenomenon for six "universal search problems" (published 1973); he later emigrated to the United States. Richard Karp (1972) showed that 21 well-known combinatorial problems are NP-complete, revealing how widespread the phenomenon is. Michael Garey and David Johnson's 1979 book catalogued hundreds more.
  • Barriers and breakthroughs. Baker, Gill and Solovay found the relativisation barrier (1975); Leonid Khachiyan put linear programming in P (1979); Alexander Razborov proved exponential lower bounds for monotone circuits (1985) and, with Steven Rudich, the natural proofs barrier (1994); the PCP theorem was proved in 1992; Agrawal, Kayal and Saxena put primality in P (2002).
  • A prize. In 2000 the Clay Mathematics Institute listed P versus NP among its seven Millennium Prize Problems, with a prize of one million US dollars for a solution.
Further reading

Further reading

  • Sanjeev Arora and Boaz Barak, Computational Complexity: A Modern Approach (2009) — the standard graduate text, from the basics to PCP and beyond.
  • Michael Sipser, Introduction to the Theory of Computation (1996; 3rd edition 2012) — Part Three is an excellent first course.
  • Michael R. Garey and David S. Johnson, Computers and Intractability: A Guide to the Theory of NP-Completeness (1979) — the classic guide, with a catalogue of NP-complete problems.
  • Christos H. Papadimitriou, Computational Complexity (1994).
  • Cristopher Moore and Stephan Mertens, The Nature of Computation (2011) — wide-ranging, with physics and random instances.
  • Avi Wigderson, Mathematics and Computation (2019) — a survey of the field's connections to the rest of mathematics.
  • Lance Fortnow, The Golden Ticket: P, NP, and the Search for the Impossible (2013) — a non-technical introduction.

Next steps in the Lumenary: proof theory (proof complexity), cryptography, and graph theory, the home of many NP-complete problems.