数独游戏是个经典的游戏。现在我要通过程序的方法才自动求解一个数独。这个问题在《Puzzles for Programmers and Pros》一书中有讲解。
思路:
最直接的方法是,可以用backtracking进行递归尝试。一旦探测到错误就回溯,最终将在递归的最深处发现正解。
注意backtracking是可以用引用类型参数进行递归深入的,但是递归回溯出来的时候一定要将改变的值回复回原样,这样下一轮的尝试才不会受到影响。
纯粹的递归尝试就相当于穷举法,速度非常慢。想想人脑做数独的时候是怎么进行的?肯定不是上面的方法。而是先找出最容易写出答案的。对于一个位置,如果发现其同行、同列、同方格中已经把9个数的8个都使用过了,那么必然的,这个位置具有唯一的答案。依照这个方法进行重复,可以先把数独填上若干。
当找不到具有唯一确定答案的位置的时候,意味着就必须要尝试性的推测了。这时候,我就找推测范围最小的位置进行尝试。因为范围越小,成功概率越大。这样可以尽量减少错误的递归次数。
代码:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<char> checker(vector<vector<char> > &board, int row, int col)
{
vector<char> posibile;
int bitmap[9];
memset(bitmap, 0, sizeof(bitmap));
int i;
for(i=0;i<9;i++)
if(board[row][i] != '.')
bitmap[board[row][i] -'1'] = 1;
for(i=0;i<9;i++)
if(board[i][col] != '.')
bitmap[board[i][col] - '1'] = 1;
int row_base = (row/3)*3;
int col_base = (col/3)*3;
for(int i = 0;i<3;i++)
for(int j=0;j<3;j++)
{
if(board[row_base + i][col_base + j] != '.')
bitmap[board[row_base + i][col_base + j] - '1'] = 1;
}
for(i=0;i<9;i++)
if(bitmap[i] == 0)
{
posibile.push_back(char('1' + i));
}
return posibile;
}
bool fun(vector<vector<char> > &board)
{
size_t min = 10;
vector<char> minoption;
int mini, minj;
// 寻找一个推测范围最小的位置
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
if(board[i][j] != '.')
continue;
vector<char> option = checker(board, i, j);
if(option.size() < min)
{
min = option.size();
minoption = option;
mini = i;
minj = j;
}
}
if(min == 10)
return true; //min没有改变,可见所有都填满
// 对推测范围最小的位置进行递归尝试
for(int k=0;k<minoption.size();k++)
{
board[mini][minj] = minoption[k];
if(fun(board))
return true;
board[mini][minj] = '.';
}
return false; //这儿很关键!!因为并非option里一定有正确答案,因为可能是之前的数放错了
}
void solveSudoku(vector<vector<char> > &board)
{
if(board.size() != 9)
return;
bool flag = true;
while(flag)
{
flag = false;
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(board[i][j] == '.')
{
vector<char> tmp = checker(board, i, j);
if(tmp.size() == 1)
{
board[i][j] = tmp[0];
flag = true;
}
}
}
}
}
fun(board);
}
void show(vector<vector<char> > &board)
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
cout<<board[i][j]<<" ";
cout<<endl;
}
cout<<endl;
}
int main(int argc, const char * argv[])
{
vector<vector<char> > board;
vector<char> t;
string s[9] = {"..9748...","7........",".2.1.9...","..7...24.",".64.1.59.",".98...3..","...8.3.2.","........6","...2759.."};
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
t.push_back(s[i][j]);
board.push_back(t);
t.clear();
}
show(board);
cout<<"start..."<<endl;
solveSudoku(board);
cout<<"over"<<endl;
show(board);
return 0;
}