数独问题题解

数独

题目描述

数独是根据 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) 放在下边供自测。

样例输入#2

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 

样例输出#2

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 

题解

思路

首先输入一个未完成数独,在输入时将已经给定的数字打上标记,接着按照顺序依次确定每个方格里的数

标记

按照数独的要求,我们需要将同行、同列、同九宫内用过的数打上标记,并在枚举选择时避开

行和列很好标记,只需各开一个二维数组,代表第几行(第几列)和已用的数

int xf[15][15],yf[15][15];//以行举例:xf[4][2],代表第4行的2的标记

而九宫格就要另想办法,我们把原本是9 * 9的数独缩小,看成3 * 3的坐标系,新坐标系的每个位置都代表一个九宫格

转换的过程很简单,只要将所在的行列分别加上二再除以三就可以了

所以我们就开一个三维数组,前两个存位置,最后一个存数字

int nf[15][15][15];//例:nf[1][3][8],表示(1,3)九宫的8的标记

输入

按顺序输入数独,并将每行、每列、每九宫中已有的数标记

for(int i=1;i<=9;i++)//行
{
	for(int j=1;j<=9;j++)//列
	{
		cin>>a[i][j];//输入
		if(a[i][j]!=0)//如果有数
		{
			int k=a[i][j];//临时储存这个数,方便下面标记
			xf[i][k]=1;//标记行
			yf[j][k]=1;//标记列
			nf[(i+2)/3][(j+2)/3][k]=1;//标记九宫
		}
	}
}

深搜

如果当前位置为空时,枚举1-9的数字

if(a[x][y]==0)//当前位置为0(空位)
{
	for(int i=1;i<=9;i++)//枚举1-9
	{
	}
}

如果当前数字未被用过,选中并标记

if(xf[x][i]==0&&yf[y][i]==0&&nf[(x+2)/3][(y+2)/3][i]==0)//判断此数是否在该行、该列、该九宫用过
{
	a[x][y]=i;//选中
	xf[x][i]=1;//标记行
	yf[y][i]=1;//标记列
	nf[(x+2)/3][(y+2)/3][i]=1;//标记九宫
}

进入下一个位置时:一般情况下让列加一,但有几个位置需要注意

每行最后一个位置,要跳转到下一行的第一列;全部的最后一个位置,要输出整个数独

if(x==9&&y==9)out();//到了最后,输出。这里单独写了一个函数
else if(y<9)dfs(x,y+1);//一般情况,列加一
else if(y==9)dfs(x+1,1);//到了每行最后,跳转到下一行第一列

然后清除所有标记

a[x][y]=0;//清除选中的数
xf[x][i]=0;//清除行标记
yf[y][i]=0;//清除列标记
nf[(x+2)/3][(y+2)/3][i]=0;//清除九宫标记

上面我们说的是当前位置为空的情况,但是如果当前位置有给定的数,我们还是要继续往下,只不过不需要再枚举选择当前的数了

else//有数的情况
{
	if(x==9&&y==9)out();//到了最后,输出
	else if(y<9)dfs(x,y+1);//一般情况,列加一
	else if(y==9)dfs(x+1,1);//到了每行最后,跳转至下一行第一列
}

补充,输出函数这样写

void out()
{
	for(int i=1;i<=9;i++)//行
	{
		for(int j=1;j<=9;j++)cout<<a[i][j]<<" ";//列和输出
		cout<<endl;//换行
	}
	exit(0);//全部结束
}

完整代码

#include<bits/stdc++.h>
using namespace std;
int a[15][15],xf[15][15],yf[15][15],nf[15][15][15];
void out()
{
	for(int i=1;i<=9;i++)//行 
	{
		for(int j=1;j<=9;j++)cout<<a[i][j]<<" ";//列和输出 
		cout<<endl;//换行 
	}
	exit(0);//全部结束 
}
void dfs(int x,int y)//以坐标填数 
{
	if(a[x][y]==0)//如果当前位置为空 
	{
		for(int i=1;i<=9;i++)//枚举1-9的数字 
		{
			if(xf[x][i]==0&&yf[y][i]==0&&nf[(x+2)/3][(y+2)/3][i]==0)//检查此数是否在该行、该列、该九宫中被使用 
			{
				a[x][y]=i;//选中 
				xf[x][i]=1;//标记行 
				yf[y][i]=1;//标记列 
				nf[(x+2)/3][(y+2)/3][i]=1;//标记九宫 
				if(x==9&&y==9)out();//到了最后,输出整个数独 
				else if(y<9)dfs(x,y+1);//一般情况,列加一 
				else if(y==9)dfs(x+1,1);//到了每行最后,跳转至下一行第一列 
				a[x][y]=0;//清除选中的数 
				xf[x][i]=0;//清除行标记 
				yf[y][i]=0;//清除列标记 
				nf[(x+2)/3][(y+2)/3][i]=0;//清除九宫标记 
			}
		}
	}
	else//如果当前位置有数,也需要继续 
	{
		if(x==9&&y==9)out();//到了最后,输出整个数独 
		else if(y<9)dfs(x,y+1);//一般情况,列加一 
		else if(y==9)dfs(x+1,1);//到了每行最后,跳转至下一行第一列 
	}
}
int main()
{
	for(int i=1;i<=9;i++)//行 
	{
		for(int j=1;j<=9;j++)//列 
		{
			cin>>a[i][j];//输入 
			if(a[i][j]!=0)//如果有数 
			{
				int k=a[i][j];//临时储存该数 
				xf[i][k]=1;//标记行 
				yf[j][k]=1;//标记列 
				nf[(i+2)/3][(j+2)/3][k]=1;//标记九宫 
			}
		}
	}
	dfs(1,1);
	return 0;
}
  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值