Java版本的DLX解决数独算法

元气小坏坏 提交于 2020-01-30 07:30:59

DLX又称作精确覆盖问题,而今天要说的就是把解数独这个问题转化为一个精确覆盖问题,然后来解决它。
其实这是一个很好的解决问题的思路,很多问题都可以转化为一个精确覆盖问题,然后用接下来这个算法来解决它。
关于DLX的思想,网上有不少的解释,这里我就不再赘述,我会在我的另一篇博客里讲解原理。

首先是整个代码的结构。
总共分为四个类,分别是:

  1. 主类(StartPoint)
  2. 转换器类(Transfer)
  3. 节点类(Node)
  4. 求解器类(Solver)

这就是我们的代码的主要结构,下面我们将展示代码的细节。

主类(StartPoint)

这是主类,也是整个程序的入口,负责从文件读取数据,调用各种其他类,然后输出结果到文件中,具体如下:

package dLX;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Vector;

public class StartPoint {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File input=new File("D:\\problem.txt");
		File output  = new File("D:\\solution.txt");
		try {
			output.createNewFile();
			FileReader in=new FileReader(input);
			FileWriter out=new FileWriter(output);
			char problem[]=new char[(int)input.length()];
			in.read(problem);
			if(problem.length<81){
				out.write("输入不合法");
				in.close();
				out.close();
			    return;
			}
			Transfer tsf= new Transfer();
			Vector<Vector<Integer>> matrix = tsf.sudoku2matrix(problem);// 矩阵转换 
			Solver sudoku=new Solver(matrix,matrix.size(),324);//初始化解题类 
			
			if (!sudoku.Search(0)){//调用解题函数 
				out.write("无解");
				in.close();
				out.close();
			    return;
			}
			Vector<Integer> solution = tsf.matrix2sudoku(matrix, sudoku.getResult());
			for (int ix = 0; ix < 81; ++ix){ 
				out.write(solution.get(ix)+""); 
				out.write((ix+1)%9!=0?"" : "\n");
			 }
			 in.close();
			 out.close();
			    
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

}

节点类(Node)

因为DLX很关键的一个数据结构是链表,所以就需要节点,这里我们就写了一个节点类:

package dLX;

public class Node {
	Node up, down, left, right, colRoot, rowRoot;
	int Num;//行对象特有,记录行数
    int Size;//列对象特有,记录该列元素数
    
    public Node(){
    	Size=0;
    	Num=-1;
    }
    
    public Node(int i){
    	Size=0;
    	Num=i;
    }
}

转换器类

因为我们人看到的数独矩阵并不是我们解题需要的01矩阵,所有我们需要一个转换的类,这个类负责把输入的数独矩阵转换成01矩阵便于解题,当解题结束时,再负责把结果转换成便于人类阅读的数独矩阵,这就是这个类的作用:

package dLX;

import java.util.Vector;

public class Transfer {
	Vector<Vector<Integer>> sudoku2matrix(char[] problem){
		Vector<Vector<Integer>> matrix=new Vector<Vector<Integer>>();
	    for (int ix = 0; ix < 81; ++ix){ 
	        int val = problem[ix] - '0';
	        Vector<Integer> current_row=new Vector<Integer>();
	        for(int i=0;i<324;i++){
	        	current_row.add(new Integer(0));
	        }
	        if (val != 0){
	            current_row.set(ix, new Integer(1));
	            current_row.set(81 + ix/9*9 + val -1, new Integer(1));
	            current_row.set(162 + ix%9*9 +val -1, new Integer(1));
	            current_row.set(243 + (ix/9/3*3+ix%9/3)*9 +val -1, new Integer(1));
	            matrix.add(current_row);
	            continue;
	        }
	        for (int jx = 0; jx < 9; ++jx){
	        	Vector<Integer> current_row2=new Vector<Integer>();
	        	for(int i=0;i<324;i++){
		        	current_row2.add(new Integer(0));
		        }
	        	current_row2.set(ix, new Integer(1));
	            current_row2.set(81 + ix/9*9 + jx, new Integer(1));
	            current_row2.set(162 + ix%9*9 +jx , new Integer(1));
	            current_row2.set(243 + (ix/9/3*3+ix%9/3)*9 +jx , new Integer(1));
	            matrix.add(current_row2);
	        }
	    }
	    return matrix;
	}
	
	
	Vector<Integer> matrix2sudoku(Vector<Vector<Integer>> matrix, Vector<Integer> result){
		Vector<Integer> solution=new Vector<Integer>();
		for(int i=0;i<81;i++){
			solution.add(new Integer(0));
		}
	    for (int ix = 0; ix < 81; ++ix){
	    	Vector<Integer> current = matrix.get(result.get(ix)-1);
	        int pos = 0, val = 0;
	        for (int jx = 0; jx < 81; ++jx){
	            if (current.get(jx) == 1){
					break;
				} 
	            ++pos;
	        }
	        for (int kx = 81; kx < 162; ++kx){
	            if (current.get(kx) == 1)
	                break;
	            ++val;
	        }
	        solution.set(pos, val%9 + 1);
	    }
	    return solution;
	}
}

求解器类

这个类就是解决问题的核心了,它负责核心的解决问题,接收转换类转换得到的01矩阵来解决问题,提供结果,具体代码如下:

package dLX;

import java.util.Vector;

public class Solver {
	Node Head;
	Vector<Integer> result;
	int _row, _col, _updates;
	public static final int INT_MAX=2147483647;
	
	public Solver(Vector<Vector<Integer>> matrix, int m, int n){
		result=new Vector<Integer>();
		Head = new Node();
		Head.up = Head;
		Head.down = Head;
		Head.right = Head;
		Head.left = Head;
		_row=m;
		_col=n;
		_updates=0;
    	init();
    	link(matrix);
	}

	void init(){
	    Node newNode;
		//表头位置向后插入,构造列对象
	    for (int ix = 0; ix < _col; ++ix){
	        newNode = new Node();
	        newNode.up = newNode;
	        newNode.down = newNode;
	        newNode.right = Head.right;
	        newNode.left = Head;
	        newNode.right.left = newNode;
	        Head.right = newNode;
	    }
	    //表头位置向下插入,构造行对象
	    for (int ix = 0; ix < _row; ++ix){
	        newNode = new Node(_row-ix);//注意序号是_row-ix
	        newNode.down = Head.down;
	        newNode.up = Head;
	        newNode.down.up = newNode;
	        Head.down = newNode;
	    }
	}
	
	void link(Vector<Vector<Integer>> matrix){
	    Node  current_row,  current_col,  newNode,  current;//当前行对象,当前列对象,新节点,当前节点
	    current_row = Head;
	    for (int row = 0; row < _row; ++row){
	        current_row = current_row.down;
	        current_col = Head;
	        for (int col = 0; col < _col; ++col){
	            current_col = current_col.right;
	            if (matrix.get(row).get(col) == 0)//矩阵上为0的位置不设置节点
	                continue;
	            newNode = new Node();
	            newNode.colRoot = current_col;
	            newNode.rowRoot = current_row;//设置当前节点对应的行列对象
	 
	            newNode.down = current_col;
	            newNode.up = current_col.up;
	            newNode.up.down = newNode;
	            current_col.up = newNode;//链接当前节点到列双向链尾端
	 
	            if (current_row.Size == 0){//行双向链不应该把行对象包含进来
	                current_row.right = newNode;
	                newNode.left = newNode;
	                newNode.right = newNode;
	                current_row.Size++;
	            }
	            current = current_row.right;//设置当前节点(即行对象右的节点)
	            newNode.left = current.left;
	            newNode.right = current;
	            newNode.left.right = newNode;
	            current.left = newNode;//链接当前节点到行双向链尾端
	 
	            current_col.Size++;
	        }
	    }
	}
	
	void cover(Node  cRoot){//覆盖列
	    ++_updates;
	    cRoot.left.right = cRoot.right;
	    cRoot.right.left = cRoot.left;//删除该列对象
	    Node  i,  j;
	    i = cRoot.down;
	    while (i != cRoot){
	        j = i.right; 
	        while (j != i){
	            j.down.up = j.up;
	            j.up.down = j.down;
	            j.colRoot.Size--;
	            j = j.right;
	        }
	        i = i.down;
	    }
	}
	
	void recover(Node  cRoot){
	    Node  i,  j;
	    i = cRoot.up;
	    while (i != cRoot){
	        j = i.left;
	        while (j != i){
	            j.colRoot.Size++;
	            j.down.up = j;
	            j.up.down = j;
	            j = j.left;
	        }
	        i = i.up;
	    }
	    cRoot.right.left = cRoot;
	    cRoot.left.right = cRoot;
	}
	
	boolean Search(int k){
		
	    if (Head.right == Head)//列空,则成功找到一组行的集合
	        return true;
	    
	    Node  cRoot,  c;
	    cRoot=new Node();
	    int minSize = INT_MAX;
	    for(c = Head.right; c != Head; c = c.right){//选择情况最少的列进行尝试 
	        if (c.Size < minSize){
	            minSize = c.Size;
	            cRoot = c;
	            if (minSize == 1){
	            	break;
	            }
	                
	            if (minSize == 0){//有一列为空,失败
	            	return false;
	            }
	        }
	    }
	    cover(cRoot);//覆盖这一列 
	 
	    Node  current_row, current;
	    for (current_row = cRoot.down; current_row != cRoot; current_row = current_row.down){
	        result.add(current_row.rowRoot.Num);//将该行加入result中
	        for (current = current_row.right; current != current_row; current = current.right){
	            cover(current.colRoot);//delete other c
	        }
	        if (Search(k+1)){
	        	return true;
	        }
	        for (current = current_row.left; current != current_row; current = current.left)
	            recover(current.colRoot);//还原 
	        	result.remove(result.size()-1);//发现该行不符合要求,还原result
	    }
	    recover(cRoot);
	    return false;
	}
	
	
	Vector<Integer> getResult(){ 
		return result;
	}
	
	int getUpdates(){ 
		return _updates;
	}
	
	
	
	
	
}

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!