/******************************************************************************
* File: TwoSat.java
* Author: Keith Schwarz (htiek@cs.stanford.edu)
*
* An implementation of a 2-SAT solver, which takes as input a boolean formula
* in 2-CNF normal form (a conjunction of disjunctions with two literals per
* clause). Unlike 3-SAT, which is known to be NP-complete and which is
* suspected to admit no polynomial-time solutions, 2-SAT has a linear-time
* solution. This is the implementation used here.
*
* The key insight that makes 2-SAT solvable in polynomial-time is the fact
* that a clause of the form (A or B) is equivalent to (~A implies B) AND (B
* implies ~A). Given this, we can convert any clause with two literals in it
* into two directed implications. This general construction allows us to
* convert any 2-SAT formula into a graph of implications between all the
* literals in the formulae and their negations. We can then follow those
* implications to see if that graph implies a logical impossibility. In
* particular, if in the graph of implications we find that A implies ~A and
* ~A implies A, then we know that the formula is unsatisfiable. If, however,
* no node and its complement imply each other, we can show that the formula is
* satisfiable. The proof is as follows. Begin by partioning the implication
* graph into strongly-connected components. If a literal and its complement
* each imply each other, then they will be in the same connected component and
* we can detect this efficiently. If not, then each literal and its
* complement must be in different connected components. We can then decompose
* the graph into a DAG of SCCs, then label the components of the SCC with
* boolean values such that no SCC labeled true has an ancestor labeled false.
* This involves some fun induction on the structure of the graph to prove.
* However, if all that we're interested in is whether the input formula is
* satisfiable, then we can do so by constructing the implication graph,
* finding its strongly connected components, then checking whether any node
* and its negation are in the same SCC. We can construct the implication
* graph in O(n) time (where n is the number of formulae), and the graph has
* O(n) nodes and O(n) edges. Finding SCCs takes O(n) time using either
* Tarjan's or Kosaraju's algorithm, and confirming that each node is in its
* own SCC takes O(n) for a total runtime of O(n).
*
* This implementation relies on the existence of a Kosaraju class, also from
* the Archive of Interesting Code. You can find it online at
*
* http://keithschwarz.com/interesting/code/?dir=kosaraju
*/
import java.util.*; // For List, Set
public final class TwoSat {
/**
* Given as input a list of clauses representing a 2-CNF formula, returns
* whether that formula is satisfiable.
*
* @param formula The input 2-CNF formula.
* @return Whether the formula has a satisfying assignment.
*/
public static <T> boolean isSatisfiable(List<Clause<T>> formula) {
/* Begin by populating a set of all the variables in this formula. */
Set<T> variables = new HashSet<T>();
for (Clause<T> clause: formula) {
variables.add(clause.first().value());
variables.add(clause.second().value());
}
/* Construct the directed graph of implications. Begin by creating the
* nodes.
*/
DirectedGraph<Literal<T>> implications = new DirectedGraph<Literal<T>>();
for (T variable: variables) {
/* Add both the variable and its negation. */
implications.addNode(new Literal<T>(variable, true));
implications.addNode(new Literal<T>(variable, false));
}
/* From each clause (A or B), add two clauses - (~A -> B) and (~B -> A)
* to the graph as edges.
*/
for (Clause<T> clause: formula) {
implications.addEdge(clause.first().negation(), clause.second());
implications.addEdge(clause.second().negation(), clause.first());
}
/* Compute the SCCs of this graph using Kosaraju's algorithm. */
Map<Literal<T>, Integer> scc = Kosaraju.stronglyConnectedComponents(implications);
/* Finally, check whether any literal and its negation are in the same
* strongly connected component.
*/
for (T variable: variables)
if (scc.get(new Literal<T>(variable, true)).equals(scc.get(new Literal<T>(variable, false))))
return false;
/* If not, the formula must be satisfiable. */
return true;
}
}