用递归回溯法求解9*9数独的C++实现,附详细说明
本人初学,有不当之处望大神指正
代码块
//风休住于2018.8.31编写完成
//All Rights Reserved
#include<bits/stdc++.h>
using namespace std;
/*数独二维数组*/
int board[9][9];
/*声明Check函数,用以检查所填的数是否合法*/
bool Check(int check_number,int check_now_line,int check_now_column,
int check_block_line,int check_block_column);
/*输入函数*/
void Input();
/*工作函数*/
bool Work(int now_line,int now_column);
/*输出函数*/
void Output();
/*主函数*/
int main()
{
Input();
if(Work(0,0)) Output();//如果有解,则调用输出函数
else cout<<"No Answer."<<endl;//如果无解,则输出“No Answer.”
system("pause");
return 0;
}
/*check_number为待检查的数字,check_now_line和check_now_column为待检查的坐标,
check_block_line和check_block_column为所在的3*3方格左上角坐标*/
bool Check(int check_number,int check_now_line,int check_now_column,
int check_block_line,int check_block_column)
{
for(int i=0;i<=8;++i)//检查与待检查的坐标同行或同列的位置
{
if(board[check_now_line][i]==check_number||board[i][check_now_column]==check_number)
return false;
}
for(int i=0;i<=2;++i)//检查与待检查的坐标同3*3方格的位置
{
for(int j=0;j<=2;++j)
{
if(board[check_block_line+i][check_block_column+j]==check_number)
return false;
}
}
return true;//通过所有检查,返回true
}
void Input()
{
cout<<"请输入一个9*9数独,两数见用空格隔开,空位用0表示:"<<endl;
for(int i=0;i<=8;++i)
{
for(int j=0;j<=8;++j)
{
cin>>board[i][j];
}
}
}
/*为使代码更为简洁,将Work函数定义为bool,如果数独有解则返回true,无解则返回false,
不用再单独定义flag来标记数独是否完成*/
bool Work(int now_line,int now_column)
{
if(now_line==9)
{
return true;//如果将数独解完,返回true
}
else
{
int next_line,next_column,block_line,block_column;
next_column=now_column+1;
next_line=(next_column>=9?now_line+1:now_line);
next_column=(next_column>=9?0:next_column);
if(board[now_line][now_column]!=0)//如果当前坐标有数字,则对下一个坐标进行工作
{
if(Work(next_line,next_column)) return true;//如果数独最终有解,则不断向前返回true
}
else
{
block_line=(now_line/3)*3;//计算所在的3*3方格左上角坐标
block_column=(now_column/3)*3;
for(int i=1;i<=9;++i)
{
if(Check(i,now_line,now_column,block_line,block_column))
{
board[now_line][now_column]=i;//如果i值合法,则对下一个坐标进行工作
if(Work(next_line,next_column)) return true;
}
}
board[now_line][now_column]=0;//回溯操作
return false;//如果i的值为1-9均不合法,则返回上一层继续循环
}
}
}
void Output()
{
cout<<endl<<endl;
for(int i=0;i<=8;++i)
{
for(int j=0;j<=8;++j)
{
cout<<setw(2)<<board[i][j];
if((j+1)%3==0) cout<<" ";
}
cout<<endl;
if((i+1)%3==0) cout<<endl;
}
}
说明
数独规则
数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。—— [ 百度百科 ]
首先对Check函数进行一下说明。假设正在对(2,4)号方块进行检查,首先需保证与(2,4)同一行及同一列上的方格中,没有与待填数字相同的方格;其次,需保证在图中标绿色的3*3方格中,没有与待填数字相同的方格,代码实现为
for(int i=0;i<=8;++i)
{
if(board[check_now_line][i]==check_number||board[i][check_now_column]==check_number)
return false;
}
for(int i=0;i<=2;++i)
{
for(int j=0;j<=2;++j)
{
if(board[check_block_line+i][check_block_column+j]==check_number)
return false;
}
}
如果通过了以上检验,则
return true
在进行求解时,主函数调用 Work(0,0)
即从方格(0,0)处开始工作,从左到右、从上到下依次进行。
如果该方格内已经填过数,则跳过这一方格,形如
if(board[now_line][now_column]!=0) { if(Work(next_line,next_column)) return true; }
接下来开始对当前方格进行处理。为了方便 Check 函数的运行,首先将计算出当前方格所处 Block 的左上角坐标,形如
block_line=(now_line/3)*3; block_column=(now_column/3)*3;
其次,检查1-9填入当前方块中是否合法,如果合法,则不管它是否正确,直接填入当前方块中,并对下一个方块进行工作,形如
if(Check(i,now_line,now_column,block_line,block_column)){board[now_line][now_column]=i;if(Work(next_line,next_column)) return true; }
像这样运行几次后,有可能出现这种情况,即先前填写的数字有错误,导致当前方块一个数也填不了;那么就会跳出检查1-9的for循环,执行以下操作
board[now_line][now_column]=0; return false;
第一句目的是将当前方块赋0(递归回溯基本操作),第二句使函数返回false,则跳出当前层次的递归,回到上一个检查1-9的for循环;如果依然没有可填的数,则再回到上一个循环,以此类推。如果第一个方格也无数可填,则主函数中的返回值为false,执行
cout<<"No Answer."<<endl;
当工作到方块(8,8)时,如果仍然找到了可填的数,则将开始对第10行进行工作。这是将会触发
if(now_line==9) {return true;}
这也意味着数独成功找到了一种解法,第81层递归返回true。在第80层递归收到true时,它也会返回true,以此类推,主函数中的返回值为true,执行
Output();
将数独数组输出,结束
特点及缺陷
关键代码 if(Work(next_line,next_column)) return true;
这段代码利用了C++语言简洁的特征,在对数独是否有解进行判断(上文中有具体说明)的同时,运行了Work函数,填写了整个数独。
缺陷在于,这篇代码纯粹利用了计算机运行速度快的特点,对所有空格进行了暴力枚举,并没有使用人工解数独时的技巧方法,有些遗憾。
测试数据
输入
0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 6
0 8 7 9 6 4 2 5 0
0 1 0 4 0 8 0 2 0
0 0 0 7 0 3 0 0 0
0 3 0 5 0 6 0 8 0
0 7 2 3 8 1 9 6 0
9 0 0 0 0 0 0 0 3
0 0 0 0 0 0 0 0 0
输出
2 9 6 1 7 5 4 3 8
4 5 1 8 3 2 7 9 6
3 8 7 9 6 4 2 5 1
6 1 5 4 9 8 3 2 7
8 2 9 7 1 3 6 4 5
7 3 4 5 2 6 1 8 9
5 7 2 3 8 1 9 6 4
9 6 8 2 4 7 5 1 3
1 4 3 6 5 9 8 7 2
输入
0 6 0 0 0 0 0 0 0
0 0 0 0 0 1 0 6 4
5 0 0 0 3 0 7 0 0
0 0 8 0 1 6 2 0 7
2 0 0 5 0 4 0 0 9
3 0 5 8 9 0 6 0 0
0 0 7 0 2 0 0 0 3
8 4 0 7 0 0 0 0 0
0 0 0 0 0 0 0 7 0
输出
9 6 1 4 8 7 5 3 2
7 8 3 2 5 1 9 6 4
5 2 4 6 3 9 7 1 84 9 8 3 1 6 2 5 7
2 1 6 5 7 4 3 8 9
3 7 5 8 9 2 6 4 16 5 7 1 2 8 4 9 3
8 4 9 7 6 3 1 2 5
1 3 2 9 4 5 8 7 6