数独这种游戏想必大家都玩过吧,对于一个9*9的方阵,每行每列都包含1~9共九个数字,一共有九个九宫格,每个九宫格也同样包含1~9共九个数字,根据已经填充的数字完成整个9*9的方阵。
基本思想:暴力破解,采用回溯法,一个一个数字尝试,利用一个堆栈保存数独的每个状态,对于这个堆栈我们可以定义为一个一维整型数组,数组中的数字代表已经填充过的第i行,第j列,大小为k的数字,可以使用一个三位数来保存这些信息,形式如下:
stack[top++] = i*100+j*10+k;
对于数独,我们可以使用一个二位数组表示,其中0表示为该处没有元素,具体代码如下所示:
import java.util.Scanner;
public class Sudoku {
int sudoku[][];
int blank; /*blank表示初始状况下的数独中空白元素的个数*/
/*构造函数初始化sudoku数组和blank*/
public Sudoku() {
sudoku = new int[9][9];
for(int i=0;i<9;i++) {
for(int j=0;j<9;j++) {
sudoku[i][j] = 0;
}
blank = 81;
}
}
/*按行读入并初始化数独*/
public void init() {
Scanner in = new Scanner(System.in);
for(int i=0;i<9;i++) {
for(int j=0;j<9;j++) {
sudoku[i][j] = in.nextInt();
if(sudoku[i][j]!=0) {
blank--;
}
}
}
}
/*利用已有数组初始化数独*/
public void init(int data[][]) {
for(int i=0;i<9;i++) {
for(int j=0;j<9;j++) {
sudoku[i][j] = data[i][j];
if(sudoku[i][j]!=0) {
blank--;
}
}
}
System.out.println(blank);
}
/*输出数独*/
public void printSudoku() {
for(int i=0;i<9;i++) {
for(int j=0;j<9;j++) {
System.out.print(sudoku[i][j]+" ");
}
System.out.println();
}
}
/*判断数字num是否存在于第row行*/
public boolean isInRow(int num,int row) {
for(int i=0;i<9;i++) {
if(sudoku[row][i]==num) {
return true;
}
}
return false;
}
/*判断数字num是否存在于第col列*/
public boolean isInCol(int num,int col) {
for(int i=0;i<9;i++) {
if(sudoku[i][col]==num) {
return true;
}
}
return false;
}
/*判断数字num是否存在于第row行,第col列所在的九宫格*/
public boolean isInCell(int num,int row,int col) {
int startRow = row/3*3;
int startCol = col/3*3;
int endRow = startRow+3;
int endCol = startCol+3;
for(int i=startRow;i<endRow;i++) {
for(int j=startCol;j<endCol;j++) {
if(sudoku[i][j]==num) {
return true;
}
}
}
return false;
}
/*求解函数*/
public boolean solve() {
int num = 0;
int data = 1;
int stack[] = new int[81]; //栈保存一个三位数,第一位代表行标,第二位代表列标,第三位代表该位置的值
int top = 0;
int i,j,k;
for(i=0;i<9;i++) {
for(j=0;j<9;j++) {
if(sudoku[i][j]==0) { //若该处元素为空则可以尝试填入1~9
for(k=data;k<=9;k++) {
if(!isInCell(k, i, j)&&!isInCol(k, j)&&!isInRow(k, i)) {
sudoku[i][j]=k;
stack[top++] = i*100+j*10+k; //填入元素后压栈,保存所填元素的基本信息
if(top==blank) {
return true;
}
data = 1;
break;
}
}
if(k==10) {
num = stack[--top]; //弹出上一个填入数独的元素信息,并重新求解
i = num/100;
j = num%100/10-1; //减一是因为在第二层for循环结束时i会自动加1
sudoku[i][j+1] = 0;
data = num%10+1; //从下一个数字开始重新尝试
}
}
}
}
return false;
}
public static void main(String[] args) {
int data[][] = {
{0,0,5,3,0,0,0,0,0},
{8,0,0,0,0,0,0,2,0},
{0,7,0,0,1,0,5,0,0},
{4,0,0,0,0,5,3,0,0},
{0,1,0,0,7,0,0,0,6},
{0,0,3,2,0,0,0,8,0},
{0,6,0,5,0,0,0,0,9},
{0,0,4,0,0,0,0,3,0},
{0,0,0,0,0,9,7,0,0}
};
Sudoku sudoku = new Sudoku();
sudoku.init(data);
boolean isfinished = sudoku.solve();
if(isfinished) {
sudoku.printSudoku();
}else {
System.out.println("ERROR!");
}
}
}
讨论:对于数独求解还有更为快速的方法,但同样的代码量会相对多一些,同时对于此程序还有优化的方法,比如说在初始化后进行前处理,把自由度为1的单元先进行求解,然后简化数独中的未知单元的个数。
我在示例中所使用的数独如下:https://baike.baidu.com/item/%E4%B8%96%E7%95%8C%E6%9C%80%E9%9A%BE%E6%95%B0%E7%8B%AC/13848819?fr=aladdin
有兴趣的朋友可以尝试着做一做!