深搜(回溯)解决数独问题

目录

数独规则

模拟深搜(回溯)解决数独问题

代码实现

思路

代码


数独规则

数独(shù dú)是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复 [1]  。

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

数独规则

模拟深搜(回溯)解决数独问题

例如以下数独(0代表没有数字)
142379568
830560009
007800100
203000050
005080031
600000800
306900700
054030000
000051000

从第一行第一列(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;
}

  • 13
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是孤衾呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值