A polynomial-time verifier for a language \(A\) is a Turing machine \(V\) such that: \[ A = \setbuild{x \in \binary^*}{\left(\exists w \in \binary^{\le |x|^k}\right) \; V \text{ accepts } \encoding{x, w}} \] for some constant \(k\). Furthermore, \(V\) must run in time \(O(|x|^c)\) for some constant \(c\).
Here, \(w\) is called a witness or certificate or proof for \(x\).
\(\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}\).
\[\probHamPath = \setbuild{\encoding{G}}{ G \text{ is a directed graph with a Hamiltonian path }}\]
\[ \probClique = \setbuild{\encoding{G, k}}{ G \text{ is an undirected graph with a } k\text{-clique}} \]
\[ \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} \]
“Proving \(\coNP \ne \NP\) is at least as hard as proving \(\P \ne \NP\).”
Theorem: \(\NP \subseteq \EXP\)
Proof:
Negation/NOT
\[ \begin{array}{c|c} x & \neg x \\ \hline 0 & 1 \\ 1 & 0 \end{array} \]
Conjunction/AND
\[ \begin{array}{cc|c} x & y & x \land y \\ \hline 0 & 0 & 0 \\ 0 & 1 & 0 \\ 1 & 0 & 0 \\ 1 & 1 & 1 \end{array} \]
Disjunction/OR
\[ \begin{array}{cc|c} x & y & x \lor y \\ \hline 0 & 0 & 0 \\ 0 & 1 & 1 \\ 1 & 0 & 1 \\ 1 & 1 & 1 \end{array} \]
More formally, we can define Boolean formulas inductively (like with regular expressions):
Given a finite set of variables \(V\), a Boolean formula \(\phi\) is defined as:
Precedence: \(\neg\) has highest precedence, followed by \(\land\), then \(\lor\).
Parentheses can be used to override this.
We can use this recursive structure to efficiently parse and evaluate Boolean formulas.
A Boolean formula \(\phi\) is satisfiable if there is an assignment of truth values to its variables that makes the formula evaluate to \(1\).
Example: \[ \phi = (x \land y) \lor (z \land \neg y) \] is satisfiable because setting \(x = 0, y = 0, z = 1\) yields \(\phi(001) = 1\).
We introduce the Boolean satisfiability problem:
\[ \probSAT = \setbuild{\encoding{\phi}}{ \phi \text{ is a satisfiable Boolean formula }} \]
We know \(\probSAT \in \NP\) because we can easily implement a polynomial-time decider for:
\[ \verifier{\probSAT} = \setbuild{\encoding{\phi, w}}{ \phi \text{ is a Boolean formula, and } \phi(w) = 1 } \]
Just evaluate \(\phi\) on the assignment \(w\)!
A Boolean formula \(\phi\) is satisfiable if there is an assignment of truth values to its variables that makes the formula evaluate to \(1\).
We introduce the Boolean satisfiability problem:
\[ \probSAT = \setbuild{\encoding{\phi}}{ \phi \text{ is a satisfiable Boolean formula }} \]
We know \(\probSAT \in \NP\) because we can easily implement a polynomial-time decider for:
\[ \verifier{\probSAT} = \setbuild{\encoding{\phi, w}}{ \phi \text{ is a Boolean formula, and } \phi(w) = 1 } \]
Recall the the \(k\)-clique problem: \[ \probClique = \setbuild{\encoding{G, k}}{ G \text{ is an undirected graph with a } k\text{-clique}} \]
We now introduce a closely related problem, the \(k\)-independent set problem: \[ \probIndSet = \setbuild{\encoding{G, k}}{ G \text{ is an undirected graph with a } k\text{-independent set}} \]
Given input \(\encoding{G_i, k}\) for \(\probIndSet\),
how can we convert it to input \(\encoding{G_c, k}\) for \(\probClique\) such that:
\[ \encoding{G_i, k} \in \probIndSet \iff \encoding{G_c, k} \in \probClique \]
A function \(f: \binary^* \to \binary^*\) is polynomial-time computable if there exists a polynomial-time algorithm that, given input \(x\), computes \(f(x)\).
Let \(A, B \subseteq \binary^*\) be decision problems. We say \(A\) is polynomial-time reducible to \(B\)
if there is a polynomial-time computable function \(f\) such that for all \(x \in \binary^*\):
\[ \fragment{x \in A \iff f(x) \in B} \]
We denote this by \(A \le^P B\).
\(A \le^P B\) means: “B is at least as hard as A” (to within a polynomial-time factor).
from itertools import combinations as subsets
def reduction_from_independent_set_to_clique(G, k):
V, E = G
Ec = [ {u,v} for (u,v) in subsets(V,2)
if {u,v} not in E and u!=v ]
Gc = (V, Ec)
return (Gc, k)
# Hypothetical polynomial-time algorithm for Independent Set
def independent_set(G, k):
Gp, kp = reduction_from_independent_set_to_clique(G, k)
return clique_algorithm(Gp, kp)
# Hypothetical polynomial-time algorithm for Clique
def clique_algorithm(G, k):
raise NotImplementedError("Not implemented yet...")