洛谷 数独 详细解析

文章介绍了数独游戏的基本规则和一个利用深度优先搜索(DFS)策略解决数独谜题的方法。通过初始化矩阵来跟踪行、列和宫的数字状态,在无法填充时进行回溯。代码示例展示了如何实现这一过程,包括处理已给出的初始数字和递归填充空格,直到找到唯一解。
摘要由CSDN通过智能技术生成

数独

题目描述

数独是根据 9 × 9 9 \times 9 9×9 盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含 1 − 9 1 - 9 19 ,不重复。每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。

芬兰一位数学家号称设计出全球最难的“数独游戏”,并刊登在报纸上,让大家去挑战。

这位数学家说,他相信只有“智慧最顶尖”的人才有可能破解这个“数独之谜”。

据介绍,目前数独游戏的难度的等级有一到五级,一是入门等级,五则比较难。不过这位数学家说,他所设计的数独游戏难度等级是十一,可以说是所以数独游戏中,难度最高的等级。他还表示,他目前还没遇到解不出来的数独游戏,因此他认为“最具挑战性”的数独游戏并没有出现。

输入格式

一个未填的数独。

输出格式

填好的数独。

样例 #1

样例输入 #1

8 0 0 0 0 0 0 0 0 
0 0 3 6 0 0 0 0 0 
0 7 0 0 9 0 2 0 0 
0 5 0 0 0 7 0 0 0 
0 0 0 0 4 5 7 0 0 
0 0 0 1 0 0 0 3 0 
0 0 1 0 0 0 0 6 8 
0 0 8 5 0 0 0 1 0 
0 9 0 0 0 0 4 0 0

样例输出 #1

8 1 2 7 5 3 6 4 9 
9 4 3 6 8 2 1 7 5 
6 7 5 4 9 1 2 8 3 
1 5 4 2 3 7 8 9 6 
3 6 9 8 4 5 7 2 1 
2 8 7 1 6 9 5 3 4 
5 2 1 9 7 4 3 6 8 
4 3 8 5 2 6 9 1 7 
7 9 6 3 1 8 4 5 2

提示

2022-04-17 @farteryhr 贡献了三组 hack 数据。加入了其中两组。第三组过强(来源:https://www.dcc.fc.up.pt/~acm/sudoku.pdf),放在下边供自测。

9 0 0 8 0 0 0 0 0
0 0 0 0 0 0 5 0 0 
0 0 0 0 0 0 0 0 0 
0 2 0 0 1 0 0 0 3
0 1 0 0 0 0 0 6 0
0 0 0 4 0 0 0 7 0
7 0 8 6 0 0 0 0 0 
0 0 0 0 3 0 1 0 0 
4 0 0 0 0 0 2 0 0 

输出

9 7 2 8 5 3 6 1 4 
1 4 6 2 7 9 5 3 8 
5 8 3 1 4 6 7 2 9 
6 2 4 7 1 8 9 5 3 
8 1 7 3 9 5 4 6 2 
3 5 9 4 6 2 8 7 1 
7 9 8 6 2 1 3 4 5 
2 6 5 9 3 4 1 8 7 
4 3 1 5 8 7 2 9 6 

不再赘述数独游戏的规则

思路

见到这种填数题,大概率需要用到深度优先搜索,其大致思路就是暴力枚举,如果当前情况下这种填法无法满足题意,便会回溯寻找下一种情况,持续下去,一定会搜索到满足题意的情况
假设我们先填了1,然后下一个点尝试1,不成功便尝试2,一直到9,如果都不行,说明先填的1存在问题,将先填入的1改成2,这样循环下去。

由于这道题建立在数独游戏的背景之上,所以我们需要关注游戏规则。数独要求每一行,每一列,每一宫不能有同样的数字出现,可以填入的数字为1到9,那么我们可以设置四个数组

int hang[9][10]={0},
lie[9][10]={0},
pand[9][10]={0},//即数字盘,先初始化为0
gong[3][3][10] = {0};

其中,hang[x][i] = 0代表i这个数字在第x行没有被填过,lie[y][i] = 0代表i这个数字没有在第y列出现过
需要注意的是对于宫数的判断,我可以举一个例子,我们数盘第一行第一列的数字坐标为(0,0)那么对于第一行第三列的数字来说,它的坐标为(0,2)。为什么纵坐标为2而不是3呢?因为我们的起始坐标为0,对于这个点来说它属于第一宫,对应gong[0][0],他紧接着右边的点属于第二宫,也就是说(0,3)对应第二宫,也就是gong[0][1]所以我们可以得到宫数组的定义和表达式
gong[x/3][y/3][i] = 0代表着i这个数字没有在对应宫出现过。这里的x和y不会等于9,因为x是从零开始,最多等于8

既然要用到深度优先搜索,我们需要先考虑回溯的条件,我们可以确定横坐标然后搜索纵坐标。如果正好完成了整个数独,刚好填入最后一个数字的时候,也就是临界情况为x == 8 && y == 8,我们已经确定了搜索思路,那么完成后,紧接着x和y对应的值就是x == 8 && y == 9,这种情况下就应该直接输出整个数盘

if(x == 8 && y == 9 ){
		for(int i = 0; i < 9; i++){
			for(int j = 0; j < 9; j ++){
				cout<<pand[i][j]<<" ";
			}
			cout<<endl;
		}
		exit(0);
	}

如果我们把exit(0)换成return,那么会有一个测试点超时!

我们可以思考这个过程,在完成全部填入之前,x相对于y变化很是缓慢,那么我们就先考虑y到临界的情况,在y为8时,填入数字后,根据顺序,y的值便成了9,那么这个时候就应该在下一列的第一行填入数字,代码如下

if(y == 9){	
		dfs(x+1,0);
	}

对于正常情况,因为在填入过程中会有回溯过程,所以我们应该判断这个点是否存在数字,如果存在,那么跳过这个点就行,如果没有,我们需要先判断填入的数字是否在行,列,宫出现过,如果出现,就跳过,如果没有,就进行正常填入,然后进行回溯。
因为这是一个暴力搜索的过程,所以我们可以从1开始填入

else{
		if(pand[x][y] != 0){
			dfs(x,y+1);
		}
		
		if(pand[x][y] == 0){
		    for(int n = 1; n <= 9; n++){
		    	if(!hang[x][n] && !lie[y][n] && !gong[x/3][y/3][n]){
		    		pand[x][y] = n;
		    		hang[x][n] = 1;
		    		lie[y][n] = 1;
		    		gong[x/3][y/3][n] = 1;
		    		dfs(x,y+1);
		    		hang[x][n] = 0;
		    		lie[y][n] = 0;
		    		gong[x/3][y/3][n] = 0;
		    		pand[x][y] = 0;
				}
				
			}
		}
		
	}

!hang[x][n] && !lie[y][n] && !gong[x/3][y/3][n]这段代表n这个没有在行,列,宫出现过。

为什么要把填入数字盘的数字也抹掉呢?对应pand[x][y] = 0;就是因为最开始说的,如果这个数字不合适,就会回溯然后寻找新的可能。换句话说,先前填入的数字不一定是正确的,如果发现填不下去,就会返回。

因为题目中给了部分数字,所以我们在main函数里面需要对这些数字进行操作

int main()
{
	for(int i = 0; i < 9; i++){
		for(int j = 0;j < 9;j++){
			cin>>pand[i][j];
			if(pand[i][j] > 0){
				hang[i][pand[i][j]]=1; //pand[i][j]代表的是一个数
				lie[j][pand[i][j]] = 1;
				gong[i/3][j/3][pand[i][j]] = 1;
			} 
		}
	}
	dfs(0,0);
	return 0;
 } 

对题目中已经给的数字,相当于已经出现过了,让对应行列宫的值等于一即可。

完整代码:

#include<iostream>
#include<algorithm>
using namespace std;
int hang[9][10]={0},lie[9][10]={0},pand[9][10]={0},gong[3][3][10] = {0};
void dfs(int x,int y){
	if(x == 8 && y == 9 ){
		for(int i = 0; i < 9; i++){
			for(int j = 0; j < 9; j ++){
				cout<<pand[i][j]<<" ";
			}
			cout<<endl;
		}
		exit(0);
	}
	if(y == 9){	
		dfs(x+1,0);
	}
	else{
		if(pand[x][y] != 0){
			dfs(x,y+1);
		}
		
		if(pand[x][y] == 0){
		    for(int n = 1; n <= 9; n++){
		    	if(!hang[x][n] && !lie[y][n] && !gong[x/3][y/3][n]){
		    		pand[x][y] = n;
		    		hang[x][n] = 1;
		    		lie[y][n] = 1;
		    		gong[x/3][y/3][n] = 1;
		    		dfs(x,y+1);
		    		hang[x][n] = 0;
		    		lie[y][n] = 0;
		    		gong[x/3][y/3][n] = 0;
		    		pand[x][y] = 0;
				}
				
			}
		}
		
	}
	
}
int main()
{
	for(int i = 0; i < 9; i++){
		for(int j = 0;j < 9;j++){
			cin>>pand[i][j];
			if(pand[i][j] > 0){
				hang[i][pand[i][j]]=1;
				lie[j][pand[i][j]] = 1;
				gong[i/3][j/3][pand[i][j]] = 1;
			} 
		}
	}
	dfs(0,0);
	return 0;
 } 

有不清楚或者错误的地方请指出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值