\(\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Cormen et al}}\) \(\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{Aalto access})}\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{online via Aalto lib})}\) \(\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Algorithms, 4th ed.}}\) \(%\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Sedgewick and Wayne}}\) \(\) \(\newcommand{\Java}{\href{http://java.com/en/}{Java}}\) \(\newcommand{\Python}{\href{https://www.python.org/}{Python}}\) \(\newcommand{\CPP}{\href{http://www.cplusplus.com/}{C++}}\) \(\newcommand{\ST}[1]{{\Blue{\textsf{#1}}}}\) \(\newcommand{\PseudoCode}[1]{{\color{blue}\textsf{#1}}}\) \(\) \(%\newcommand{\subheading}[1]{\textbf{\large\color{aaltodgreen}#1}}\) \(\newcommand{\subheading}[1]{\large{\usebeamercolor[fg]{frametitle} #1}}\) \(\) \(\newcommand{\Blue}[1]{{\color{flagblue}#1}}\) \(\newcommand{\Red}[1]{{\color{aaltored}#1}}\) \(\newcommand{\Emph}[1]{\emph{\color{flagblue}#1}}\) \(\) \(\newcommand{\Engl}[1]{({\em engl.}\ #1)}\) \(\) \(\newcommand{\Pointer}{\raisebox{-1ex}{\huge\ding{43}}\ }\) \(\) \(\newcommand{\Set}[1]{\{#1\}}\) \(\newcommand{\Setdef}[2]{\{{#1}\mid{#2}\}}\) \(\newcommand{\PSet}[1]{\mathcal{P}(#1)}\) \(\newcommand{\Card}[1]{{\vert{#1}\vert}}\) \(\newcommand{\Tuple}[1]{(#1)}\) \(\newcommand{\Implies}{\Rightarrow}\) \(\newcommand{\Reals}{\mathbb{R}}\) \(\newcommand{\Seq}[1]{(#1)}\) \(\newcommand{\Arr}[1]{[#1]}\) \(\newcommand{\Floor}[1]{{\lfloor{#1}\rfloor}}\) \(\newcommand{\Ceil}[1]{{\lceil{#1}\rceil}}\) \(\newcommand{\Path}[1]{(#1)}\) \(\) \(%\newcommand{\Lg}{\lg}\) \(\newcommand{\Lg}{\log_2}\) \(\) \(\newcommand{\BigOh}{O}\) \(%\newcommand{\BigOh}{\mathcal{O}}\) \(\newcommand{\Oh}[1]{\BigOh(#1)}\) \(\) \(\newcommand{\todo}[1]{\Red{\textbf{TO DO: #1}}}\) \(\) \(\newcommand{\NULL}{\textsf{null}}\) \(\) \(\newcommand{\Insert}{\ensuremath{\textsc{insert}}}\) \(\newcommand{\Search}{\ensuremath{\textsc{search}}}\) \(\newcommand{\Delete}{\ensuremath{\textsc{delete}}}\) \(\newcommand{\Remove}{\ensuremath{\textsc{remove}}}\) \(\) \(\newcommand{\Parent}[1]{\mathop{parent}(#1)}\) \(\) \(\newcommand{\ALengthOf}[1]{{#1}.\textit{length}}\) \(\) \(\newcommand{\TRootOf}[1]{{#1}.\textit{root}}\) \(\newcommand{\TLChildOf}[1]{{#1}.\textit{leftChild}}\) \(\newcommand{\TRChildOf}[1]{{#1}.\textit{rightChild}}\) \(\newcommand{\TNode}{x}\) \(\newcommand{\TNodeI}{y}\) \(\newcommand{\TKeyOf}[1]{{#1}.\textit{key}}\) \(\) \(\newcommand{\PEnqueue}[2]{{#1}.\textsf{enqueue}(#2)}\) \(\newcommand{\PDequeue}[1]{{#1}.\textsf{dequeue}()}\) \(\) \(\newcommand{\Def}{\mathrel{:=}}\) \(\) \(\newcommand{\Eq}{\mathrel{=}}\) \(\newcommand{\Asgn}{\mathrel{\leftarrow}}\) \(%\newcommand{\Asgn}{\mathrel{:=}}\) \(\) \(%\) \(% Heaps\) \(%\) \(\newcommand{\Downheap}{\textsc{downheap}}\) \(\newcommand{\Upheap}{\textsc{upheap}}\) \(\newcommand{\Makeheap}{\textsc{makeheap}}\) \(\) \(%\) \(% Dynamic sets\) \(%\) \(\newcommand{\SInsert}[1]{\textsc{insert}(#1)}\) \(\newcommand{\SSearch}[1]{\textsc{search}(#1)}\) \(\newcommand{\SDelete}[1]{\textsc{delete}(#1)}\) \(\newcommand{\SMin}{\textsc{min}()}\) \(\newcommand{\SMax}{\textsc{max}()}\) \(\newcommand{\SPredecessor}[1]{\textsc{predecessor}(#1)}\) \(\newcommand{\SSuccessor}[1]{\textsc{successor}(#1)}\) \(\) \(%\) \(% Union-find\) \(%\) \(\newcommand{\UFMS}[1]{\textsc{make-set}(#1)}\) \(\newcommand{\UFFS}[1]{\textsc{find-set}(#1)}\) \(\newcommand{\UFCompress}[1]{\textsc{find-and-compress}(#1)}\) \(\newcommand{\UFUnion}[2]{\textsc{union}(#1,#2)}\) \(\) \(\) \(%\) \(% Graphs\) \(%\) \(\newcommand{\Verts}{V}\) \(\newcommand{\Vtx}{v}\) \(\newcommand{\VtxA}{v_1}\) \(\newcommand{\VtxB}{v_2}\) \(\newcommand{\VertsA}{V_\textup{A}}\) \(\newcommand{\VertsB}{V_\textup{B}}\) \(\newcommand{\Edges}{E}\) \(\newcommand{\Edge}{e}\) \(\newcommand{\NofV}{\Card{V}}\) \(\newcommand{\NofE}{\Card{E}}\) \(\newcommand{\Graph}{G}\) \(\) \(\newcommand{\SCC}{C}\) \(\newcommand{\GraphSCC}{G^\text{SCC}}\) \(\newcommand{\VertsSCC}{V^\text{SCC}}\) \(\newcommand{\EdgesSCC}{E^\text{SCC}}\) \(\) \(\newcommand{\GraphT}{G^\text{T}}\) \(%\newcommand{\VertsT}{V^\textup{T}}\) \(\newcommand{\EdgesT}{E^\text{T}}\) \(\) \(%\) \(% NP-completeness etc\) \(%\) \(\newcommand{\Poly}{\textbf{P}}\) \(\newcommand{\NP}{\textbf{NP}}\) \(\newcommand{\PSPACE}{\textbf{PSPACE}}\) \(\newcommand{\EXPTIME}{\textbf{EXPTIME}}\)
\(\newcommand{\Prob}[1]{\textsf{#1}}\) \(\newcommand{\SUBSETSUM}{\Prob{subset-sum}}\) \(\newcommand{\MST}{\Prob{minimum spanning tree}}\) \(\newcommand{\MSTDEC}{\Prob{minimum spanning tree (decision version)}}\) \(\newcommand{\ISET}{\Prob{independent set}}\) \(\newcommand{\SAT}{\Prob{sat}}\) \(\newcommand{\SATT}{\Prob{3-cnf-sat}}\) \(\) \(\newcommand{\VarsOf}[1]{\mathop{vars}(#1)}\) \(\newcommand{\TA}{\tau}\) \(\newcommand{\Eval}[2]{\mathop{eval}_{#1}(#2)}\) \(\newcommand{\Booleans}{\mathbb{B}}\) \(\) \(\newcommand{\UWeights}{w}\) \(\) \(\newcommand{\False}{\textbf{false}}\) \(\newcommand{\True}{\textbf{true}}\) \(\) \(\newcommand{\SSDP}{\textrm{hasSubset}}\) \(\) \(\newcommand{\SUD}[3]{x_{#1,#2,#3}}\) \(\newcommand{\SUDHints}{H}\)

Approaches for Solving NP-Complete problems

So what to do when a problem is \(\NP\)-complete? As there are lots practical problems that have to be solved even though they are \(\NP\)-complete (or even harder), one has to develop some approaches that work “well enough” in practice. Some alternatives are:

  • ​ Develop backtracking search algorithms with efficient heuristics and pruning techniques.

  • ​ Attack some special cases occurring in practice.

  • ​ Develop approximation algorithms.

  • ​ Apply incomplete local search methods.

  • ​ Use existing tools.

Special cases

Sometimes we have a computationally hard problem but the instances that occur in practice are somehow restricted special cases. In such a case, it may be possible to develop an algorithm that solves such special instances efficiently. Technically, we are not solving the original problem anymore but a different problem where the instance set is a subset of the original problem instance set.

Example: subset-sum with smallish values

In the case of \(\SUBSETSUM\), it is quite easy to develop a dynamic programming (recall Round 6) algorithm that works extremely well for “smallish” numbers. First, define a recursive function \(\SSDP(S,t)\) evaluating to true if and only if \(S = \Set{v_1,v_2,...,v_n}\) has a subset that sums to \(t\):

  • \(\SSDP(S,t) = \True\) if \(t = 0\),

  • \(\SSDP(S,t) = \False\) if \(S = \emptyset\) and \(t \neq 0\),

  • \(\SSDP(S,t) = \True\) if \(\SSDP(\Set{v_2,v_3,...,v_n},t)=\True\) or \(\SSDP(\Set{v_2,v_3,...,v_n},t-v_1)=\True\), and

  • \(\SSDP(S,t) = \False\) otherwise.

Now, using the memoization technique with arrays, it is easy to implement a dynamic programming algorithm that works in time \(\Oh{\Card{S} M}\) and needs \(\Oh{M \log_2 M}\) bytes of memory, where \(M = \sum_{s \in S,s > 0}s - \sum_{s \in S,s < 0}s\) is the size of the range in which the sums of all possible subsets of \(S\) must be included. Solution reconstruction is also rather straightforward. Algorithm sketch:

  • ​ Take an empty array \(a\).

  • ​ For each number \(x \in S\) in turn, insert in one pass

    • \(a[y+x] = x\) for each \(y\) for which \(a[y]\) is already defined, and

    • \(a[x] = x\).

That is, in a number-by-number manner, insert all the subset sums that can be obtained either using the number alone or by adding it to any of the sums obtainable by using earlier numbers. The value written in the element \(a[s]\) tells what was the last number inserted to get the sum \(s\); this allows reconstruction of the solution subset.

Example

Assume that the set of numbers is \(S= \Set{2,7,14,49,98,343,686,2409,2793,16808,17206,117705,117993}\). The beginning of the array is

_images/dynprog-subsetsum.png

Inspecting the array, one can directly see that, for instance, there is a subset \(S' \subseteq S\) for which \(\sum_{s \in S'} = 58\) and that such a subset is obtained with the numbers \(a[58] = 49\), \(a[58-49] = a[9] = 7\) and \(a[9-7] = a[2] = 2\).

Is the dynamic programming algorithm above a polynomial time algorithm for the \(\NP\)-complete problem \(\SUBSETSUM\)? No, unfortunately it is not:

  • ​ the size of the input instance is the number of bits needed to represent the numbers, and

  • ​ therefore the value of the sum \(\sum_{s \in S,s > 0}s - \sum_{s \in S,s < 0}s\), and thus the running time as well, can be exponentially large with respect to the instance size.

For instance, the numbers that were produced in our earlier reduction from \(\SATT\) to \(\SUBSETSUM\) are not small: if the \(\SATT\) instance has \(n\) variables and \(m\) clauses, then the numbers have \(n+m\) digits and their values are in the order \(10^{n+m}\), i.e., exponential in \(n+m\). Note: if the dynamic programming approach is implemented with hash maps instead of arrays, then it works for the special case when the numbers are not necessarily “smallish” but when the number of different sums that can be formed from \(S\) is “smallish”.

Reducing to an another problem

For some \(\NP\)-complete and harder problems there are some very sophisticated solver tools that work extremely well for many practical instances:

Thus if one can reduce the current problem \(B\) into one of the problems \(A\) above, it may be possible to obtain a practically efficient solving method without programming the search method by one-self. Notice that here we are using reductions in the other direction compared to the case when we proved problems \(\NP\)-complete: we now take the problem which we would like to solve and reduce it a problem for which we have an usually-efficient tool available:

_images/npc2.jpg

Example: solving Sudokus with SAT

Let us apply the above described approach to Sudoku solving. Our goal is to use the sophisticated CNF SAT solvers available. Thus, given an \(n \times n\) partially filled Sudoku grid, we want to make a propositional formula \(\phi\) such that

  • ​ the Sudoku grid has a solution if and only the formula is satisfiable, and

  • ​ from a satisfying assignment for \(\phi\) we can build a solution for the Sudoku grid.

In practice we thus want to use the following procedure:

_images/flow-sudoku-cnf.png

To obtain such a CNF formula \(\phi\) for a Sudoku grid, we first introduce a variable

\[\SUD{r}{c}{v}\]

for each row \(r = 1,...,n\), column \(c = 1,...,n\) and value \(v = 1,...,n\). The intuition is that if \(\SUD{r}{c}{v}\) is true, then the grid element at row \(r\) and column \(c\) has the value \(v\). The formula

\[\phi = C_1 \land C_2 \land C_3 \land C_4 \land C_5 \land C_6\]

is built by introducing sets of clauses that enforce different aspects of the solution:

  • ​ Each entry has at least one value: \(C_1 = \bigwedge_{1 \le r \le n, 1 \le c \le n}(\SUD{r}{c}{1} \lor \SUD{r}{c}{2} \lor ... \lor \SUD{r}{c}{n})\)

  • ​ Each entry has at most one value: \(C_2 = \bigwedge_{1 \le r \le n, 1 \le c \le n,1 \le v < v' \le n}(\neg\SUD{r}{c}{v} \lor \neg\SUD{r}{c}{v'})\)

  • ​ Each row has all the numbers: \(C_3 = \bigwedge_{1 \le r \le n, 1 \le v \le n}(\SUD{r}{1}{v} \lor \SUD{r}{2}{v} \lor ... \lor \SUD{r}{n}{v})\)

  • ​ Each column has all the numbers: \(C_4 = \bigwedge_{1 \le c \le n, 1 \le v \le n}(\SUD{1}{c}{v} \lor \SUD{2}{c}{v} \lor ... \lor \SUD{n}{c}{v})\)

  • ​ Each block has all the numbers: \(C_5 = \bigwedge_{1 \le r' \le \sqrt{n}, 1 \le c' \le \sqrt{n}, 1 \le v \le n}(\bigvee_{(r,c) \in B_n(r',c')}\SUD{r}{c}{v})\) where \(B_n(r',c') = \Setdef{(r'\sqrt{n}+i,c'\sqrt{n}+j)}{0 \le i < \sqrt{n}, 0 \le j < \sqrt{n}}\)

  • ​ The solution respects the given cell values \(\SUDHints\): \(C_6 = \bigwedge_{(r,c,v) \in \SUDHints}(\SUD{r}{c}{v})\)

The resulting formula has \(\Card{\SUDHints}\) unary clauses, \(n^2\frac{n(n-1)}{2}\) binary clauses, and \(3n^2\) clauses of length \(n\). Thus the size of the formula is polynomial with respect to \(n\).

Example

Consider the following Sudoku grid:

_images/royle-plain.png

The corresponding CNF formula is:

\begin{eqnarray*} (x_{1,1,1} \lor x_{1,1,2} \lor ... \lor x_{1,1,9}) \land ... \land (x_{9,9,1} \lor x_{9,9,2} \lor ... \lor x_{9,9,9}) &\land&\\ (\neg x_{1,1,1} \lor \neg x_{1,1,2}) \land ... \land (\neg x_{9,9,8} \lor \neg x_{9,9,9}) &\land&\\ (x_{1,1,1} \lor x_{1,2,1} \lor ... \lor x_{1,9,1}) \land ... \land (x_{9,1,9} \lor x_{9,2,9} \lor ... \lor x_{9,9,9}) &\land&\\ (x_{1,1,1} \lor x_{2,1,1} \lor ... \lor x_{9,1,1}) \land ... \land (x_{1,9,9} \lor x_{2,9,9} \lor ... \lor x_{9,9,9}) &\land&\\ (x_{1,1,1} \lor x_{1,2,1} \lor ... \lor x_{3,3,1}) \land ... \land (x_{7,7,9} \lor x_{7,8,9} \lor ... \lor x_{9,9,9}) &\land&\\ (x_{1,8,1}) \land (x_{2,1,4}) \land (x_{3,2,2}) \land ... \land (x_{9,6,6})&& \end{eqnarray*}

When the formula is given to a SAT solver, it declares that the formula has a model in which all \(x_{1,1,i}\) except \(x_{1,1,6}\) are false, and so on. From the variables that are true, we obtain the following (unique) solution to the original Sudoku grid:

_images/royle-alldiff.png

An implementation of this approach, using the Sat4j SAT solver implemented in Java, is available in the Eclipse package for the “coloring with SAT” programming exercise. Feel free to compare its performance to the Sudoku solver you implemented in the CS-A1120 Programming 2 course.

Note

This approach of describing an instance of some other problem as a satisfiability instance, or an instance of some other similar constraint satisfaction formalism, is also called constraint programming.

It is a form of declarative programming: one only declares how the solution should look like and then a constraint solver finds one (or states that none exists).