Cook's Theorem: CNFSAT is NP-complete
Part I. CNFSAT is in NP.
Step 1: Go through the instance of CNFSAT and list each Boolean
variable used in the expression. (O(n2) steps if n is the
total size of
the CNFSAT instance.)
Step 2: Loop through the list of variables, and for each one ask the
"CHOICE" function whether it should be true or false. Write down
whether it should
be true or false along with the variable. (O(n2) steps
Step 3: Replace each instance of a variable in the CNFSAT instance with
the true or false value you have created. (O(n2) again.)
Step 4: Go through the CNFSAT expression and make sure every sum
contains at least one true. (O(n2) again.)
If Step 4 verifies that every sum contains at least one true in each
sum then the instance is satisfiable. If not, it is not since the
"CHOICE" would have given a correct value to satisfy the expression if
the expression could be satisfied.
Part II: Every problem in NP can be translated into an instance of
CNFSAT in polynomial time.
Let P be a problem in NP. Then there is a non-determinisitic,
polynomial-time algorithm to solve P. Suppose we have a program to
solve P at the machine-language level. In fact, to simplify things, we
are going to assume that we have a one-bit machine containing 1-bit
addressable memory. We are going to show how we can translate such a
program into an instance of CNFSAT.
From CSCI 150, we know that all operations on a computer can be reduced
to multiple AND, OR, and NOT operations together with LOAD/STORE
operations, conditional jumps, and a clock to move from one instruction
to the next. We are going to show how to implement AND, OR, NOT,
calls, conditional jumps, and a clock and conclude that since the
algorithm can be implemented in this "low-level" way that the program
can be translated into an instance of CNFSAT.
Here is how we do it:
We translate the program into a
logical expression describing the complete execution of the program.
First, our transformed program will need a concept of time
(corresponding to the system clock), memory (in our case 1-bit
addressable memory), and, of course, the transformed program.
Let us suppose to start that we have L instructions in the program. Let
p(n) be a polynomial and let n be the length of an instance of the
problem. Suppose p(n) is a polynomial
that is "big enough" that the program will solve an instance of this
problem of size n in time
<=p(n) if the instance should return true. (Remember, we are just
doing decision problems - problems that return true or false.)
Our "clock", then, is just a variable t representing time that "runs" for as long as the problem needs to run to get an answer.
Since the program finishes in less than p(n) time, we will only let the
variable t run from 0 to p(n). If t becomes larger than p(n) we will
assume the instance could not return true so it will return false. In
addition, there is the interesting fact that if the program runs in
less than p(n) time, it cannot possibly access more than p(n) memory
locations. We will picture our memory as pairs of addresses and bits
and a load or store of a memory bit will, then, simply mean going
through that list until you find the matching address and, then,
modifying that bit. Thus each memory access could take p(n) time, there
could be p(n) times, and there could be at most p(n) memory locations.
We have, then, modified our program so that it could take p(n)2
time but since this is still a polynomial we are ok with it. In fact,
let us call the new polynomial q(n). Now let time t run from 0 to q(n)+1.
Let E(i,t) be a series of logical variables meaning that instruction i
is executed at time t if E(i,t) is true. Let M(j,t) be a series of
Boolean variables representing the contents of memory bit j at time t.
Notice that we have a constant number of instructions (L) and time
running from 0 to q(n)+1 as above, so we have at most L*(q(n)+2) of
these E variables. Since both j and t could be q(n) different values,
we have at most q(n)2 of the M variables.
We will deal with the following instructions:
if(M) goto k and M=NT and M=N+T and CHOICE() and M=N and M=N'
where the if is obvious, M=NT means M receives the and of N and
T, CHOICE() sets a memory location called G to true or false and M=N
a simple assignment.
Here are the logical steps in the solution:
Clearly, an and of these 4 parts will represent a complete correct run
of the program - modulo some hand-waving. Also, if we can rewrite each
part of 1-4 in CNF then the and of those parts will be in CNF.
- At time 0 the first instruction will be executed. This is
represented by E(1,0)E'(2,0)E'(3,0)...E'(L,0) where this is the and of
these variables and where E'(i,j) means the negation of E(i,j). Note
this is simple CNF.
- At time 0 the memory contains the instance of the problem to be
run. Whatever the bits required - say, for example, 101111001....010 -
we would then have
a simple and to represent that condition.
- At any time t, only one instruction can be executed. This is
represented at a particular time t by
E(1,t) => E'(2,t)E'(3,t)...E'(L-1,t)E'(L,t), E(2,t) =>
E'(1,t)E'(3,t)...E'(L-1,t)E'(L,t), ... E(L,t) =>
E'(1,t)E'(2,t)E'(3,t)...E'(L-1,t). Note that since A => B is
equivalent to A'+B, we can rewrite this as
this can be fixed by distributing the + over the products to get
we and this for all t from 1 to q(n)+1 and for all j from 1 to L we
will have what we want and
it is in CNF.
- For any instruction, we can write the effect of that instruction
in CNF. The effect always consists of determining which instruction
executes next AND what affect the instruction has. (We will demonstrate
the CNF for the instructions above.)
- The run of the program returns true if and only if at time q(n)+1
the return true instruction is executing. This can be written
E(rt,q(n)+1) where rt is the index of the return true instruction.
Note 1 2, and 3 are done. (I.e., they are in CNF and obviously
correct.) Number 5 can be handled in the following way. Whenever we
find ourselves executing a return true instruction, we simply replace
it with a return true followed by a statement that "infinite
loops" in the same spot. Hence, E(rt,t) => E(rt,t+1) and since an
A => B can be rewritten as A'+B we see that we need only add the
and of all statements of the form E'(rt,t)+E(rt,t+1) for each t and,
then, the instance comes out true if and only if E(rt,q(t)+1) is true
and this and of these or's is in CNF.
Suppose instruction j is if(M(i,t)) then goto k. This is E(j,t) means
if(M(i,t)) then k is the instruction at t+1 else j+1 is the instruction
at t+1. Thus E(j,t) => ( M(i,t) => E(k,t+1) && M'(i,t) =>
E(j+1,t+1)). Using the rule for implication gives us
E'(j,t)+(M'(i,t)+E(k,t+1))(M(i,t)+E(j+1,t+1)) which is logically
equivalent to (E'(j,t)+M'(i,t)+E(k,t+1))(E'(j,t)+M(i,t)+E(j+1,t+1))
which is in CNF. In addition, for every memory location M(i,t+1)=M(i,t)
is equivalent to (M'(i,t+1)+M(i,t))(M(i,t+1)+M'(i,t)) should be added as a
product which the E'(j,t) should be distributed over but we will leave
that off for simplicity. Note that it should be there.
Suppose that instruction j is M(i)=M(k). Then what we have is E(j,t) => [(M(i,t+1)=M(k,t+1)) and E(j+1,t+1)]. Using our distributive
law again, we write this as
(E'(j,t)+(M(i,t+1)=M(k,t+1)))(E'(j,t)+E(j+1,t+1)). We need to show that
E'(j,t)+(M(i,t+1)=M(k,t+1)) can be put in CNF. If we use product of sum
formulation from a truth table we see that A=B <=>
(A+B')(A'+B) so that E'(j,t)+(M(i,t+1)=M(k,t+1)) is equivalent to
E'(j,t)+(M(i,t+1)+M'(k,t+1))(M'(i,t+1)+M(k,t+1)) and distributing +
gives us the expression
(E'(j,t)+M(i,t+1)+M'(k,t+1))(E'(j,t)+M'(i,t+1)+M(k,t+1)) and when we
and this with (E'(j,t)+E(j+1,t+1)) the result is clearly in CNF. Again,
we should add an and of M(m,t+1)=M(m,t) for every m not equal to i as
above. Again, we keep it simple by not doing this.
Suppose that instruction j is CHOICE(). What should it do? Well, it
should set M(G,t+1) to true if that will work or to false if that will
work. Thus, all we can say is that the choice could be either one but
if it can be done it will be chosen so as to make the program work and,
therefore, the corresponding CNF expression to work. This means that
CHOICE() should advance and allow M(G,T+1) to be either value in the
so that the expression can give the value to it that will make the
expression come true. In other words, E(j,t)=>(M(G,t+1)+M'(G,t+1)) && E(j+1,t+1). This becomes
E'(j,t)+(M(G,t+1)+M'(G,t+1)E(j+1,t+1) which by distribution is
equivallent to (E'(j,t)+M(G,t+1)+M'(G,t+1))(E'(j,t)+E(j+1,t+1)) which
is in CNF. Again, all other memory is preserved.
Suppose instruction j is M(i)=M(k)M(h). Recall that A=B can be written
as (A+B')(A'+B) we get that instruction j at time t is E(j,t) =>
(M(i)+(M(k)M(h))')(M'(i)+M(k)M(h)). (M(k)M(h))' is (M'(k)+M'(h)) so
this becomes (M(i)+M'(k)+M'(h))(M'(i)+M(k))(M'(i)+M(h)) so using the
rule for => and distributing E'(j,t) we represent this instruction
M(i)=M(k)+M(h) and M(i)=M'(k) are left as exercises.
Note that we have part 1 taking at most O(q2(n)), part 2 takes at most
O(q2(n)), part 3 takes O(L2q2(n)) which is O(q2(n)), part 4
takes O(q(n)4) steps and part 5 takes only a slight rewrite
of the return instruction. All that gets added is the one ending
statement E(rt,q(n+1)+1) anded with everything else.
QEAD (Quid Errat almost demonstratum).