Title

ECS 120 Theory of Computation
Time Complexity Class NP
Julian Panetta
University of California, Davis

Problems with Efficiently Verifiable Solutions

  • So far we’ve seen problems that can be solved efficiently (in polynomial time).

  • Next we’ll look at problems that we do not know how to solve efficiently, but we can verify a solution efficiently.

  • These belong to the class of languages/problems NP (nondeterministic polynomial time).

    \(\NP\): solvable in polynomial time by a nondeterministic Turing machine.

  • To prove a problem is in NP, we need to exhibit a polynomial-time verifier for it.

  • Let’s see some examples!

\(\probHamPath\)

Given a directed graph \(G\), check if a Hamiltonian path exists.

\[\probHamPath = \setbuild{\encoding{G}}{ G \text{ is a directed graph with a Hamiltonian path }}\]

  • Is \(\probHamPath \in \P\)? We don’t know of any polynomial-time algorithm for it!

  • However, we can efficiently verify a solution that is given to us: \[ \fragment{\verifier{\probHamPath} = \setbuild{\encoding{G, p}}{ G \text{ is a directed graph with the Hamiltonian path } p }} \]

    def ham_path_verify(G,p):
       V, E = G
       # verify each pair of adjacent nodes in p is connected by an edge in `E`
       for i in range(len(p) - 1):
           if (p[i], p[i+1]) not in E:
               return False
       # verify p and V have same number of nodes
       if len(p) != len(V):
           return False
       # verify each node appears at most once in p
       if len(set(p)) != len(p):
           return False
       return True

    Why is this algorithm polynomial-time?

  • Therefore \(\probHamPath \in \NP\).

\(\probComposites\)

\[ \probComposites = \setbuild{\encoding{n}}{ n \in \N^+ \text{ and } n = p q \text{ for some integers } p, q \ge 2} \]

  • Is \(\probComposites \in \P\)?
    Surprisingly, yes! But this is not obvious (AKS primality test discovered in 2002)

  • But it’s easy to prove that \(\probComposites \in \NP\).

  • What is the verification language? \[ \verifier{\probComposites} = \left\{\fragment{\encoding{n, d} \; \big| \; n, d \in \N^+,} \; \fragment{d \text{ divides } n,} \; \fragment{\text{ and } \; 1 < d < n}\right\} \]

  • What’s an algorithm for deciding \(\verifier{\probComposites}\)?

    def composite_verify(n: int, d: int) -> bool:
      return 1 < d < n and n % d == 0
  • What’s the time complexity in terms of the number of bits in \(n\) and \(d\)?

    • \(O(k)\) for the comparison
    • \(O(k^2)\) for the division using the standard grade-school algorithm

Deciding \(\probComposites\) using the Verifier

\[ \probComposites = \setbuild{\encoding{n}}{ n \in \N^+ \text{ and } n = p q \text{ for some integers } p, q \ge 2} \]

  • Is \(\probComposites \in \P\)?
    Surprisingly, yes! But this is not obvious (AKS primality test discovered in 2002)

  • But it’s easy to prove that \(\probComposites \in \NP\).

  • What is the verification language? \[ \verifier{\probComposites} = \left\{\encoding{n, d} \; \big| \; n, d \in \N^+, \; d \text{ divides } n, \; \text{ and } \; 1 < d < n\right\} \]

  • What’s an algorithm for deciding \(\verifier{\probComposites}\)?

    def composite_verify(n: int, d: int) -> bool:
      return 1 < d < n and n % d == 0
  • Can we decide \(\probComposites\) using the verifier?
    • Yes, but in exponential time!
      def composites_decide_slow(n: int) -> bool:
          for d in range(2, n):
              if composite_verify(n, d):
                  return True
          return False

The Class \(\NP\)

Now let’s formally define the class \(\NP\).

A polynomial-time verifier for a language \(A\) is a Turing machine \(V\) such that: \[ \fragment{A = \setbuild{x \in \binary^*}{\fragment{\left(\exists w \in \binary^{\le |x|^k}\right)} \; \fragment{V \text{ accepts } \encoding{x, w}}}} \] for some constant \(k\). Furthermore, \(V\) must run in time \(O(|x|^c)\) for some constant \(c\).

\(\NP\) is the class of languages that have polynomial-time verifiers.
We call the language decided by the verifier for \(A \in \NP\) the verification language of \(A\), denoted \(\verifier{A}\).

  • We can measure the running time of \(V\) either with respect to \(|x|\) or \(|\encoding{x, w}|\).
    • Since \(|w| = O(|x|^k)\), these only differ in length by a polynomial.
    • Therefore \(V\) runs in polynomial time with respect to \(|x|\)
      if and only if it runs in polynomial time with respect to \(|\encoding{x, w}|\).

Decision vs Optimization Problems

  • Our focus on decision problems in this course might seem limiting and unrealistic.
  • Many problems are not posed as a yes/no question.
  • Another very common type of problem is an optimization problem.
    • For example, find the minimum-cost path between two nodes in a weighted graph.
      (“What is the cheapest way to fly from Sacramento to Rome”)
    • Find the maximum clique in a graph.
  • However, we can convert any optimization problem into a decision problem.
    • Pick a threshold \(k\) and ask if there is a solution at least as good as \(k\).
    • For example, “Is there a flight from Sacramento to Rome that fits within my budget of \(k\)?”
      This decision problem is clearly in \(\NP\)!
  • We can then solve the original optimization problem by solving a sequence of decision problems. How? Binary search!
  • If the decision problem is in \(\P\) or \(\NP\), then so is the optimization problem!

Decision vs Search Problems

  • All problems in \(\NP\) can also be thought of as a search problem.
  • Every problem \(A\) in \(\NP\) has a verifier \(V\).
  • Deciding if \(x \in A\) is equivalent to searching for a witness \(w\) such that \(V\) accepts \(\encoding{x, w}\)
    or reporting that none exists.
    • For example, for \(\probHamPath\), we search for a Hamiltonian path \(p\).
    • While the search problem sounds harder (it needs to exhibit \(w\) rather than answer yes/no),
      it is not actually harder.
    • If \(\P = \NP\), then every search problem corresponding to a decision problem in \(\NP\) can be solved in polynomial time.

More Examples of Problems in \(\NP\): \(\probClique\)

A clique in a graph \(G\) is a subset of vertices such that every two are connected by an edge.

A \(k\)-clique is a clique with \(k\) vertices.

\[ \probClique = \setbuild{\encoding{G, k}}{ G \text{ is an undirected graph with a } k\text{-clique}} \]

Theorem: \(\probClique \in \NP\).

Proof: the following is a polynomial-time verifier, deciding \[ \verifier{\probClique} = \setbuild{\encoding{G, \fragment{k, C}}}{ G \text{ is an undirected graph } \fragment{\text{with a } k\text{-clique } C}} \]

from itertools import combinations as subsets
def clique_verifier(G, k, C):
    V, E = G
    # verify C is the correct size, and k is not too large
    if len(C) != k or k > len(V):
        return False
    # verify each pair of nodes in C shares an edge
    for (u, v) in subsets(C, 2):
        if (u, v) not in E: return False
    return True

Why is this algorithm polynomial-time?

Is the witness short enough?

More Examples of Problems in \(\NP\): \(\probSubsetSum\)

“Given a collection of integers, can we select some of them that sum to target integer \(t\)?”

\[ \probSubsetSum = \setbuild{\encoding{C, t}}{ C \text{ is a } \textbf{multiset} \text{of integers, and } (\exists S \subseteq C) \; t = \sum_{y \in S} y} \]

Example: \(\encoding{\{4, 4, 11, 16, 21, 27\}, 29} \in \probSubsetSum\),
but \(\encoding{\{4, 11, 16\}, 13} \notin \probSubsetSum\).

Theorem: \(\probSubsetSum \in \NP\).

Proof: the following is a polynomial-time verifier

def subset_sum_verify(C, t, S):
    if sum(S) != t: # check sum
        return False
    for x in S: # verify that S is a subset of C
        if x not in C:
            return False
    return True
  • Let \(n = \log t + \sum_{x \in C} \log x + \sum_{y \in S} \log y\) be the input length.
  • We must compute \(|S| - 1\) additions to sum \(S\), with \(|S| = O(n)\)
  • The summed numbers never get longer than \(n\) bits, meaning each addition takes \(O(n)\) time.