前几天笔者外出培训,刚刚学习了深度优先搜索,突然想到了数独的求解其实也可以用深搜实现,遂写了数独求解器与生成器。
一开始,当然是头文件~
#include
#include
接下来,是变量定义区
int sudoku[10][10];
//题目存储区
int row[10][10],column[10][10],block[4][4][10];
/*标记行、列、九宫格内某数字是否被使用过,例如row[4][7]=1代表第4行已有数字7*/
int known[10][10];
/*标记某单元格是否是题目所给数字,1代表题目中的数字*/
/*以上数组下标均从1开始使用*/
相信学过深搜的朋友应该知道,深搜的过程含搜索与回溯两步。
void search(int x,int y){
if (known[x][y]){
search((9*x+y-9)/9+1,y%9+1); //如果是题目中的数字则直接搜索下一个单元格,这里一并处理了y=9即现在所处单元格在行末的情况
}
else{
if (x==10 && y==1) { //已经填完所有的数并合法
print(); //打印输出
exit(0); //找到一个解就退出。若欲找出所有的解,将此行去掉即可
}
else
for (int i=1;i<=9;i++){ //枚举填数1~9
if (row[x][i]==0 && column[y][i]==0 && block[(x-1)/3+1][(y-1)/3+1][i]==0){ //判断此数是否合法。九宫格计算略显复杂
sudoku[x][y]=i;
row[x][i]=1;
column[y][i]=1;
block[(x-1)/3+1][(y-1)/3+1][i]=1;
search((9*x+y-9)/9+1,y%9+1);//填入下一个数,计算方法同第三行
sudoku[x][y]=0;
row[x][i]=0;
column[y][i]=0;
block[(x-1)/3+1][(y-1)/3+1][i]=0;//回溯:恢复之前的状态
}
}
}
}
以上代码,一气呵成。
为获得较好的视觉效果,笔者决定,输出格式用“-”和“|”画出单元格边界,用“=”和“||”表示九宫格边界和题目的边界。具体效果如下图所示:
简单。马上给出代码实现:
void print(){
cout<<" 1 2 3 4 5 6 7 8 9 "<
当然笔者并未满足,整天对着黑白的控制台也会疯掉的。百度了一下C++如何使输入输出带上颜色之后,笔者改进了输出函数:
const int colour [10]={0,3,4,13,5,11,6,14,2,9};//为每个数字设置颜色
void print(){
cout<<" 1 2 3 4 5 6 7 8 9 "<
以上代码需加上windows.h头文件。最终效果如下图:
int main(){
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
cin>>sudoku[i][j];
if (sudoku[i][j]) known[i][j]=1;//从这里可以看出,本程序用0代表空白。此处设置known数组,使程序分辨出是题目所给数字还是空白。
}
}
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
if (sudoku[i][j]){
row[i][sudoku[i][j]]=1;
column[j][sudoku[i][j]]=1;
block[(i-1)/3+1][(j-1)/3+1][sudoku[i][j]]=1;//构造候选数表
}
}
}
search(1,1);//从(1,1)开始搜索
return 0;
}
#include
#include
#include
using namespace std;
int sudoku[10][10];
int row[10][10],column[10][10],block[4][4][10];
int known[10][10];
const int colour [10]={0,3,4,13,5,11,6,14,2,9};
void print(){
cout<<" 1 2 3 4 5 6 7 8 9 "<>sudoku[i][j];
if (sudoku[i][j]) known[i][j]=1;
}
}
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
if (sudoku[i][j]){
row[i][sudoku[i][j]]=1;
column[j][sudoku[i][j]]=1;
block[(i-1)/3+1][(j-1)/3+1][sudoku[i][j]]=1;
}
}
}
search(1,1);
return 0;
}
void search(int x,int y){
if (f) return;
if (known[x][y]){
search((9*x+y-9)/9+1,y%9+1);
}
else{
if (x==10 && y==1) {
f=1;//生成完毕标识。可不能用exit(0)
cnt++;
}
for (int j=1;j<40;j++){
int i=rand()%9+1;//生成[1,9]随机整数
if (row[x][i]==0 && column[y][i]==0 && block[(x-1)/3+1][(y-1)/3+1][i]==0){
sudoku[x][y]=i;
row[x][i]=1;
column[y][i]=1;
block[(x-1)/3+1][(y-1)/3+1][i]=1;
search((9*x+y-9)/9+1,y%9+1);
if (!f){
sudoku[x][y]=0;
row[x][i]=0;
column[y][i]=0;
block[(x-1)/3+1][(y-1)/3+1][i]=0;
}
}
}
}
}
int main(){
srand(time(0)*time(0)-0x5e2d6aa*rand()+time(0)*338339);//只是想写一个复杂的种子值
search(1,1);//生成一个数独
int sum=0;
int sudoku2[10][10]={0};//sudoku2用于保存原题
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
problem[i][j]=sudoku2[i][j]=sudoku[i][j];
}
}
cnt=0;
while (cnt!=1){//cnt就是数独解的个数
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
known[i][j]=1;//先假设所有数都是题目所给
}
}
sum=0;
while (sum<50){//50意为生成题目的空白数
int x=rand()%9+1,y=rand()%9+1;
if (known[x][y]==0) continue;
else {
sum++;
known[x][y]=0;//随机设置空白
}
}
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
if (known[i][j]==0) sudoku[i][j]=problem[i][j]=0; //将空白处置0
}
}
cnt=0;
f=0;
memset(row,0,sizeof(row));
memset(colom,0,sizeof(column));
memset(block,0,sizeof(block));//全部清空候选数表
init();//重新设置候选数表,具体实现见后面
search2(1,1);//其实就是1.1里面的search函数,不过一并记录了数独解的个数
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
sudoku[i][j]=sudoku2[i][j];//恢复原题
if (cnt!=1) problem[i][j]=sudoku2[i][j];//若多解,problem也恢复原题。否则,problem就是我们的题目
}
}
}
print(problem);//print函数稍作更改,带了一个参数。具体实现见下
return 0;
}
void print(int sudoku[][10])
巧用C++中生存期的概念,让形参与全局变量同名,使得sudoku数组这一全局变量在print函数中不可见,而原print函数中调用sudoku数组的语句都是无需改动的。各位朋友可慢慢体会其妙处。
#include
#include
#include
#include
#include
using namespace std;
int sudoku[10][10];
int problem[10][10];
int row[10][10],column[10][10],block[4][4][10];
int known[10][10];
int f=0;
int cnt=0;
const int colour [10]={0,3,4,13,5,11,6,14,2,9};
void init(){
for (int i=1;i<=9;i++){
for (int j=1;j<=9;j++){
if (sudoku[i][j]){
row[i][sudoku[i][j]]=1;
column[j][sudoku[i][j]]=1;
block[(i-1)/3+1][(j-1)/3+1][sudoku[i][j]]=1;
}
}
}
}
void print(int sudoku[][10]){
cout<<" 1 2 3 4 5 6 7 8 9 "<
运行效果如下图: