前几天笔者外出培训,刚刚学习了深度优先搜索,突然想到了数独的求解其实也可以用深搜实现,遂写了数独求解器与生成器。

1 数独求解器

1.1 预备

一开始,当然是头文件~

#include <iostream>
#include <stdlib.h>

接下来,是变量定义区

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开始使用*/

1.2 核心算法——深搜

相信学过深搜的朋友应该知道,深搜的过程含搜索与回溯两步。

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); //找到一个解就退出。若欲找出所有的解,将此行去掉即可}elsefor (int i=1;i<=9;i++){  //枚举填数1~9if (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;//回溯:恢复之前的状态}}}
}

以上代码,一气呵成。

1.3 输出

为获得较好的视觉效果,笔者决定,输出格式用“-”和“|”画出单元格边界,用“=”和“||”表示九宫格边界和题目的边界。具体效果如下图所示:

简单。马上给出代码实现:

void print(){cout<<"    1   2   3    4   5   6    7   8   9   "<<endl;//列号cout<<"   =====================================  "<<endl;for (int i=1;i<=9;i++){cout<<i<<"|| ";//行号for (int j=1;j<=9;j++){if (j%3!=0) cout<<sudoku[i][j]<<" | ";//非九宫格边界else cout<<sudoku[i][j]<<" || ";//九宫格边界}cout<<endl;if (i%3!=0)cout<<"   -------------------------------------  "<<endl;//非九宫格边界else       cout<<"   =====================================  "<<endl;//九宫格边界}
}

当然笔者并未满足,整天对着黑白的控制台也会疯掉的。百度了一下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   "<<endl;cout<<"   =====================================  "<<endl;for (int i=1;i<=9;i++){cout<<i<<"|| ";for (int j=1;j<=9;j++){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),colour[sudoku[i][j]]);//设置输出颜色为该数字对应的颜色cout<<sudoku[i][j];SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),7);//7为控制台默认的白色,用于输出边界if (j%3!=0) cout<<" | ";else cout<<" || ";}cout<<endl;if (i%3!=0)cout<<"   -------------------------------------  "<<endl;else       cout<<"   =====================================  "<<endl;}}

以上代码需加上windows.h头文件。最终效果如下图:

1.4 初始化

万事俱备,最后的输入工作也慢慢完成了。且看代码:

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;
}

也就是说,输入格式如下:

sample in
0 6 0 0 0 0 9 3 0
0 9 3 0 1 6 5 8 0
0 0 0 0 9 0 0 0 0
6 0 7 1 8 9 4 2 0
4 0 0 0 0 2 3 0 8
0 8 0 0 4 7 1 0 6
3 0 6 9 0 8 0 5 0
5 7 1 2 3 0 8 6 9
9 2 8 6 0 0 7 0 3
综上,数独求解器已经全部完成。完整代码如下:

#include <iostream>
#include <stdlib.h>
#include <windows.h>
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   "<<endl;cout<<"   =====================================  "<<endl;for (int i=1;i<=9;i++){cout<<i<<"|| ";for (int j=1;j<=9;j++){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),colour[sudoku[i][j]]);cout<<sudoku[i][j];SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),7);if (j%3!=0) cout<<" | ";else cout<<" || ";}cout<<endl;if (i%3!=0)cout<<"   -------------------------------------  "<<endl;else       cout<<"   =====================================  "<<endl;}}
void search(int x,int y){if (known[x][y]){search((9*x+y-9)/9+1,y%9+1);}else{if (x==10 && y==1) {print();exit(0);}elsefor (int i=1;i<=9;i++){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;}}}
}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;}}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;
}

2 数独生成器

数独生成器要做的事情要稍多一些。第一,要随机填数。第二,要确保生成出来的数独有且仅有一组解(否则别人会卡在多解的地方无法继续推理填数)。笔者在数独求解器的基础上,对主程序和search函数进行改动,便写出了数独生成器。

2.1 search函数

随机填数,简单粗暴,不做解释。直接贴代码:

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;}}}}
}

2.2 main函数

由于此处解释过长,请原谅笔者将解释放到代码的注释中了。

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;
}

2.3 print函数

其实print函数只改动了一处:
void print(int sudoku[][10])

巧用C++中生存期的概念,让形参与全局变量同名,使得sudoku数组这一全局变量在print函数中不可见,而原print函数中调用sudoku数组的语句都是无需改动的。各位朋友可慢慢体会其妙处。

2.4 代码实现

说了这么多,也该贴上全部代码了:

#include <iostream>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include <cstring>
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   "<<endl;cout<<"   =====================================  "<<endl;for (int i=1;i<=9;i++){cout<<i<<"|| ";for (int j=1;j<=9;j++){if (sudoku[i][j]){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),colour[sudoku[i][j]]);cout<<sudoku[i][j];SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),7);}else cout<<" ";if (j%3!=0) cout<<" | ";else cout<<" || ";}cout<<endl;if (i%3!=0)cout<<"   -------------------------------------  "<<endl;else       cout<<"   =====================================  "<<endl;}}
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;cnt++;}for (int j=1;j<40;j++){int i=rand()%9+1;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;}}}}
}
void search2(int x,int y){if (x!=10 && known[x][y]){search2((9*x+y-9)/9+1,y%9+1);}else{if (x==10 && y==1){cnt++;}elsefor (int i=1;i<=9;i++){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;search2((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;}}}
}int main(){srand(time(0)*time(0)-0x5e2d6aa*rand()+time(0)*338339);search(1,1);int sum=0;int sudoku2[10][10]={0};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){for (int i=1;i<=9;i++){for (int j=1;j<=9;j++){known[i][j]=1;}}sum=0;while (sum<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; }}cnt=0;f=0;memset(row,0,sizeof(row));memset(column,0,sizeof(column));memset(block,0,sizeof(block));init();search2(1,1);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];}}}print(problem);return 0;
}

运行效果如下图:

读者朋友们,你们能解出这道来自C++的数独题吗?
warning:虽然主程序中空白块的数量可以任意改动,但笔者建议不要大于53,否则程序会长时间运行,很久才会出结果。

2.5 一个bug

运行数独生成器的时候,有时程序会像陷入死循环一般,无论等多长时间都不会动弹。笔者尚未找出此问题产生的原因及解决方案。若遇到此种情况,建议直接按Ctrl+C结束程序,再次启动,或许会运行正常。(发生概率目测为15%左右)

3 结语

本C++数独求解器用到的知识有:
(1) 深度优先搜索DFS
(2) 随机数
(3) 没了。
如有错误之处,敬请广大读者指正,笔者自当感激不尽。

C++数独求解器与生成器相关推荐

  1. MATLAB 自动数独求解器(导入图片自动求解)

    做了一个导入图片自动求解数独的软件,不过由于目前是通过最小二乘法匹配数字的,所以导入图片中的数字最好不要是手写的..,图片大概就像这样: 使用效果: 完整代码: function sudokuApp ...

  2. 编程之美之数独求解器的C++实现方法

    编程之美的第一章的第15节,讲的是构造数独,一开始拿到这个问题的确没有思路, 不过看了书中的介绍之后, 发现原来这个的求解思路和N皇后问题是一致的, 但是不知道为啥,反正一开始确实没有想到这个回溯法, ...

  3. 好用的z3数独求解器

    github 上发现一个好用 用z3 编写的数独求解器 传送门: https://github.com/dferri/z3-skyscrapers Generate a skyscrapers puz ...

  4. 数独输出Java_java – 使用回溯的数独求解器

    我最近一直致力于回溯数独求解算法,目前我想询问我应该如何将我的solve()方法从void更改为boolean. 我正在使用一个非常简单的回溯算法,它目前工作正常,但我宁愿有一个布尔值而不是一个空格, ...

  5. POJ 2676 Sudoku (数独求解器 DFS)

    题目链接 POJ2676 题目大意 输入n个数独,空格用0表示,填数独(符合的一种方案即可). 分析 类似于八皇后的简单搜索题,用DFS回溯法求解. 将空格的位置都记录下来,1个1个填下去,填不下去就 ...

  6. python数独解题器,Python中最短的数独求解器 – 它是如何工作的?

    那么,通过修正语法,可以使事情变得更容易: def r(a): i = a.find('0') ~i or exit(a) [m in[(ij)%9*(i/9^j/9)*(i/27^j/27|i%9/ ...

  7. c 语言写数独游戏,经典数独游戏+数独求解器—纯C语言实现

    [转]NGUI研究院之三种方式监听NGUI的事件方法(七) NGUI事件的种类很多,比如点击.双击.拖动.滑动等等,他们处理事件的原理几乎万全一样,本文只用按钮来举例. 1.直接监听事件 把下面脚本直 ...

  8. 利用全连接网络实现数独求解

    [摘要] 本文介绍了一个能够求解数独问题的求解器的设计与实现,该求解器具备多种功能.它可以通过识别图片中的数独网格,也可以随机生成数独题目,识别数独题目:对数独进行验证.提示,并能展示求解过程的算法演 ...

  9. 数独求解算法_我如何回到一个老问题,终于写了一个数独求解算法

    数独求解算法 by Ali Spittel 通过Ali Spittel 我如何回到一个老问题,终于写了一个数独求解算法 (How I came back to an old problem and f ...

最新文章

  1. mysql--多实例启动方法
  2. python基础知识资料-Python基础知识篇 列表简介
  3. java filesystem_Java FileSystem isReadOnly()用法及代码示例
  4. maven插件之build-helper-maven-plugin
  5. 20应用统计考研复试要点(part26)--简答题
  6. python-函数的注释
  7. java rf14bug_hadoop2.7.0集群,使用中遇到的bug及解决办法
  8. Node.js--Stream
  9. Spring Boot学习总结(7)——SpringBoot之于Spring优势
  10. c语言生日创意代码_C语言如何编程生日快乐代码
  11. Linux内核编程打印所有线程信息
  12. step7-micro/win 在win10系统下安装步骤
  13. matlab设计匹配滤波器,[转载]利用MATLAB实现匹配滤波器的仿真验证
  14. 三轴加速度传感器 角度值 转换原理
  15. 【新手教程】51Sim-One Cloud 2.0如何接入被测算法
  16. 【Leetcode刷题】:Python:347. 前 K 个高频元素
  17. 云e办学习笔记(十五)Redis学习以及相关部署
  18. HDMI转Displayport转换器支持4K分辨率
  19. Excel中批量添加批注图片
  20. android 9.0背光调节流程

热门文章

  1. 清除pycharm残留文件
  2. 小程序设置渐变色背景
  3. iuv_5g组网问题表
  4. Web APIs /APIs --DOM简述/DOM中获取元素方法/事件(含鼠标事件)/操作(含案例)
  5. Python的大数据之旅(1)---Anaconda与WingIDE安装
  6. c语言赋值运算与除法运算顺序,C语言运算符的优先级和结合律
  7. python玩王者荣耀皮肤_python 王者荣耀皮肤高清图片下载 附源码
  8. ILSSI|六西格玛DMAIC的历程
  9. 乍得“随军”记 ——写在结婚一周年
  10. Android 11.0 12.0蓝牙遥控器确认键弹不出输入法的解决方法