目录
数独规则
数独(shù dú)是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复 [1] 。
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
模拟深搜(回溯)解决数独问题
1 | 4 | 2 | 3 | 7 | 9 | 5 | 6 | 8 |
8 | 3 | 0 | 5 | 6 | 0 | 0 | 0 | 9 |
0 | 0 | 7 | 8 | 0 | 0 | 1 | 0 | 0 |
2 | 0 | 3 | 0 | 0 | 0 | 0 | 5 | 0 |
0 | 0 | 5 | 0 | 8 | 0 | 0 | 3 | 1 |
6 | 0 | 0 | 0 | 0 | 0 | 8 | 0 | 0 |
3 | 0 | 6 | 9 | 0 | 0 | 7 | 0 | 0 |
0 | 5 | 4 | 0 | 3 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 5 | 1 | 0 | 0 | 0 |
从第一行第一列(1,1)开始,它原本没有数,那么先假设它为1,然后判断:这行、这列、以及该方阵 是否还存在1,可以看到不存在,那么(1,1)的值就先定为1,继续判断(1,2)
(1,2)以及有数字了,那么就跳过,判断(1,3)
同样的,先假设它为1,然后判断:这行、这列、以及该方阵 是否还存在1
可以看到(1,1)是1了,那么继续假设它为2,然后判断:这行、这列、以及该方阵 是否还存在2,可以看到不存在,那么(1,3)的值就先定为2,继续判断(1,4)
就这样,一直假设下去,可以假设得出(1,4)为3,(1,5)为7,(1,6)为9,(1,7)为5,(1,8)为6,(1,9)为8
到此时,我们发现第一行刚好可以完美形成不重复,那么接下来,还是和刚才一样,判断第二行第一列(2,1)
(2,1)最开始没有数,那么就从1-9依次序假设,我们可以发现(2,1)只能为8,那么就只能假设它是8了
同理,一直判断下去,可以得出(2,2)只能为3。直到此时,我们发现(2,3)什么数都不行了。
这个时候,就说明我们之前的假设有问题,就依次返回,返回到(2,2),由于之前判断只能为3,那么继续返回,到(2,1),同理,(2,1)也只能为8,继续返回,同理,(1,9)也只能为8,继续返回。
直到(1,8),我们发现(1,8),不仅仅可以是6,还可以是8,然后就可以假设(1,8)是8,然后假设(1,9)......
就这样又假设下去,直到出现像刚才(2,3)什么都不行的情况,那么又返回。
重复刚才的步骤,除非返回到(1,1)什么都不行,那么该数独就无解,其余情况最终一定可以搜到一个结果(当然,答案可能不唯一,这样搜到的是最小的结果)
一共9*9个格子,所以极端情况,就是判断9^9=3 8742 0489 次,也就是最多运行3.87s。
对于我们来说3.87s很短暂,能3.87s解出数独,已经很快很快了,但是对于计算机来说,却是特别特别慢。没办法,毕竟是一个一个去找的,这样的结果也是最稳定最正确的。
代码实现
(可以拿去乱杀一切数独题,然后装B啦)(★ ω ★)
思路
就像刚才模拟那样,想必dfs搜索代码应该心里都有思路了吧。
然而,思路是有的,具体该怎么去实现?
比如说:怎么判断原本有没有数?
可以打三个二维的 bool 类型数组,以及一个 int 定义的二维数组,表示该数独。
bool hang[10][10],lie[10][10],fz[10][10];//做记录
//例如 hang[1][2]=1,代表第一行有数字2
// lie[3][4]=1,代表第3列有数字4
// fz[1][5]=1,代表方阵1有数字5
// hang[2][3]=0,代表第2行没有数字3
//最开始默认全部为0,也就是都没有
//所以在主函数输入的时候,记得要做一下记录
int sd[10][10];//代表数独的元素
//做记录
//其中(x,y)表示坐标,x为行,y为列
//sd[x][y]的值就表示数独的第x行第y列是多少
void record(int x,int y) {
hang[x][sd[x][y]]=1;//代表第x行有数字 sd[x][y]
lie[y][sd[x][y]]=1;//代表第y列有数字 sd[x][y]
fz[(x-1)/3*3+(y-1)/3+1][sd[x][y]]=1;//这是一个计算是第几方阵的公式
//可以试试,例如x=4,y=4,按理说(4,4)是第5方阵,带入公式计算确实为5
//所以就代表第[(x-1)/3*3+(y-1)/3+1]方阵有数字 sd[x][y]
}
//这样就完成了做记录
同样的,当判断到无法再填入的时候,就要返回上一个格子,并销毁记录了
//销毁记录,也就是全部置0就行
void derecord(int x,int y) {
hang[x][sd[x][y]]=0;
lie[y][sd[x][y]]=0;
fz[(x-1)/3*3+(y-1)/3+1][sd[x][y]]=0;
sd[x][y]=0;
}
比如说:怎么让电脑从1-9依次假设?
这好办,用for循环,从1-9依次判断
for(int i=1;i<=9;i++) {
}
所以深搜代码也就出来了
回溯法模板:
void Backtrack(原参数区间/或者参数) {
if(达到目的/或者撞到“南墙”) {
存放结果;//或者输出结果,输出最好调用新函数
return;
}
进行一些操作(根据实际情况可省去);
for(int i=1;i<=子区间个数(换句话说,也就是父亲节点的度);i++) {//或者子参数个数
if(没被记录) {
做记录;//已经搜过就不再搜了
Backtrack(子区间或者下个搜索的参数); //调用子区间或者参数
销毁记录;//搜完,或者撞到南墙,就销毁记录
}
}
}
void Backtrack(int x,int y) {
//当这个数原本就有
if(sd[x][y]!=0) {
//当搜到(9,9)时候,全部已经弄完,就输出结果
if(x==9 && y==9) print1();
//当搜到(x,9)时,说明这一行已经搜完了,该搜下一行了
else if(y==9) Backtrack(x+1,1);
//以上都不满足就搜这行的下一个数
else Backtrack(x,y+1);
}
//当这个数原本没有
else {
//循环遍历,依次输入1-9,一次一次试。
for(int i=1;i<=9;i++) {
//如果这行,列,方阵都没有i这个数,就可以假设它是i
if((!hang[x][i]) && (!lie[y][i]) && (!fz[(x-1)/3*3+(y-1)/3+1][i])) {
//先假设是i
sd[x][y]=i;
// 做记录
record(x,y);
//当搜到(9,9)时候,全部已经弄完,就输出结果
if(x==9 && y==9) print1();
//当搜到(x,9)时,说明这一行已经搜完了,该搜下一行了
else if(y==9) Backtrack(x+1,1);
//以上都不满足就搜这行的下一个数
else Backtrack(x,y+1);
//下一个格子1-9都找不到,说明本次假设不成立,就销毁已经做了的记录,并假设是下一个数
derecord(x,y);
}
}
//1-9都假设完了,发现还不满足,说明上一个格子假设有问题,返回上一个格子
return;
}
}
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
bool hang[10][10],lie[10][10],fz[10][10];//做记录
//例如 hang[1][2]=1,代表第一行有数字2
// lie[3][4]=1,代表第3列有数字4
// fz[1][5]=1,代表方针1有数字5
int sd[10][10];//代表数独的元素
// 输出
void print1() {
for(int i=1;i<=9;i++) {
for(int j=1;j<=9;j++) {
cout<<sd[i][j]<<" ";
}
cout<<endl;
}
system("pause");
exit(0);
}
//做记录
void record(int x,int y) {
hang[x][sd[x][y]]=1;
lie[y][sd[x][y]]=1;
fz[(x-1)/3*3+(y-1)/3+1][sd[x][y]]=1;
}
//销毁记录
void derecord(int x,int y) {
hang[x][sd[x][y]]=0;
lie[y][sd[x][y]]=0;
fz[(x-1)/3*3+(y-1)/3+1][sd[x][y]]=0;
sd[x][y]=0;
}
void dfs(int x,int y) {
//当这个数原本就有
if(sd[x][y]!=0) {
//当搜到(9,9)时候,全部已经弄完,就输出结果
if(x==9 && y==9) print1();
//当搜到(x,9)时,说明这一行已经搜完了,该搜下一行了
else if(y==9) dfs(x+1,1);
//以上都不满足就搜这行的下一个数
else dfs(x,y+1);
}
//当这个数原本没有
else {
//循环遍历,依次输入1-9,一次一次试。
for(int i=1;i<=9;i++) {
//如果这行,列,方阵都没有i这个数,就可以假设它是i
if((!hang[x][i]) && (!lie[y][i]) && (!fz[(x-1)/3*3+(y-1)/3+1][i])) {
//先假设是i
sd[x][y]=i;
// 做记录
record(x,y);
//当搜到(9,9)时候,全部已经弄完,就输出结果
if(x==9 && y==9) print1();
//当搜到(x,9)时,说明这一行已经搜完了,该搜下一行了
else if(y==9) dfs(x+1,1);
//以上都不满足就搜这行的下一个数
else dfs(x,y+1);
//下一个格子1-9都找不到,说明假设不成立,就销毁已经做了的记录,就假设是下一个数
derecord(x,y);
}
}
//1-9都假设完了,发现还不满足,说明上一个格子假设有问题,返回上一个格子
return;
}
}
int main() {
for(int i=1;i<=9;i++) {
for(int j=1;j<=9;j++) {
cin>>sd[i][j];
// 不是0就做记录
if(sd[i][j]) {
record(i,j);
}
}
}
dfs(1,1);
cout<<"该数独无解"<<endl;
system("pause");
return 0;
}