CMPUT-474 · Lesson 10 of 12 · 34 min

Time complexity: P, NP and NP-completeness

Computability theory separates the solvable from the unsolvable. Complexity theory asks, among solvable problems, which can be solved efficiently. The standard answer, proposed by Cobham and Edmonds in the mid-1960s, is that a problem is efficiently solvable if it can be decided in time bounded by a polynomial in the input length. The class of such problems is P. Many important problems are not known to be in P, but have a different, weaker property: a proposed solution can be checked in polynomial time. These form the class NP. Whether the two classes are equal is the P versus NP question. This lesson defines both classes, proves the Cook–Levin theorem that SAT is NP-complete, and builds a chain of reductions that makes many other problems NP-complete, extending the introduction in CMPUT-204 with full definitions and proofs.

Measuring running time

The running time of a decider M is the function \(t(n)\) giving the maximum number of steps M takes on any input of length n. We use asymptotic notation from CMPUT-204 and write \(\text{TIME}(t(n))\) for the class of languages decided by some single-tape Turing machine in \(O(t(n))\) time. The model matters for exact bounds: the palindrome language is in \(\text{TIME}(n^2)\) on one tape, and in fact needs \(\Omega(n^2)\) there, but takes linear time on two tapes. Lesson 7 showed that all reasonable deterministic models simulate each other with polynomial overhead, so the class \(\text{P} = \bigcup_k \text{TIME}(n^k)\) is the same for all of them. That robustness, together with the fact that polynomials are closed under composition, is why P is the standard formalization of 'efficiently solvable'.

P contains a great deal: graph reachability, shortest paths, minimum spanning trees, matching, linear programming, context-free parsing (lesson 5), primality testing (shown to be in P by Agrawal, Kayal and Saxena in 2002) and much more. Of course an \(n^{100}\) algorithm is not practical, and an exponential algorithm can beat a polynomial one for every input that fits on Earth. But in practice, problems that are shown to be in P usually soon get algorithms of low degree, and problems that resist polynomial algorithms usually resist them because of real combinatorial explosion. The polynomial boundary has proved a remarkably good guide.

The class NP

A verifier for a language A is an algorithm V such that \(A = \{w : V \text{ accepts } \langle w, c \rangle \text{ for some string } c\}\). The string c is a certificate or witness. A polynomial-time verifier runs in time polynomial in \(|w|\), which forces useful certificates to have polynomial length. Definition. NP is the class of languages that have polynomial-time verifiers. Equivalently, NP is the class of languages decided by nondeterministic Turing machines in polynomial time, \(\text{NP} = \bigcup_k \text{NTIME}(n^k)\): the nondeterministic machine guesses the certificate and runs the verifier, and conversely a certificate can record the choices of an accepting branch. The name means 'nondeterministic polynomial time', not 'non-polynomial'.

Examples abound. HAMPATH: does a graph have a path visiting every vertex exactly once? The certificate is the path. SUBSET-SUM: does some subset of given numbers sum to a target? The certificate is the subset. SAT: is a Boolean formula satisfiable? The certificate is a satisfying assignment. CLIQUE: does a graph have k mutually adjacent vertices? The certificate is the set of vertices. Every problem in P is in NP, because a verifier can ignore the certificate and solve the problem directly. The program below implements verifiers for three NP problems. Each is short, obviously polynomial, and needs only the certificate to confirm a yes answer; none of them helps with confirming a no answer, which is the asymmetry that the class coNP, discussed below, captures.

Polynomial-time verifiers for Hamiltonian path, subset sum and satisfiability: checking a proposed certificate is easy
# NP as "quickly checkable": verifiers for three problems, each running in polynomial time.
# A verifier gets the instance and a proposed certificate, and checks it.
def verify_hampath(n, edges, path):
    E = {frozenset(e) for e in edges}
    return (sorted(path) == list(range(n))
            and all(frozenset((path[i], path[i + 1])) in E for i in range(n - 1)))

def verify_subset_sum(nums, target, chosen):
    return len(set(chosen)) == len(chosen) and all(0 <= i < len(nums) for i in chosen) \
        and sum(nums[i] for i in chosen) == target

def verify_sat(clauses, assign):
    return all(any((lit > 0) == assign[abs(lit)] for lit in c) for c in clauses)

edges = [(0, 1), (1, 2), (2, 3), (3, 4), (0, 2), (1, 4)]
print("Hamiltonian path 0-1-2-3-4:", verify_hampath(5, edges, [0, 1, 2, 3, 4]))
print("Hamiltonian path 0-2-1-4-3:", verify_hampath(5, edges, [0, 2, 1, 4, 3]))
print("Not a path      0-3-1-2-4:", verify_hampath(5, edges, [0, 3, 1, 2, 4]))
nums = [34, 4, 12, 5, 2, 27, 18]
print("subset {4, 12, 2, 18} sums to 36:", verify_subset_sum(nums, 36, [1, 2, 4, 6]))
cnf = [(1, -2, 3), (-1, 2), (2, -3), (-1, -2, -3)]
print("x1=T, x2=T, x3=F satisfies the formula:", verify_sat(cnf, {1: True, 2: True, 3: False}))
print("x1=T, x2=T, x3=T satisfies the formula:", verify_sat(cnf, {1: True, 2: True, 3: True}))
Output
Hamiltonian path 0-1-2-3-4: True
Hamiltonian path 0-2-1-4-3: True
Not a path      0-3-1-2-4: False
subset {4, 12, 2, 18} sums to 36: True
x1=T, x2=T, x3=F satisfies the formula: True
x1=T, x2=T, x3=T satisfies the formula: False

Polynomial-time reductions and NP-completeness

Language A is polynomial-time reducible to B, written \(A \le_p B\), if there is a polynomial-time computable function f with \(w \in A \iff f(w) \in B\). This is a mapping reduction with a time bound. If \(A \le_p B\) and \(B \in \text{P}\), then \(A \in \text{P}\), because composing f with a polynomial-time decider for B is still polynomial. A language B is NP-hard if every language in NP reduces to it, and NP-complete if additionally \(B \in \text{NP}\). NP-complete problems are the hardest problems in NP: if any one of them is in P, then \(\text{P} = \text{NP}\), and if any problem in NP is not in P, then no NP-complete problem is in P. Reductions are transitive, so to show a new problem NP-complete, it suffices to show it is in NP and that some known NP-complete problem reduces to it.

Two diagrams side by side. On the left, labeled if P is not equal to NP, a large region NP contains a smaller region P at the bottom and a band NP-complete at the top, with a middle zone containing examples such as graph isomorphism and factoring. A separate overlapping region coNP shares P. On the right, labeled if P equals NP, a single region contains P, NP, coNP and the NP-complete problems.
The two possible worlds: if P differs from NP, the NP-complete problems lie outside P and some problems are in between

The Cook–Levin theorem

Theorem (Cook, 1971; Levin, 1973). SAT is NP-complete. SAT is in NP, because an assignment is a certificate. For hardness, take any language A in NP, decided by a nondeterministic machine N in time \(n^k\). Given an input w of length n, we build in polynomial time a formula \(\phi_w\) that is satisfiable exactly when N has an accepting branch on w. The idea is a tableau: an \(n^k \times n^k\) table whose rows are the configurations of one branch, row i being the configuration at step i. Variables \(x_{i,j,s}\) say that cell \((i, j)\) contains symbol s, where symbols include tape symbols and states (configurations are written as strings, as in lesson 7, with the state written just before the head's cell).

The formula is a conjunction of four parts. \(\phi_{\text{cell}}\) says each cell contains exactly one symbol. \(\phi_{\text{start}}\) says the first row is the initial configuration on w. \(\phi_{\text{accept}}\) says some cell contains the accept state. \(\phi_{\text{move}}\) says each row follows from the previous one by a legal move. The key observation is that legality is local: a row follows legally from the one above if and only if every \(2 \times 3\) window of cells is consistent with N's transition function, and each window condition is a fixed-size formula over the variables of six cells. There are \(O(n^{2k})\) cells and windows, so \(\phi_w\) has polynomial size, and it can be written down in polynomial time. A satisfying assignment describes a valid accepting tableau, and an accepting branch gives a satisfying assignment.

The same proof gives more. With small changes the formula is in conjunctive normal form with at most three literals per clause, so 3SAT, satisfiability of such formulas, is also NP-complete. Any long clause \((\ell_1 \lor \ell_2 \lor \cdots \lor \ell_m)\) can be split into three-literal clauses with fresh variables, \((\ell_1 \lor \ell_2 \lor z_1) \land (\overline{z_1} \lor \ell_3 \lor z_2) \land \cdots\), preserving satisfiability. By contrast 2SAT, with two literals per clause, is in P, solvable by finding strongly connected components of an implication graph. The boundary between easy and hard can be very sharp.

A chain of reductions

From 3SAT, reductions spread NP-completeness to thousands of problems. Richard Karp's 1972 paper showed 21 problems NP-complete, and Garey and Johnson's 1979 catalog listed several hundred. The figure shows a few links. Each reduction is a gadget construction that translates the structure of one problem into the other. The reduction from 3SAT to INDEPENDENT-SET is typical. For each clause, create a triangle of three vertices, labeled by the clause's literals; then join every vertex labeled x to every vertex labeled \(\overline{x}\). Ask for an independent set of size k, the number of clauses. An independent set can use at most one vertex per triangle, so it must use exactly one, which selects one true literal per clause, and the contradiction edges ensure the selected literals are consistent. The program below builds the graph for a small formula and checks the correspondence.

A tree of boxes with arrows. At the top, any problem in NP points to SAT, labeled Cook-Levin. SAT points to 3SAT. 3SAT points to INDEPENDENT-SET, to HAMPATH and to SUBSET-SUM. INDEPENDENT-SET points to CLIQUE, labeled complement graph, and to VERTEX-COVER, labeled complement set. HAMPATH points to HAMCYCLE and then to the traveling salesperson problem.
A chain of polynomial-time reductions from SAT that makes each problem in the tree NP-complete
The SAT to INDEPENDENT-SET reduction on a satisfiable and an unsatisfiable formula: independent sets of size k exist exactly when the formula is satisfiable
# The reduction from 3SAT to INDEPENDENT-SET, checked on small formulas.
# Each clause becomes a group of mutually joined literal-vertices; contradictory literals are joined.
# The formula is satisfiable iff the graph has an independent set of size k = number of clauses.
from itertools import combinations, product

def reduce(clauses):
    V = [(ci, pos, lit) for ci, c in enumerate(clauses) for pos, lit in enumerate(c)]
    E = set()
    for u, v in combinations(V, 2):
        if u[0] == v[0] or u[2] == -v[2]:
            E.add(frozenset({u, v}))
    return V, E, len(clauses)

def independent_sets(V, E, k):
    return [S for S in combinations(V, k) if all(frozenset({u, v}) not in E for u, v in combinations(S, 2))]

def satisfying(clauses):
    n = max(abs(l) for c in clauses for l in c)
    return [a for a in product([False, True], repeat=n)
            if all(any((l > 0) == a[abs(l) - 1] for l in c) for c in clauses)]

phi = [(1, 2, -3), (-1, -2, 3), (1, -2, -3), (-1, 2, 3)]
V, E, k = reduce(phi)
sets = independent_sets(V, E, k)
print(f"formula 1: {len(V)} vertices, {len(E)} edges, k = {k}")
print("  independent sets of size k:", len(sets), "  satisfying assignments:", len(satisfying(phi)))
S = sets[0]
assign = {abs(l): l > 0 for _, _, l in S}
print("  one set picks literals", [l for _, _, l in S], "-> assignment", assign)
print("  (several sets can give the same assignment when a clause has two true literals)")

psi = [(1, 2), (1, -2), (-1, 2), (-1, -2)]      # unsatisfiable; clauses of size 2 work the same way
V, E, k = reduce(psi)
print(f"formula 2: {len(V)} vertices, {len(E)} edges, k = {k}")
print("  independent sets of size k:", len(independent_sets(V, E, k)), "  satisfying assignments:", len(satisfying(psi)))
Output
formula 1: 12 vertices, 24 edges, k = 4
  independent sets of size k: 14   satisfying assignments: 4
  one set picks literals [1, -2, 1, 3] -> assignment {1: True, 2: False, 3: True}
  (several sets can give the same assignment when a clause has two true literals)
formula 2: 8 vertices, 12 edges, k = 4
  independent sets of size k: 0   satisfying assignments: 0

The other links are similar in spirit. INDEPENDENT-SET reduces to CLIQUE by complementing the graph, since an independent set in G is a clique in its complement, and to VERTEX-COVER because S is independent exactly when the remaining vertices cover every edge. 3SAT reduces to HAMPATH by building, for each variable, a 'diamond' of vertices that can be traversed left to right (true) or right to left (false), with detours to clause vertices that can be taken only in the direction that makes the clause true. 3SAT reduces to SUBSET-SUM by writing numbers in base 10 with one digit per variable and per clause, so that choosing numbers corresponds to choosing literal values and the target forces each clause digit to be satisfied. HAMPATH reduces to the traveling salesperson problem by giving edges cost 1 and non-edges cost 2.

coNP and the shape of the landscape

The class coNP contains the complements of NP languages: problems whose no answers have short certificates. TAUTOLOGY, whether a formula is true under every assignment, is coNP-complete; a falsifying assignment certifies a no answer, but no short certificate for a yes answer is known. Clearly \(\text{P} \subseteq \text{NP} \cap \text{coNP}\), and it is widely believed that \(\text{NP} \ne \text{coNP}\), which would imply \(\text{P} \ne \text{NP}\). Some problems lie in \(\text{NP} \cap \text{coNP}\) without being known to be in P: factoring (in its decision form) is one, because a factorization certifies both yes and no answers when combined with primality certificates. If \(\text{P} \ne \text{NP}\), Ladner's theorem (1975) guarantees problems in NP that are neither in P nor NP-complete; factoring and graph isomorphism are natural candidates. In 2015 László Babai announced a quasi-polynomial algorithm for graph isomorphism, placing it very close to P.

Pseudo-polynomial time

SUBSET-SUM is NP-complete, yet a simple dynamic program solves it in time \(O(nT)\) for n numbers and target T. There is no contradiction. The input encodes T in binary using about \(\log_2 T\) bits, so a running time polynomial in T is exponential in the input length. Such algorithms are called pseudo-polynomial, and problems that become easy when numbers are written in unary, like SUBSET-SUM and KNAPSACK, are called weakly NP-complete. Problems that remain NP-complete even with numbers bounded by a polynomial in the input length, such as 3-PARTITION and bin packing, are strongly NP-complete. The program below compares the work done by brute force and by the dynamic program. While the numbers are at most 1,000 the dynamic program does far less work; when the numbers grow to about \(2^n\), the number of distinct reachable sums explodes and the advantage shrinks, as the theory predicts. This is the encoding issue first raised in lesson 1, with real consequences.

Subset sum by brute force and by pseudo-polynomial dynamic programming: the DP is fast because the target is small, not because the problem is easy
# SUBSET-SUM: exponential brute force versus pseudo-polynomial dynamic programming.
# The DP does O(n * T) work: polynomial in the VALUE of T but exponential in its number of bits.
import random
from itertools import combinations
random.seed(474)

def brute(nums, T):
    # worst case: examine every subset (an early exit only helps on lucky yes-instances)
    examined, found = 0, False
    for r in range(len(nums) + 1):
        for c in combinations(nums, r):
            examined += 1
            found = found or sum(c) == T
    return found, examined

def dp(nums, T):
    reach = {0}; work = 0
    for x in nums:
        work += len(reach)
        reach |= {s + x for s in reach if s + x <= T}
    return T in reach, work

print(" n   max value        T   answer   subsets examined   DP work")
for n, top in ((12, 1000), (18, 1000), (22, 1000), (16, 2 ** 16), (20, 2 ** 20)):
    nums = [random.randint(1, top) for _ in range(n)]
    T = sum(nums) // 2 + 1
    (b, ex), (d, w) = brute(nums, T), dp(nums, T)
    assert b == d
    print(f"{n:2d}  {top:9d}  {T:9d}   {b!s:6s} {ex:17d}   {w:9d}")
Output
 n   max value        T   answer   subsets examined   DP work
12       1000       3230   True                4096        2579
18       1000       3731   True              262144       21129
22       1000       5385   True             4194304       44568
16      65536     287017   False              65536       44141
20    1048576    5172851   False            1048576      665137

Living with NP-completeness

Proving a problem NP-complete is not the end of the story; it changes the question. If P differs from NP, there is no algorithm that is always fast and always exact. So one can give up 'always fast' and use exponential algorithms that are fast on the instances that occur, such as branch and bound, integer programming solvers or SAT solvers; give up 'always exact' and use approximation algorithms with proven guarantees or heuristics without them; or give up generality and exploit special structure, since many NP-complete problems are easy on trees, planar graphs or bounded parameters. The program below compares brute-force search with DPLL, the backtracking procedure with unit propagation at the core of modern SAT solvers, on random formulas near the hardest ratio of clauses to variables. Lesson 12 returns to why SAT solvers succeed on enormous industrial instances.

Brute force versus DPLL on random 3-CNF formulas near the threshold ratio 4.26: unit propagation and backtracking explore far fewer assignments
# Brute force versus DPLL (unit propagation and branching) on random 3-CNF formulas.
import random
random.seed(474)

def brute(n, clauses):
    tried = 0
    for m in range(2 ** n):
        tried += 1
        a = [(m >> i) & 1 == 1 for i in range(n)]
        if all(any((l > 0) == a[abs(l) - 1] for l in c) for c in clauses): return True, tried
    return False, tried

def dpll(clauses, assign, stats):
    stats[0] += 1
    changed = True
    while changed:                                    # unit propagation
        changed = False
        simplified = []
        for c in clauses:
            if any(assign.get(abs(l)) == (l > 0) for l in c): continue
            rest = [l for l in c if abs(l) not in assign]
            if not rest: return False
            if len(rest) == 1:
                assign[abs(rest[0])] = rest[0] > 0; changed = True
            simplified.append(rest)
        clauses = simplified
    if not clauses: return True
    x = abs(clauses[0][0])
    for val in (True, False):
        if dpll(clauses, {**assign, x: val}, stats): return True
    return False

def random_3cnf(n, m):
    return [tuple(random.choice([-1, 1]) * v for v in random.sample(range(1, n + 1), 3)) for _ in range(m)]

print(" n   clauses  sat?   brute-force assignments   DPLL nodes")
for n in (10, 14, 18, 22):
    f = random_3cnf(n, int(4.26 * n))                 # near the satisfiability threshold
    s1, t = brute(n, f)
    st = [0]; s2 = dpll(f, {}, st)
    print(f"{n:2d}   {len(f):5d}   {str(s2):5s}  {t:22d}   {st[0]:9d}")
Output
 n   clauses  sat?   brute-force assignments   DPLL nodes
10      42   False                    1024          21
14      59   False                   16384          13
18      76   True                    68078           6
22      93   True                   819875          16

Worked examples

Worked problem

Showing a problem is in NP

Show that COMPOSITES = {n : n is composite, written in binary} is in NP.

Show solution
  1. Certificate: a factor d with 1 < d < n.
  2. Verifier: check 1 < d < n and that d divides n, by long division in time polynomial in the number of bits.
  3. If n is composite such a d exists; if n is prime, no certificate passes.

Answer: COMPOSITES is in NP (and in fact in P, by the AKS primality test).

Worked problem

A reduction proof outline

Show that VERTEX-COVER is NP-complete.

Show solution
  1. In NP: a set of k vertices is a certificate, checked by testing every edge.
  2. Reduce from INDEPENDENT-SET: map (G, k) to (G, n - k), where n is the number of vertices.
  3. S is independent in G exactly when V minus S touches every edge, i.e. is a vertex cover.
  4. The map takes linear time.

Answer: VERTEX-COVER is in NP and NP-hard, hence NP-complete.

Worked problem

Splitting a long clause

Convert \((a \lor b \lor c \lor d \lor e)\) into 3-literal clauses preserving satisfiability.

Show solution
  1. Introduce \(z_1, z_2\).
  2. Write \((a \lor b \lor z_1) \land (\overline{z_1} \lor c \lor z_2) \land (\overline{z_2} \lor d \lor e)\).
  3. If some literal is true, set the zs to pass the obligation along; if all are false, the chain forces a contradiction.

Answer: Three clauses with two fresh variables, satisfiable exactly when the original clause is.

Common mistakes

  • MistakeRead NP as "not polynomial".

    InsteadNP means nondeterministic polynomial time; P is contained in NP.

  • MistakeReduce the new problem to SAT to prove it NP-hard.

    InsteadReduce a known NP-complete problem to the new problem.

  • MistakeForget to show the problem is in NP.

    InsteadNP-completeness needs membership in NP as well as NP-hardness.

  • MistakeCall the subset-sum DP a polynomial algorithm.

    InsteadIt is polynomial in the value of T, exponential in its bit length.

  • MistakeConclude a problem is hopeless in practice because it is NP-complete.

    InsteadSolvers, approximations and special structure often handle real instances well.

Summary and key terms

  • P is the class of languages decidable in polynomial time; it is robust across reasonable deterministic models.
  • NP is the class with polynomial-time verifiers, equivalently nondeterministic polynomial time.
  • \(A \le_p B\) with B in P implies A in P; NP-complete problems are in NP and NP-hard.
  • Cook-Levin: SAT is NP-complete, by encoding an accepting tableau as a formula with local window constraints.
  • 3SAT, INDEPENDENT-SET, CLIQUE, VERTEX-COVER, HAMPATH and SUBSET-SUM are NP-complete via a chain of gadget reductions.
  • coNP contains complements of NP problems; factoring lies in NP intersect coNP.
  • SUBSET-SUM has a pseudo-polynomial algorithm; it is weakly NP-complete.
P
Languages decidable in polynomial time by a deterministic machine.
NP
Languages with polynomial-time verifiers; nondeterministic polynomial time.
Certificate
A string that a verifier uses to confirm a yes answer.
Polynomial-time reduction
A polynomial-time computable mapping reduction.
NP-hard
At least as hard as every problem in NP under polynomial-time reductions.
NP-complete
In NP and NP-hard.
Cook-Levin theorem
SAT is NP-complete.
Tableau
A table of configurations of one branch of a nondeterministic computation.
coNP
Complements of languages in NP.
Pseudo-polynomial time
Polynomial in the numeric values of the input, not in its length.
Gadget
A small structure in a reduction that simulates a piece of the source problem.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Is 2SAT NP-complete?

    Show answer

    No: it is in P, via strongly connected components of the implication graph.

  2. Give the certificate for CLIQUE.

    Show answer

    The k vertices of the clique.

  3. If SAT were in P, what would follow?

    Show answer

    P = NP.

  4. How does INDEPENDENT-SET reduce to CLIQUE?

    Show answer

    Take the complement graph and keep k.

  5. Why is the subset-sum DP not a proof that P = NP?

    Show answer

    Its running time depends on the value of T, exponential in the input length.

  6. Name a problem in NP intersect coNP not known to be in P.

    Show answer

    Factoring (decision version).

  7. What property of legal moves makes the Cook-Levin formula small?

    Show answer

    Locality: each 2 by 3 window can be checked by a fixed-size formula.

  8. In the 3SAT to IS reduction, why can an independent set use at most one vertex per clause?

    Show answer

    The vertices of a clause form a triangle.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz