BackGround:
做完LeetCode上的数独题目好长时间了,今天将做题时参考的Algorithm X 以及 Dancing Links 整理出来。话说理解算法+写出程序一共用了三天,智商果然余额不足。。。
介绍:
由于Dancing Links 是为了解决数独问题学习的,那就从数独问题下手,围绕数独问题展开对Algorithm X 和 Dancing Links的介绍,最后将数独问题的解法以及Java源码放出来。
精确覆盖问题:
没错,解数独的问题就是一个解 精确覆盖 问题的过程,即数独问题是精确覆盖问题的一个特例。
精确覆盖问题可以用下面的例子来说明:
假设有集合S = { a , b , c , d , e , f } , 还有一些S的子集 S1 = { a , c , f} , S2 = {b , d } , S3 = { e } , S4 = { b , d , f , e} ,从这些子集中选出n个集合,使得这些集合的并集为S , 并且子集中的元素有且只能出现一次。很容易看出我们可以选择S1 、 S2 、 S3 。 但是不能选择 S1、 S4 因为f元素重复出现了。
精确覆盖问题的形式化表达:
我们可以用一个01矩阵来形式化表达精确覆盖问题,还是上面的例子,我们可以表达为如下的矩阵:
每一行代表一个子集,那么问题就是选择某些行,使得a-f列出现且仅出现一个1。
精确覆盖问题的解法:
Knuth大神给出的解法就是Algorithm X 。 其思想就是从01矩阵入手。解法的过程(不考虑回溯的次数多少)为:
设01矩阵为A
如果 A 是空的,
问题解决;成功终止。
否则,
选择一个列 c(确定的)
选择一个行 r,满足 A[r, c]=1 (不确定的)
把 r 包含进部分解
对于所有满足 A[r,j]=1 的 j
从矩阵 A 中删除第 j 列;
对于所有满足 A[i,j]=1 的 i,
从矩阵 A 中删除第 i 行。
在不断减少的矩阵 A 上递归地重复上述算法。
这个算法的有点麻烦,其实过程很简单。用上面的例子来解释就是
1、选择一个列,这里我们选择a列,找到这一列中有1的所有行,记为row1 , row2 , row3....
2、首先选择row1作为解的一部分,这里为选择第一行,然后将这一行中所有 为1的 列删除(这个行中已经满足了这些列有1)。在上面的例子中就是a , c , f列。
3、在删除a 、 c 、 f 列的过程中,还要删除这一列中有1的行(避免当前列中重复出现1)。以删除f列为例,由于选择第一行了,所以f列的1不能重复出现,即最后一行不能再选择了,所以要删除最后一行。
4、重复上面的1-3步骤,如果矩阵最后为空,那么问题已经解决。否则,说明row1不能选择,我们再回溯问题,不选择row1,选择row2。
X算法的实现:
算法有了,那么怎么实现呢?我们可以递归解决问题,每一步复制一个矩阵,但是如果搜索次数很多的话,需要的空间会比较大。所以又有了Dancing links 的实现(后面叫DLX)。DLX虽然听起来很麻烦,但是他实现起来确实麻烦-,-!本质上DLX就是一个双向十字链表。如下(图是挖别人的。。):
h是整个链表的表头,A、B、.....G是每一列的列头,其他的是节点。用这个链表来代表上述的01矩阵(这一步应该很容易理解)。然后将矩阵的操作转化为链表的操作。
用DLX的好处是:
1、X算法在链表的列头上进行搜索。==========》如果需要删除某一列,直接删除对应的列头即可。
2、可以方便的进行恢复,假如要删除C列,只需要两个操作便可删除
C.L.R = C.R
C.R.L = C.L
恢复时
C.R.L = C.L
C.L.R = C.R
3、扫描列是否都满足时,不必扫描全部的列,只需判断表头.L 是否 表头即可。
代码:
用Java写的LeetCode的解题数独的解题代码。
public class Solution {
private static final int colmns = 324; //9 * 9 * 3 + 81
private Node head;
private Node[] cols;
private int[] solution = new int[81];
private boolean flag =false;
String sudoku = "53..7...." +
"6..195..." +
".98....6." +
"8...6...3" +
"4..8.3..1" +
"7...2...6" +
".6....28." +
"...419..5" +
"....8..79";
char[][] sudoku1;
private int[][] sudokuArray = new int[9][9];
THE LEETCODE MAIN//
public void solveSudoku(char[][] board) {
StringBuilder sb = new StringBuilder();
for(char[] t : board){
for(char tt : t) {
sb.append(tt);
}
}
sudoku1 = board;
init();
construct(sb.toString());
search(0);
}
///
public void init(){
head = new Node();
head.L = head;
head.R = head;
cols = new Node[colmns];
for(int i = 0 ; i < cols.length ; i++){
Node temp = new Node();
temp.C = temp;
temp.U = temp;
temp.D = temp;
temp.R = head;
temp.L = head.L;
temp.r = i;
head.L.R = temp;
head.L = temp;
cols[i] = temp;
}
}
public void link(int r , int[] cs){
Node rowHead = null;
for(int i = 0 ; i < cs.length ; i++){
int c = cs[i];
Node columnHeader = cols[c].C;
Node temp = new Node();
temp.r = r;
if(i == 0){
rowHead = temp;
rowHead.R = rowHead;
rowHead.L = rowHead;
}
///
temp.R = rowHead;
temp.L = rowHead.L;
rowHead.L.R = temp;
rowHead.L = temp;
///
temp.D = columnHeader;
temp.U = columnHeader.U;
//set the columnHeader
temp.C = columnHeader;
columnHeader.U.D = temp;
columnHeader.U = temp;
columnHeader.count++;
}
}
/**
* remove the column c
* @param c the column obj
*/
public void remove(Node c){
//The column head
Node cHead = c.C;
cHead.R.L = cHead.L;
cHead.L.R = cHead.R;
//System.out.println("remove -----" + cHead.r);
//delete the row of this column
for(Node row = cHead.D ; row != cHead ; row = row.D){
//delete the row
for(Node rowNode = row.R ; rowNode != row ; rowNode = rowNode.R){
rowNode.D.U = rowNode.U;
rowNode.U.D = rowNode.D;
rowNode.C.count--;
}
}
}
/**
* resume the column
* @param c
*/
public void resume(Node c){
Node cHead = c.C;
//resume the row
for(Node row = cHead.U ; row != cHead ; row = row.U){
for(Node rowNode = row.L ; rowNode != row ; rowNode = rowNode.L){
rowNode.D.U = rowNode;
rowNode.U.D = rowNode;
rowNode.C.count++;
}
}
//resume the head
cHead.R.L = cHead;
cHead.L.R = cHead;
}
/**
*
* @return the column header
*/
public Node chooseColumn(){
int min = Integer.MAX_VALUE;
Node result = null;
for(Node c = head.R ; c != head ; c = c.R){
if(c.count < min){
result = c;
min = c.count;
}
}
return result;
}
public boolean search(int k){
//System.out.println(java.util.Arrays.toString(solution));
if(flag == true)
return true;
if(head.R == head){
//System.out.println(java.util.Arrays.toString(solution));
printSolution1();
flag = true;
return true;
}
Node c = chooseColumn();
remove(c); //remove the c
for(Node solutionRow = c.D ; solutionRow != c ; solutionRow = solutionRow.D){
solution[k] = solutionRow.r; //add the solution
//remove the column in the right
for(Node rightNode = solutionRow.R ; rightNode != solutionRow ; rightNode = rightNode.R){
remove(rightNode);
}
//continue searching
search(k+1);
//after searching , resume the state
for(Node leftNode = solutionRow.L ; leftNode != solutionRow ; leftNode = leftNode.L){
resume(leftNode);
}
}
resume(c);
return false;
}
/**
* Construct the two direction circular linked list according to the puzzle which is in the form of
* 2.2.2.2......
* @param puzzle
*/
public void construct(String puzzle){
if(puzzle.length() != 81){
return;
}
for(int i = 0 ; i < puzzle.length() ; i++){
char current = puzzle.charAt(i);
int r = i / 9 ;
int c = i%9;
if(current == '.'){
for(int val = 1 ; val <= 9 ; val++){
link(r*100 + c * 10 + val , getColumnIndex(r,c,val));
}
}else{
int number = current - '0';
link(r*100 + c * 10 + number , getColumnIndex(r,c,number));
}
}
}
public void construct(char[][] puzzle){
for(int pr = 0 ; pr < puzzle.length ; pr++){
for(int pc = 0 ; pc < puzzle[pr].length ; pc++){
int i = pr * 9 + pc;
char current = puzzle[pr][pc];
int r = i / 9 ;
int c = i%9;
if(current == '.'){
for(int val = 1 ; val <= 9 ; val++){
link(r*100 + c * 10 + val , getColumnIndex(r,c,val));
}
}else{
int number = current - '0';
link(r*100 + c * 10 + number , getColumnIndex(r,c,number));
}
}
}
}
/**
* given the row and column and the value , return the four column index of it
* @param r
* @param c
* @param val
* @return System.out.println(java.util.Arrays.toString(solution));
*/
public int[] getColumnIndex(int r , int c , int val){
int[] array = new int[4];
array[0] = r * 9 + val - 1;
array[1] = 81 + c * 9 + val - 1;
// int b = (r / 3) * 3 + c % 3;
int tr = r / 3;
int tc = c / 3;
int b = tr * 3 + tc;
array[2] = 162 + b * 9 + val - 1;
array[3] = 243 + r * 9 + c;
return array;
}
public void printSolution(){
for(int i = 0 ; i < solution.length ; i++){
int r = solution[i] / 100;
int c = solution[i] / 10 % 10;
int val = solution[i] % 10;
sudokuArray[r][c] = val;
}
for(int[] rr : sudokuArray){
for(int vv : rr){
System.out.print(vv + " ");
}
System.out.println();
}
}
public void printSolution1(){
for(int i = 0 ; i < solution.length ; i++){
int r = solution[i] / 100;
int c = solution[i] / 10 % 10;
int val = solution[i] % 10;
sudoku1[r][c] = (char)(val + '0');
}
}
}
class Node{
int r; //represent the solution ****rcv****
//direction pointer
Node U;
Node D;
Node L;
Node R;
//---------
Node C; //the header
int count; //Used to store the count of 1 in column
}