15 — Reductions, P, NP & NP-Completeness
From "what can be computed?" to "what can be computed efficiently?"
The Big Picture

On the previous page, we drew hard lines: some problems are undecidable, period. We proved this using reductions — showing that if you could solve problem B, you could solve the known-undecidable problem A. But those reductions had no concern for efficiency. A computable function that takes a trillion years is still a valid mapping reduction.

Now we shift the question. We're no longer asking "can this be computed at all?" — we're asking "can this be computed efficiently?" This is the transition from computability theory to complexity theory. And the tool that lets us compare efficiency between problems is the polynomial-time reduction.

But first, we need to formalize what we already used informally on the undecidability page: mapping reductions. Then we'll define "efficient" (the class P), define "efficiently verifiable" (the class NP), and build the machinery to ask the most famous open question in computer science: is P = NP?

The SWE analogy. Think of this page as the architecture of problem difficulty. Mapping reductions are like saying "if you have a library that solves X, you can solve Y with a wrapper function." Polynomial-time reductions add the constraint: "...and the wrapper must be fast." NP-completeness is: "this one problem is so universal that every problem in a huge class can be wrapper-adapted to it." It's the ultimate base service — solve SAT, solve everything.
Mapping Reductions (\(\leq_m\))

Before we get to efficiency, let's formalize the reduction technique we used throughout the undecidability chapter. Every proof there had the same shape: "assume B is decidable, use it to decide the known-undecidable A, contradiction." The tool that connects A to B is a mapping reduction.

The "Disguise" Intuition

Imagine you have a nightclub (problem B) with a bouncer who perfectly decides who gets in. You want to figure out whether someone is on the VIP list for a different club (problem A). If you can disguise any person from A's list so that the B-bouncer accepts them if and only if they'd be accepted at A — then B's bouncer is effectively solving A's problem. The disguise function is the reduction.

Definition. Language \(A\) is mapping reducible to language \(B\), written \(A \leq_m B\), if there exists a computable function \(f: \Sigma^* \to \Sigma^*\) such that for every string \(w\): $$w \in A \iff f(w) \in B$$ The function \(f\) is called the reduction (or reduction function).

The key property: \(f\) must map yes-instances of A to yes-instances of B, AND no-instances of A to no-instances of B. It's a perfect disguise — it never introduces false positives or false negatives.

Direction matters! \(A \leq_m B\) means "A is not harder than B" — you can solve A using B. This is counterintuitive: the arrow points from the easier problem to the harder one. Think of it as "A reduces TO B" — A delegates its work to B. If B is decidable, so is A. If A is undecidable, so is B.

Example: \(A_{\text{TM}} \leq_m \overline{E_{\text{TM}}}\)

Recall from the undecidability page: to prove \(E_{\text{TM}}\) is undecidable, we showed that if we could decide emptiness, we could decide acceptance. Let's restate that as a formal mapping reduction to the complement of \(E_{\text{TM}}\):

Reduction \(A_{\text{TM}} \leq_m \overline{E_{\text{TM}}}\)

Define \(f(\langle M, w \rangle)\) = \(\langle M_1 \rangle\), where \(M_1\) is a TM that does:

\(M_1 = \) "On input \(x\): if \(x \neq w\), reject. If \(x = w\), run \(M\) on \(w\) and accept if \(M\) accepts."

Why this works:

The function \(f\) is computable (it just constructs a TM description), and \(\langle M, w \rangle \in A_{\text{TM}} \iff f(\langle M, w \rangle) \in \overline{E_{\text{TM}}}\). Therefore \(A_{\text{TM}} \leq_m \overline{E_{\text{TM}}}\).

Example: \(\overline{A_{\text{TM}}} \leq_m E_{\text{TM}}\)

The same reduction also gives us \(\overline{A_{\text{TM}}} \leq_m E_{\text{TM}}\) (note the complements flip):

Reduction \(\overline{A_{\text{TM}}} \leq_m E_{\text{TM}}\)

Use the same function \(f(\langle M, w \rangle) = \langle M_1 \rangle\) as above.

The trick with complements. If \(A \leq_m B\) via function \(f\), then also \(\overline{A} \leq_m \overline{B}\) via the same \(f\). The "if and only if" flips on both sides simultaneously. This is why one reduction function can prove multiple results.
Properties of Mapping Reductions

Mapping reductions transfer decidability and recognizability downward (from the target to the source), and undecidability upward (from the source to the target):

Theorem. If \(A \leq_m B\), then:
  1. If \(B\) is decidable, then \(A\) is decidable.
  2. If \(B\) is Turing-recognizable, then \(A\) is Turing-recognizable.
  3. If \(A\) is undecidable, then \(B\) is undecidable. (contrapositive of 1)
  4. If \(A\) is not Turing-recognizable, then \(B\) is not Turing-recognizable. (contrapositive of 2)
Proof of (1)

Let \(R\) be a decider for \(B\) and \(f\) the reduction from \(A\) to \(B\). Build a decider for \(A\):

"On input \(w\): compute \(f(w)\), then run \(R\) on \(f(w)\). Accept if \(R\) accepts; reject if \(R\) rejects."

This always halts (both \(f\) and \(R\) are computable/total), and it's correct because \(w \in A \iff f(w) \in B\).

Think of it like a dependency graph in your codebase. If service B has no bugs (decidable), and your adapter function (reduction) works correctly, then service A has no bugs either. Conversely, if service A has a bug (undecidable), and the adapter works correctly, then B must have a bug too — the bug must come from somewhere.

Recognizability transfer uses the same proof. Replace "decider" with "recognizer" — if \(R\) recognizes \(B\) (accepts members, may loop on non-members), then "compute \(f(w)\), run \(R\) on \(f(w)\)" recognizes \(A\). The computation of \(f\) always finishes (it's computable), and then \(R\) accepts iff \(f(w) \in B\) iff \(w \in A\).
Input Encoding & Big-O for Turing Machines

Before we can talk about "efficient" computation, we need to agree on what counts as the size of an input. This seems obvious — but it's the source of the single most common mistake in complexity theory.

Big-O Notation (Formal)

Definition. \(f(n)\) is \(O(g(n))\) if there exist constants \(c > 0\) and \(n_0 \geq 0\) such that for all \(n \geq n_0\): $$f(n) \leq c \cdot g(n)$$ In words: "after some threshold, \(f\) is bounded above by a constant multiple of \(g\)."

The key detail: the bound only needs to hold eventually. We get to choose both the constant \(c\) and the starting point \(n_0\). This means Big-O ignores constant factors and lower-order terms — it captures the growth rate.

Quick check — which is false?

Input Size: Bits, Not Values

When we say "the running time of an algorithm is \(O(n^2)\)," \(n\) is the length of the input encoding — the number of bits (or symbols) needed to write down the input. For most data structures, this is intuitive:

That last one is the trap.

The range(x) Trap

Consider these two Python functions:

# Function A: iterate a list def foo(xs: list[int]) -> int: s = 0 for x in xs: s += x return s # Function B: iterate range(x) def bar(x: int) -> int: s = 0 for i in range(x): s += i return s

Function A runs in \(O(n)\) time where \(n\) is the input size — the loop count is proportional to the number of list elements, which is proportional to the encoding size.

Function B also looks like it runs in \(O(x)\) time. But \(x\) is not the input size! The integer \(x\) is encoded in \(n = O(\log x)\) bits. So \(x = 2^{O(n)}\), and the loop runs \(O(2^n)\) timesexponential in the input size.

This is the single most important insight about complexity. Adding one more bit to the binary encoding of \(x\) doubles the number of loop iterations. A function that counts up to \(x\) is not polynomial-time — it's exponential in the number of bits encoding \(x\). This exact trap is why the Knapsack DP algorithm is pseudo-polynomial, not truly polynomial.

Pseudo-Polynomial Time — The Knapsack Example

The classic Knapsack problem has a well-known \(O(|S| \cdot W)\) dynamic programming solution. Is Knapsack in P?

No — at least, this algorithm doesn't prove it. The weight capacity \(W\) is a number, encoded in \(\log W\) bits. The algorithm runs \(W\) iterations, which is \(2^{\log W}\) — exponential in the encoding length. An algorithm that is polynomial in the numeric value of the input but exponential in the encoding length is called pseudo-polynomial.

What pseudo-polynomial tells you: Nothing about P membership. An \(O(|S|W)\) algorithm proves that Knapsack can be solved if \(W\) is small, but it doesn't tell us anything about the worst case where \(W\) is astronomically large relative to its encoding size.
The Class P

Now we can define "efficient computation." The idea: a problem is "tractable" if a deterministic Turing machine can solve it in time bounded by a polynomial in the input size.

Definition. \(\text{TIME}(t(n))\) is the class of languages decidable by a deterministic single-tape TM in \(O(t(n))\) steps, where \(n\) is the input length. $$P = \bigcup_{k \geq 0} \text{TIME}(n^k)$$ In words: P is the set of all languages decidable in polynomial time by a deterministic TM.

P captures the problems we consider "tractable" in practice. Why polynomial? Because polynomial-time algorithms compose polynomially (we'll use this later), and because the Strong Church-Turing Thesis says: anything your laptop solves in polynomial time, a TM can solve in polynomial time too.

P does not mean "linear time." A problem in \(\text{TIME}(n^{100})\) is in P. The constant and degree can be huge. In practice, most problems in P have small polynomial bounds, but the class itself makes no such restriction. Don't confuse P with \(\text{TIME}(n)\).

Examples in P

Problem Algorithm Running time
\(A_{\text{DFA}}\) — does DFA \(B\) accept \(w\)? Simulate \(B\) on \(w\) \(O(|w|)\) on a real computer; polynomial on a single-tape TM
PATH — is there a path from \(s\) to \(t\) in graph \(G\)? BFS or DFS \(O(|V| + |E|)\)
Shortest Path Dijkstra's algorithm \(O((|V| + |E|) \log |V|)\)
Sorting Merge sort \(O(n \log n)\)
\(\Sigma^*\) (accept everything) Immediately accept \(O(1)\) — in \(\text{TIME}(1) \subseteq P\)
\(\{w \mid w = a^*\}\) (all a's) Scan and check each symbol \(O(n)\) — in \(\text{TIME}(n) \subseteq P\)

Subtle Boundary: Small Changes, Big Consequences

Consider these variations on graph path problems, all on undirected graph \(G = (V, E)\):

Problem In P? Why
Simple path from \(s\) to \(t\) visiting all vertices in alphabetical order Yes Unique candidate path — just check if edges exist. \(O(|V|)\)
Simple path visiting all vertices exactly once (HAMP) No* NP-complete — no known polynomial algorithm
Simple path from \(s\) to \(t\) with weight \(\geq K\) No* Longest path — NP-hard

*Assuming P ≠ NP.

The lesson: When a constraint uniquely determines the answer (alphabetical order → one candidate path), verification suffices and the problem is easy. When the constraint allows exponentially many candidates (\(|V|!\) permutations of vertices), search may be required and the problem may be hard. This "verification vs. search" gap is exactly P vs. NP.
The Class NP

Some problems seem hard to solve but easy to check. Finding a Hamiltonian path in a graph? Nobody knows a fast algorithm. But if someone hands you a path, you can verify it visits every vertex exactly once in linear time. This "easy to check" property defines NP.

Two Equivalent Definitions

Definition (Verifier). A language \(L\) is in NP if there exists a polynomial-time TM \(V\) (the verifier) and a polynomial \(p\) such that: $$L = \{w \mid \exists c \in \Sigma^*, |c| \leq p(|w|), \text{ such that } V \text{ accepts } \langle w, c \rangle\}$$ The string \(c\) is called a certificate (or witness). The verifier checks whether the certificate proves membership.
Definition (NDTM). Equivalently: $$NP = \bigcup_{k \geq 0} \text{NTIME}(n^k)$$ A language is in NP if a nondeterministic TM decides it in polynomial time. The NDTM "guesses" the right path (nondeterministic branching) and verifies it.

The two definitions are equivalent: the NDTM's nondeterministic choices encode the certificate, and the verification is the deterministic check on that branch.

The Kite-Flying Analogy

Consider this paragraph:

"A newspaper is better than a magazine. A seashore is a better place than the street. At first it is better to run than to walk. You may have to try several times. It takes some skill but is easy to learn. Even young children can enjoy it. Once successful, complications are minimal. Birds seldom get too close. Rain, however, soaks in very fast."

Without context, this is incomprehensible. But if I give you the certificate — "kite-flying" — suddenly every sentence makes perfect sense, and you can verify the fit instantly. That's the P vs. NP gap: understanding with a certificate is easy; discovering the answer from scratch may be hard.

Example: HAMP ∈ NP

The Hamiltonian Path problem: given an undirected graph \(G = (V, E)\), is there a simple path that visits every vertex exactly once?

Proof that HAMP ∈ NP (verifier approach)

Certificate: A sequence of vertices \((v_1, v_2, \ldots, v_{|V|})\) — the proposed path.

Verifier \(V\): On input \(\langle G, (v_1, \ldots, v_{|V|}) \rangle\):

  1. Check that the sequence contains exactly \(|V|\) vertices.
  2. Check that every vertex in \(V\) appears exactly once.
  3. Check that for each consecutive pair \((v_i, v_{i+1})\), the edge \(\{v_i, v_{i+1}\} \in E\).
  4. If all checks pass, accept. Otherwise, reject.

Running time: Steps 1–3 are all \(O(|V| + |E|)\). The certificate is polynomial in size (\(|V|\) vertex names). So the verifier runs in polynomial time.

Alternative: HAMP ∈ NP (NDTM approach)

"Nondeterministically pick a starting vertex. Then nondeterministically pick an unvisited neighbor. Repeat until all vertices are visited. If successful, accept."

Each nondeterministic choice is \(O(1)\), we make \(|V|\) choices, and the final verification is \(O(|V|)\). Total: polynomial nondeterministic time.

Example: VERTEX COVER ∈ NP

VERTEX COVER. Given a graph \(G = (V, E)\) and integer \(K\): is there a subset \(V' \subseteq V\) with \(|V'| \leq K\) such that every edge has at least one endpoint in \(V'\)?

Real-world analogy: You're placing security cameras at intersections. Each camera covers all roads meeting at that intersection. Can you cover every road segment with at most \(K\) cameras?

VERTEX COVER ∈ NP

Certificate: A set of vertices \(V'\) with \(|V'| \leq K\).

Verifier: Check \(|V'| \leq K\). Then for each edge \(\{u,v\} \in E\), check that \(u \in V'\) or \(v \in V'\). All in \(O(|V| + |E|)\).

Example: CLIQUE ∈ NP

CLIQUE. Given a graph \(G = (V, E)\) and integer \(K\): is there a subset \(V' \subseteq V\) with \(|V'| \geq K\) such that every pair of vertices in \(V'\) is connected by an edge? (A complete subgraph of size \(\geq K\).)

Real-world analogy: In a social network, a clique is a group of people who are all mutual friends. Can you find such a group of size at least \(K\)?

CLIQUE ∈ NP

Certificate: A set of vertices \(V'\) with \(|V'| \geq K\).

Verifier: Check \(|V'| \geq K\). Then for each pair \(u, v \in V'\) with \(u \neq v\), check that \(\{u,v\} \in E\). This takes \(O(K^2) \leq O(|V|^2)\) — polynomial.

P vs NP

We know:

┌─────────────────────────────────────────────┐
│                  EXP                          │
│    ┌─────────────────────────────────┐    │
│    │              NP                    │    │
│    │    ┌─────────────────────┐       │    │
│    │    │         P          │       │    │
│    │    │   PATH, A_DFA,    │       │    │
│    │    │   Sorting, ...    │ HAMP? │    │
│    │    └─────────────────────┘       │    │
│    └─────────────────────────────────┘    │
└─────────────────────────────────────────────┘

  P ⊆ NP ⊆ EXP       P ⊂ EXP (strict)
      P = NP ???
The million-dollar question: Is P = NP? Does "easy to check" imply "easy to solve"? Nobody knows. It's one of the seven Millennium Prize Problems ($1M bounty). Most computer scientists believe P ≠ NP, but no proof exists either way. The course assumes P ≠ NP unless stated otherwise.

Why it matters beyond CS:

If P = NP, all of these "verifying is easier" intuitions would be wrong. Every verification procedure could be turned into a construction procedure with only polynomial overhead.

Polynomial-Time (Karp) Reductions (\(\leq_p\))

Mapping reductions (\(\leq_m\)) asked only that the function \(f\) be computable — it could take exponential time or worse. For complexity theory, we need a tighter version: the reduction itself must be efficient.

Definition. Language \(A\) is polynomial-time reducible to language \(B\), written \(A \leq_p B\), if there exists a polynomial-time computable function \(f: \Sigma^* \to \Sigma^*\) such that for every string \(w\): $$w \in A \iff f(w) \in B$$ This is also called a Karp reduction (after Richard Karp, 1972).

Every Karp reduction is a mapping reduction. Not every mapping reduction is a Karp reduction (the function might take too long). Karp reductions are the "polynomial-time mapping reductions" — same structure, stricter time budget.

Running Time Composition

Why do polynomial-time reductions matter? Because polynomial composed with polynomial is still polynomial.

Theorem. If \(A \leq_p B\) with reduction \(f \in O(n^d)\), and \(B\) is solvable in \(O(n^c)\), then \(A\) is solvable in \(O(n^{cd})\).
Proof

Given input \(w\) of size \(n\):

  1. Compute \(f(w)\). This takes \(O(n^d)\) time and produces output of size \(O(n^d)\).
  2. Run the algorithm for \(B\) on \(f(w)\). The input to \(B\) has size \(m = O(n^d)\), so this takes \(O(m^c) = O((n^d)^c) = O(n^{dc})\).

Total: \(O(n^d) + O(n^{dc}) = O(n^{dc})\). A polynomial of a polynomial is still a polynomial.

This is the key property. It means: if \(A \leq_p B\) and \(B \in P\), then \(A \in P\). Polynomial-time solvability transfers downward through Karp reductions, just like decidability transfers through mapping reductions.

Example: DCYCLE \(\leq_p\) DPATH

Two problems on directed graphs:

We have a solver for DPATH (BFS). Can we use it to solve DCYCLE?

Reduction: DCYCLE \(\leq_p\) DPATH

On input \((V, E, s)\), construct \((V', E', s', t')\):

  1. \(V' = V \setminus \{s\} \cup \{s', t'\}\) — replace \(s\) with two copies.
  2. \(E' = E \setminus \{(s, u), (u, s) \mid u \in V\} \cup \{(s', u) \mid (s, u) \in E\} \cup \{(u, t') \mid (u, s) \in E\}\)

All outgoing edges from \(s\) now leave from \(s'\). All incoming edges to \(s\) now arrive at \(t'\).

Correctness: A cycle through \(s\) in \(G\) corresponds to a path from \(s'\) to \(t'\) in \(G'\), and vice versa.

Time: The transformation is \(O(|V| + |E|)\) — linear, clearly polynomial.

Worked example. Graph: \(s \to a \to b \to c \to s\). After reduction: \(s' \to a \to b \to c \to t'\). The cycle \(s \to a \to b \to c \to s\) becomes the path \(s' \to a \to b \to c \to t'\). Yes-instance maps to yes-instance. If no cycle exists, no such path exists either.

What Karp Reductions Are For

Karp reductions classify problems by difficulty:

This is the foundation for NP-completeness: finding the hardest problems in NP.

NP-Completeness

Some problems in NP seem harder than others. HAMP, SAT, CLIQUE, VERTEX COVER — nobody has found polynomial-time algorithms for any of them, despite decades of effort. Are they the same kind of hard? Yes — they are all NP-complete.

NP-Hard and NP-Complete

Definition. A language \(X\) is NP-hard if for every \(Y \in NP\), \(Y \leq_p X\).

In words: every NP problem can be efficiently reduced to \(X\). It's "at least as hard as everything in NP."
Definition. A language \(X\) is NP-complete (NPC) if:
  1. \(X \in NP\), AND
  2. \(X\) is NP-hard.
$$\text{NPC} = NP \cap \text{NP-hard}$$
Critical distinction: NP-hard vs. NP-complete. NP-hard problems don't have to be in NP — or even decidable! \(A_{\text{TM}}\) is NP-hard (every NP problem reduces to it) but it's undecidable. You can't brute-force it. NP-complete means NP-hard AND in NP — the problem sits right at the boundary, as hard as possible while still being verifiable.
          NP-hard (extends beyond decidable!)
     ┌─────────────────────────────────────────
     │
     │  ┌───────── Decidable ────────┐
     │  │  ┌──────── NP ───────┐  │
     │  │  │  ┌───────────┐  │  │
     │  │  │  │     P     │  │  │
     │  │  │  └───────────┘  │  │
     │  │  │       [NPC]       │  │  ← NP ∩ NP-hard
     │  │  └───────────────────┘  │
     │  └─────────────────────────┘
     │
     │  A_TM (undecidable, but NP-hard!)
     │

The Nuclear Theorem

Theorem. If NPC ∩ P ≠ ∅, then P = NP.
Proof

Suppose some problem \(X\) is in both NPC and P. Take any \(A \in NP\).

  1. Since \(X \in NPC\), it is NP-hard, so \(A \leq_p X\).
  2. Since \(X \in P\) and \(A \leq_p X\), then \(A \in P\) (P is closed downward under \(\leq_p\)).
  3. Since \(A\) was arbitrary, \(NP \subseteq P\).
  4. We already know \(P \subseteq NP\), so \(P = NP\). \(\square\)

This is the reason NP-completeness matters. A polynomial-time algorithm for any single NPC problem would collapse the entire P vs NP divide. Conversely, proving any NPC problem has no polynomial algorithm would prove P ≠ NP.

All NPC problems are inter-reducible. If \(A, B \in NPC\), then \(A \leq_p B\) and \(B \leq_p A\). Why? Both are NP-hard, so every NP problem (including each other) reduces to them. This means all NPC problems are "equally hard" — they rise and fall together.
Cook-Levin Theorem: SAT is NP-Complete

The definitions above are useless without an actual NPC problem. How do you prove something is NP-hard — that every NP problem reduces to it? You'd need to handle infinitely many problems. This seems impossible, but Stephen Cook (1971) and Leonid Levin (independently, 1973) found a way.

The SAT Problem

SAT (Boolean Satisfiability).
Instance: A set of Boolean variables \(U = \{u_1, \ldots, u_n\}\) and a set of clauses \(C = \{c_1, \ldots, c_m\}\), where each clause is a disjunction (OR) of literals (a variable or its negation).
Question: Is there a truth assignment \(\tau: U \to \{0, 1\}\) that satisfies every clause?

A SAT instance in conjunctive normal form (CNF) looks like:

$$\underbrace{(u \lor x \lor \neg z)}_{\text{clause 1}} \;\land\; \underbrace{(\neg u \lor \neg y)}_{\text{clause 2}} \;\land\; \underbrace{(u \lor \neg x \lor y \lor \neg z)}_{\text{clause 3}} \;\land\; \underbrace{(\neg u \lor \neg y \lor \neg z)}_{\text{clause 4}}$$
Worked example — SAT verification

Try \(\tau = \{u = 1, x = 1, y = 0, z = 0\}\):

ClauseSubstitutionResult
\(u \lor x \lor \neg z\)\(1 \lor 1 \lor 1\)1
\(\neg u \lor \neg y\)\(0 \lor 1\)1
\(u \lor \neg x \lor y \lor \neg z\)\(1 \lor 0 \lor 0 \lor 1\)1
\(\neg u \lor \neg y \lor \neg z\)\(0 \lor 1 \lor 1\)1

All clauses satisfied. This is a yes-instance of SAT.

3SAT: The Restricted Variant

3SAT. Same as SAT, but every clause has exactly 3 literals. Example: \((x_1 \lor \neg x_2 \lor x_3) \land (\neg x_1 \lor x_2 \lor \neg x_4)\).

3SAT is a special case of SAT (every 3SAT instance is a SAT instance). Despite the restriction, 3SAT is still NP-complete — proven by reducing general SAT to 3SAT (splitting long clauses with fresh variables). 3SAT is the standard starting point for NPC reductions because its fixed clause size simplifies constructions.

3SAT ∈ NP

Certificate: A truth assignment \(\tau: U \to \{0, 1\}\).

Verifier: For each clause (exactly 3 literals), check that at least one literal is true. Each clause check is \(O(1)\); total is \(O(|C|)\). Polynomial.

The Cook-Levin Proof Idea

Theorem (Cook-Levin). SAT is NP-complete.

The proof shows that for any language \(L \in NP\), there is a polynomial-time reduction from \(L\) to SAT. The idea:

  1. Since \(L \in NP\), some NDTM \(M\) decides \(L\) in polynomial time \(p(n)\).
  2. Given input \(w\), construct a Boolean formula \(\phi\) whose variables encode the entire computation of \(M\) on \(w\) — the cell contents, head position, and state at each of the \(p(n)\) time steps.
  3. The clauses of \(\phi\) enforce: (a) valid initial configuration, (b) valid transitions at each step (matching \(M\)'s transition function), (c) acceptance at the final step.
  4. \(\phi\) is satisfiable if and only if \(M\) has an accepting computation on \(w\).
  5. The construction of \(\phi\) from \(\langle M, w \rangle\) takes polynomial time.

The proof is not trivial (Sipser devotes several pages to it), but the core idea is beautiful: any NDTM computation can be "compiled" into a Boolean formula. If you can solve SAT, you can simulate any nondeterministic polynomial-time computation.

The SWE analogy. Think of SAT as a universal constraint solver. The Cook-Levin theorem says: any problem where you can efficiently check solutions can be encoded as a SAT instance. This is why SAT solvers are used in practice for everything from circuit verification to scheduling to package dependency resolution — they're the "universal backend."
Proving NP-Completeness: The Two-Step Recipe

Once you have one NPC problem (SAT, via Cook-Levin), you can prove others NPC without repeating the Cook-Levin construction. The recipe:

To prove \(X\) is NP-complete:
  1. Show \(X \in NP\): Give a certificate and polynomial-time verifier (or an NDTM algorithm).
  2. Show \(X\) is NP-hard: Reduce a known NPC problem to \(X\). That is, show \(Y \leq_p X\) where \(Y\) is already known NPC.

Why does step 2 work? If \(Y\) is NPC, then every NP problem reduces to \(Y\). If \(Y \leq_p X\), then by transitivity every NP problem reduces to \(X\). So \(X\) is NP-hard.

The chain of reductions typically starts from SAT or 3SAT:

Cook-Levin: every NP problem \(\leq_p\) SAT \(\leq_p\) 3SAT \(\leq_p\) VERTEX COVER \(\leq_p\) CLIQUE \(\leq_p\) ...

Each arrow is a Karp reduction. The L15 lecture covers the VC, CLIQUE, and HAMP reductions in detail.

Reduction direction is critical. To prove \(X\) is NP-hard, you reduce FROM a known NPC problem TO \(X\): known-hard \(\leq_p X\). This shows \(X\) is at least as hard as the known-hard problem. Reducing \(X\) to something else shows \(X\) is not harder than that thing — the wrong direction!
Decision-to-Search Reduction

SAT is a decision problem: it answers "yes" or "no." But in practice, you don't just want to know whether a satisfying assignment exists — you want to find one. Can a decision oracle help?

Yes. SAT is self-reducible: if you have a fast SAT decider, you can find a satisfying assignment with only a polynomial number of calls to the decider.

Algorithm: Finding a satisfying assignment using a SAT decider

Given: A SAT decider \(D\) (runs in \(O(1)\) for simplicity) and instance \((U, C)\).

Step 1: Call \(D(U, C)\). If "no," return null (unsatisfiable).

Step 2: For each variable \(u \in U\):

Step 3: Return \(\tau\).

Worked example

Instance: \(U = \{a, b\}\), \(C = \{(a \lor b),\; (\neg a \lor b)\}\).

Step 1: \(D(U, C)\) = yes. Proceed.

Step 2, variable \(a\):

Step 2, variable \(b\):

Result: \(\tau = \{a = 0, b = 1\}\). Verify: \((0 \lor 1) = 1\) ✓, \((1 \lor 1) = 1\) ✓. Correct!

Running time: The loop runs \(|U|\) times. Each iteration does \(O(|C|)\) clause manipulation plus one \(O(1)\) decider call. Total: \(O(|U| \cdot |C|)\) — polynomial. Even if the decider ran in \(O(n^k)\), the total would be \(O(n^{k+1})\) — still polynomial.

Why this matters. Decision-to-search reductions show that the "decision" version of NP problems captures the full difficulty. If you could decide SAT in polynomial time, you could also find solutions in polynomial time. This is why complexity theory focuses on yes/no questions without loss of generality.
Summary: The Hierarchy
Concept Definition Key fact
\(A \leq_m B\) Computable \(f\) with \(w \in A \iff f(w) \in B\) Transfers decidability/recognizability
\(A \leq_p B\) Polynomial-time \(f\) with \(w \in A \iff f(w) \in B\) Transfers P membership
P \(\bigcup_k \text{TIME}(n^k)\) Efficiently solvable
NP \(\bigcup_k \text{NTIME}(n^k)\) = poly-time verifiable \(P \subseteq NP \subseteq EXP\)
NP-hard \(\forall Y \in NP: Y \leq_p X\) Can extend beyond Decidable (\(A_{\text{TM}}\)!)
NPC \(NP \cap \text{NP-hard}\) If any NPC problem is in P, then P = NP
SAT Boolean satisfiability First known NPC problem (Cook-Levin)
3SAT SAT with exactly 3 literals per clause Also NPC; standard starting point for reductions
The story arc. Acts I and II asked: "what can be computed?" (decidability). This page transitions to: "what can be computed efficiently?" (complexity). Mapping reductions (\(\leq_m\)) sorted problems into decidable/undecidable. Karp reductions (\(\leq_p\)) sort problems into P/NP/NPC. Same technique, finer resolution. Next up (W8): the actual NPC reductions — proving VERTEX COVER, CLIQUE, and HAMP are NP-complete by constructing Karp reductions from 3SAT.