100行代码求解数独游戏
最近笔者沉迷数独游戏,但慢慢就感觉了无生趣,因为有些确实太难了,所以想彻底丢掉这个游戏,但笔者呢,又有点小虚荣,一点都不想承认自己太弱鸡玩不了这个游戏,所以喽,嘿嘿嘿~ 就写了一个求解程序来K掉这个游戏,这样我就可以快快乐乐的丢掉这个游戏了。
那怎么做到呢?emmm…好像不容易呢…
但笔者已经被这个游戏搞得很烦躁了,实在不想动脑筋了,那就暴力破解吧, 九九八十一个格子好像也废不了多少时间。
不用动脑子真好,很容易就想出一个穷举的策略,那就是把空着的格子都找出来,然后把每个格子里可能的情况都罗列出来,然后找到正确的罗列组合就行,听起来很简单嘛,用图表示一种情况就是
2 6 //第一个空格
/ | \ / \
6 7 9 3 9 //第二个空格
/|\ /|\ /|\ /|\ /|\
... ... ... ... ... //第三个空格
............................... //第n个空格
.................................... //最后一个空格
|
正确结果
这个ugly的图示让我们想到了什么呢?虽然它很ugly,但它也能让我们顺利想到利用深度优先搜索遍历所有的情况。
来看一个例子(空格用数字0表示)
0 0 0 0 0 4 5 0 0
6 8 0 0 0 9 0 0 0
2 5 0 3 8 0 0 0 0
0 2 0 0 4 3 8 0 0
3 4 0 0 0 8 0 0 6
8 0 0 6 5 2 4 7 0
1 6 2 8 9 0 7 0 0
5 0 8 0 6 7 2 0 9
0 7 0 0 0 0 6 0 5
对第一个空着的格子,很显然可能的情况只有7、9,因为其他的数字在对应的行、列、九宫格中都出现过了,所以这启发我们如何确定可能情况呢,那就是对每行、每列、每个九宫格中出现的数字进行标记,那么对于空着的格子, 我们遍历一下看哪些数字没有在对应的行、列、九宫格被标记,那这个空格子可能的情况就是这些数字。所以我们来定义一个标记数字是否已经出现过的数据结构:
struct Book{
int row[10][10]; //记录每一行哪些数字已经出现过
int col[10][10]; //记录每一列哪些数字已经出现过
int block[10][10]; //记录每一个九宫格哪些数字已经出现过
};
/* 标记方法:如果数字一在第一行出现过,则标记row[1][1] = 1,其他情况以此类推
* (因为这样进行标记,所以二维数组应该申请为row[10][10])
*/
这样我们就定义好了标记的方法,那么,现在来思考一个问题,第一个空着的格子里理应为9,在我们在试7这种情况时,遍历剩下的空格子时会发生什么呢?
这个问题好像有那么一点点困难,但想通了这个问题那你就可以顺利写出整个程序喽!
会发生什么呢?因为数独的解是唯一的,所以在试7这种情况时必然会导致在遍历剩下的空格时出现一个空格连一个数都没法填的情况。想通了这一点程序就变得很简单,显然当一个格子没有数可填时,我们直接换下一种情况就行。所以没有必要罗列每个格子每一种可能的情况再检查最后罗列组合的正确性。很显然如果我们在当一个格子无数可填时进行下一种情况,那么那种能够把数填满的情况就是我们需要的答案,即我们最后也不用检查解的正确性,能填满的那种情况就是解。
理论就是这么个理论,那就快乐的开始编程吧。
#include <iostream>
#include <vector>
using namespace std;
//位置结构,用于记录空格的位置
typedef struct {
int row_num; //行号
int col_num; //列号
int block_num; //九宫格号(九宫格按照从左到右0, 1, 2, ..., 8编号)
}Position;
//标记结构
typedef struct {
int col[10][10];
int row[10][10];
int block[10][10];
}Book;
Book book;
int num[9][9]; //存储每个格子中的数
vector<Position> V; //存储所有的空格的位置
void initBook(); //初始化标记数组
void input(); //获取输入
int dfs(int i); //深度优先搜索
void showNum(); //展示结果
int main(void)
{
initBook();
input();
if (dfs(0)) {
showNum();
}
return 0;
}
void initBook()
{
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
book.block[i][j] = book.col[i][j] = book.row[i][j] = 0;
}
}
}
void input()
{
int t1;
Position t;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
cin >> num[i][j];
if (num[i][j] != 0) { //格子不为空则标记
book.row[i + 1][num[i][j]] = 1;
book.col[j + 1][num[i][j]] = 1;
t1 = (i / 3) * 3 + j / 3; //计算九宫格号
book.block[t1 + 1][num[i][j]] = 1;
} else { //格子为空则记录位置并将位置存储在V中
t.row_num = i;
t.col_num = j;
t.block_num = (i / 3) * 3 + j / 3;
V.push_back(t);
}
}
}
}
int dfs(int i)
{
if (i == V.size()) { //所有数全部填满if才会成功,返回1表示成功
return 1;
}
int row, col, block; //从V[i]中获取当前空格的行号、列号、九宫格号
row = V[i].row_num + 1;
col = V[i].col_num + 1;
block = V[i].block_num + 1;
for (int k = 1; k < 10; k++) { //从1遍历到9
//判断当前k能否被填入,如果对应的行、列、九宫格均未标记该数则填入
if (book.row[row][k] != 1 && book.col[col][k] != 1 && book.block[block][k] != 1) {
book.row[row][k] = book.col[col][k] = book.block[block][k] = 1; //标记该数被填
if (dfs(i + 1)) {
num[row - 1][col - 1] = k;
return 1;
} else {
//解除标记以用于下一种情况
book.row[row][k] = book.col[col][k] = book.block[block][k] = 0;
}
}
}
//如果没有数可以填入则返回0表示失败
return 0;
}
void showNum()
{
cout << "ans: " << endl;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
cout << num[i][j] << " ";
}
cout << endl;
}
}
欢迎点赞、收藏哦!