前言:这是一篇算法题的AC代码。
我首先是在leetcode上刷到这道题,然而用例过少,因此在51nod上又做了一遍。作为Java代码,运行时间大约在900ms,效率无疑是十分低下的。
然而想要更进一步的优化,就只能选择舞蹈链,我尚未读懂……严格来说,思路是读懂了,代码还没有……所以先贴上这份AC代码作为日记,其中注释很详细,内置了若干数据,欢迎大神指点。
原题:51nod 题号1211 解数独。
import java.io.*;
import java.util.*;
// 本方案充分利用整数的数位进行判断,从而可以简单地通过一次合并逻辑与得到可选数值
// 但是在数据检测的重复性上处理存在重大缺失,尽管通过廉价的布尔值判断可以过滤掉
// 无意义的大量的重复的判定依然消费了太多计算量
// 接下来要做的事情就是读懂舞蹈链解数独的真正算法
public class Solution {
// 解决方案
List backdate = new ArrayList<>();
int[][] board;
public boolean solveSudoku(int[][] board) {
this.init(board);
if(this.tryBack(0) != 1) {
return false;
}
return true;
}
// 主递归函数,即便不增加剪枝函数也一样能完成任务,不过效率会低很多
private int tryBack(int index) {
if(index==backdate.size()) {
// 递归到列的终点说明成功解得一个数组
this.loadBoard();
return 1;
}
// e 是坐标约定,其二进制最后四位表示j,五到八位表示i
int e = backdate.get(index);
if(isExisted(e)) return tryBack(index+1);
// 数独中可选数值的数位表示
int selection = this.get(e);
// 约定 t 是数位表示转化为实值的结果,记录为0~8
int t = 0;
boolean hasAnswer = false;
while(selection>0) {
// 末位为0意味着这个数不可选
if((selection&1)==0) {
selection >>= 1;
t++;
continue;
}
// 尝试选中当前值
this.push(e, t);
// 尝试剪枝
if(!this.fastScaning(e)) {
// 剪枝失败时重置判定集已经在剪枝函数中完成,这里只需要重置当前选中值
this.pop(e);
selection >>= 1;
t++;
continue;
}
// 尝试递归
int tryNext = tryBack(index+1);
// 根据提议要求,多解、无解的情况都返回 No Solution 当存在多解时无需再对数据做任何处理
if(tryNext == -1) return -1;
if(tryNext == 1 && hasAnswer) return -1;
if(tryNext == 1) hasAnswer = true;
// 重置判定集,进入下一次尝试
this.clearResults();
this.pop(e);
selection >>= 1;
t++;
}
return hasAnswer ? 1 : 0;
}
// 剪枝成功时将这一批数据压入栈,主递归函数结束当前节点时需要通过栈读出数据并重置状态
private boolean fastScaning(int e) {
int i = e>>4;
int j = e&15;
List marks = new ArrayList<>();
if(this.fastScaning(i, j, marks)) {
scanStack.push(marks);
return true;
} else {
for(int e_ : marks) {
this.pop(e_);
}
return false;
}
}
// 快速剪枝,当坐标可选数字为0时,这是失败的数据。否则如果是1,则将其填充进去。
private boolean fastScaning(int i, int j, List marks) {
int[] xs = new int[21];
int[] ys = new int[21];
this.findNexts(xs, ys, i, j);
for(int p=0; p<21; p++) {
int x = xs[p];
int y = ys[p];
if(isExisted(x, y)) {
continue;
}
int vals = this.get(x, y);
int n = this.count(vals);
if(n == 0) {
return false;
} else if(n == 1) {
int t = this.convert(vals);
marks.add((x<<4)|y);
this.push(x, y, t);
if(!fastScaning(x, y, marks)) {
return false;
}
}
}
return true;
}
// 失败的处理逻辑 -> 意图通过当前坐标寻找行、列、九宫作为"接下来要寻找的数据" -> 却导致了大量的重复判定,舞蹈链则不存在这个问题
int[][] incolrow = {{3,4,5,6,7,8}, {0,1,2,6,7,8}, {0,1,2,3,4,5}};
int[][] insqr = {{0,1,2}, {3,4,5}, {6,7,8}};
private void findNexts(int[] xs, int[] ys, int i, int j) {
for(int x=0; x<6; x++) {
xs[x] = i;
ys[x] = incolrow[i/3][x];
}
for(int y=0; y<6; y++) {
xs[y+6] = incolrow[j/3][y];
ys[y+6] = j;
}
for(int x=0; x<3; x++) {
for(int y=0; y<3; y++) {
xs[(x*3+y)+12] = insqr[i/3][x];
ys[(x*3+y)+12] = insqr[j/3][y];
}
}
}
// 数据逻辑区
int[] row = new int[9];
int[] col = new int[9];
int[] sqr = new int[9];
// 返回当前i,j可选数字的数位表示
private int get(int e) {
return this.get(e>>4, e&15);
}
private int get(int i, int j) {
return row[i] & col[j] & sqr[(i/3*3)+(j/3)];
}
// 将给定数值加入到当前坐标的判定集中
private void push(int e, int num) {
int i = e>>4;
int j = e&15;
this.push(i, j, num);
}
private void push(int i, int j, int num) {
int b = ~(1<>4;
int j = e&15;
int b = (1<> scanStack = new Stack<>();
private boolean isExisted(int e) {
return this.isExisted(e>>4, e&15);
}
private boolean isExisted(int i, int j) {
return results[i][j]!=0;
}
private void addResult(int i, int j, int value) {
results[i][j] = value+1;
}
private int findResult(int i, int j) {
return results[i][j] - 1;
}
private void delResult(int i, int j) {
results[i][j] = 0;
}
private void clearResults() {
List marks = scanStack.pop();
for(int e : marks) {
this.pop(e);
}
}
private void loadBoard() {
for(int i=0; i<9; i++) {
for(int j=0; j<9; j++) {
board[i][j] = results[i][j];
}
}
}
// 工具区
// 对待处理列标进行排序,数据量足够小,采用冒泡排序即可
private void sort(List backdate) {
for(int i=0; ii; j--) {
int a = backdate.get(j-1);
int b = backdate.get(j);
if(bigThan(a, b)) {
backdate.set(j-1, b);
backdate.set(j, a);
}
}
}
}
private boolean bigThan(int sit1, int sit2) {
return this.count(get(sit1)) > this.count(get(sit2));
}
// 标准函数,统计整数在二进制位上有几个1
private int count(int m) {
int temp = m - ((m>>1)&033333333333) - ((m>>2)&011111111111);
return ((temp + (temp>>3)) & 030707070707) % 63;
}
// 当数位上有且只有一个1时,其位数恰好表示数独中唯一可选的值
private int convert(int p2n) {
int t = 0;
while(p2n!=1) {
t++;
p2n>>=1;
}
return t;
}
// 主函数处理区
private static void readsudo(int[][] sudo, Scanner scan) {
for(int i=0; i<9; i++) {
sudo[i] = new int[9];
for(int j=0; j<9; j++) {
sudo[i][j] = scan.nextInt();
}
}
}
private static void printSudo(int[][] sudo) throws Exception {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out), 1 << 16);
for(int[] cs : sudo) {
for(int c : cs) {
writer.write(c + " ");
}
writer.write("\r\n");
}
writer.flush();
}
public static void main(String args[]) throws Exception {
/*
String data =
"0 6 0 5 9 3 0 0 0\n"+
"9 0 1 0 0 0 5 0 0\n"+
"0 3 0 4 0 0 0 9 0\n"+
"1 0 8 0 2 0 0 0 4\n"+
"4 0 0 3 0 9 0 0 1\n"+
"2 0 0 0 1 0 6 0 9\n"+
"0 8 0 0 0 6 0 2 0\n"+
"0 0 4 0 0 0 8 0 7\n"+
"0 0 0 7 8 5 0 1 0";
*/
/*
String data =
"0 0 0 3 4 0 0 0 0\n"+
"8 0 0 0 0 0 0 2 0\n"+
"0 7 0 0 1 0 5 0 0\n"+
"4 0 0 0 0 5 3 0 0\n"+
"0 1 0 0 7 0 0 0 6\n"+
"0 0 3 2 0 0 0 8 0\n"+
"0 6 0 5 0 0 0 0 9\n"+
"0 0 4 0 0 0 0 3 0\n"+
"0 0 0 0 0 9 7 0 0";
*/
/*
String data =
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0\n"+
"0 0 0 0 0 0 0 0 0";
*/
/*
String data =
"0 0 0 4 0 0 0 0 7\n"+
"0 1 0 0 0 0 0 0 0\n"+
"0 0 0 0 2 0 0 0 0\n"+
"6 0 0 0 5 0 2 7 0\n"+
"0 0 0 0 0 0 8 3 0\n"+
"7 0 0 0 0 0 0 0 0\n"+
"2 0 6 0 0 0 5 0 0\n"+
"0 0 0 1 0 0 0 0 4\n"+
"0 0 0 3 0 0 0 0 0";
*/
/*
String data =
"0 6 0 5 9 3 0 0 0\n"+
"9 0 1 0 0 0 5 0 0\n"+
"0 3 0 4 0 0 0 9 0\n"+
"1 0 8 0 2 0 0 0 4\n"+
"4 0 0 3 0 9 0 0 1\n"+
"2 0 0 0 1 0 6 0 9\n"+
"0 8 0 0 0 6 0 2 0\n"+
"0 0 4 0 0 0 8 0 7\n"+
"0 0 0 7 8 5 0 1 0";
*/
/*
String data =
"7 6 2 5 0 3 1 4 8\n"+
"9 4 1 0 0 8 5 3 6\n"+
"8 3 5 4 0 0 7 9 2\n"+
"1 9 8 6 2 0 3 5 4\n"+
"4 7 6 3 5 0 2 8 1\n"+
"2 5 3 8 1 0 6 7 9\n"+
"3 8 7 1 4 0 9 2 5\n"+
"5 1 4 9 3 0 8 6 7\n"+
"6 2 9 7 8 0 4 1 3";
*/
// 51nod 题号 1211 数据格式
Scanner scan = new Scanner(new ByteArrayInputStream(data.getBytes()));
int[][] sudo = new int[9][];
readsudo(sudo, scan);
if(new Solution().solveSudoku(sudo)) {
printSudo(sudo);
} else {
System.out.println("No Solution");
}
}
}