SudokuSolver 1.0:用C++实现的数独解题程序 【一】
SudokuSolver 1.0 用法与实现效果
SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有:
H:\Read\num\Release>sudoku.exe Order please: Sudoku Solver 1.0 2021/9/20 by readalps Order List:
load-quiz <file>: load quiz from file
show: show quiz info
step: step forward
run: run till the end or a new solution met
bye: quit Order please:
以 一道心形数独题的求解 里的那道题为例,先在一个文本文件(如:H:\s.txt)里记录这道题,约定用 0 表示待填数字,内容如下:
000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000
load-quiz 命令用于从文本文件中把数独题(称为一个 quiz)作为输入加载到程序内存;
show 命令用于显示 quiz 相关信息;
step 和 run 命令用于解题,step 只往前走一步;run 则一直走到求出一个解或走到末尾无解时才停步;
bye 命令结束交互退出程序。
以下是load-quiz、show 以及第一次 step 命令的运行效果:
H:\Read\num\Release>sudoku.exe Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
show
000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Order please:
step
000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 6 7 8
[1,2]: 4 5 7 8 9
...
[2,1]: 5 6
[2,4]: 1 5 9
[2,5]: 1
[2,6]: 5 9
...
[9,8]: 1 2 3 4 5 6 7
[9,9]: 2 3 4 5 7 Order please:
对比 show 显示的九宫格内容,和 step 之后显示出来的九宫格内容,会发现两者内容完全一致,即这一次 step 还没有开始填数。随后的 candidates 内容显示了各个待填数的格子里所有可能的候选值,其中 [2,5]: 1 表示第 2 行第 5 列的位置只有一个候选值 1,说明下一步这个位置会填上 1。具体看一下:
Order please:
step
000 000 000
023 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 6 7 8
...
[1,9]: 2 3 4 5
[2,1]: 5 6
[2,4]: 5 9
[2,6]: 5 9
[2,9]: 4 5
[3,2]: 5 7 8
...
[9,9]: 2 3 4 5 7 Order please:
可以看到 [2,5] 位置如期填上了 1,但是从随后的 candidates 信息里看,剩余待填格的候选项数都大于 1,其中位置最靠前的是 :
[2,1]: 5 6
由 一道心形数独题的求解 里的方法,可以直接确定 [2,1] = 6 以及 [2,9] = 4,但这个程序并没有实现这个方法。因此,在下一步里会尝试让 [2,1] = 5。具体看一下:
Order please:
step
Guess [2,1] level 1 at 1 out of 2 000 000 000
523 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 6 7 8
...
[1,9]: 2 3 4 5
[2,4]: 9
[2,6]: 9
[2,9]: 4
[3,2]: 7 8
[3,3]: 7 8
...
[9,9]: 2 3 4 5 7 At guess level 1 [2,1] 1 Order please:
[2,1] 位置上如期填上了 5。在 step 命令之后有如下一句信息:
Guess [2,1] level 1 at 1 out of 2
这是说,[2,1] 位置上有两个候选值,这里先尝试第一个候选值(at 1 out of 2),当前处在的猜测级数为1(level 1)。
随后的 candidates 信息里有:
[2,4]: 9
[2,6]: 9
即 [2,4] 和 [2,6] 位置的唯一候选值都为 9,因为同处第 2 行,这是不合理的。因此,下一步要做出调整:
Order please:
step
9 is in row 2 before!
Forward guess [2,1] level 1 at 2 out of 2 000 000 000
623 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[2,4]: 5 9
[2,6]: 5 9
[2,9]: 4 5
...
[9,9]: 2 3 4 5 7 At guess level 1 [2,1] 2 Order please:
[2,1] 位置如期调整为第二个候选值 6。
Forward guess [2,1] level 1 at 2 out of 2
Forward guess 是指在同一个猜测级向下一个候选值调整,即两个候选值中的第二个(at 2 out of 2)。
在随后的 candidates 信息里,剩余待填格的候选项数都大于 1,说明又要做更进一级的猜测,即猜测级数为 2。其中位置最靠前的是 :
[2,4]: 5 9
下一步会猜测 [2,4] = 5:
Order please:
step
Guess [2,4] level 2 at 1 out of 2 000 000 000
623 510 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[2,6]: 9
[2,9]: 4
[3,2]: 5 7 8
...
[9,9]: 2 3 4 5 7 At guess level 2 [2,4] 1 Order please:
从如下信息可以看出猜测级别进到了 level 2:
Guess [2,4] level 2 at 1 out of 2
新的候选值信息里有唯一候选值的情形,下一步会进一步填值:
Order please:
step
000 000 000
623 519 784
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[3,3]: 7 8
...
[9,9]: 2 3 5 7 At guess level 2 [2,4] 1 Order please:
新的候选值信息里没有唯一候选值的情形,说明下一步里猜测会加深一级:
Order please:
step
Guess [3,3] level 3 at 1 out of 2 000 000 000
623 519 784
107 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 8
...
[3,2]: 5 8
...
[9,9]: 2 3 5 7 At guess level 3 [3,3] 1 Order please:
后面不再一步步走了,直接试一次 run 命令:
Order please:
run
Guess [1,1] level 4 at 1 out of 2
Guess [1,2] level 5 at 1 out of 2
Guess [1,9] level 6 at 1 out of 2
Guess [1,4] level 7 at 1 out of 2
Guess [1,5] level 8 at 1 out of 2
Guess [1,7] level 9 at 1 out of 2
Guess [3,7] level 10 at 1 out of 2
Guess [4,2] level 11 at 1 out of 2
7 is in row 4 before!
Forward guess [4,2] level 11 at 2 out of 2
3 is in row 4 before!
Upward guess [3,7] level 10 at 2 out of 2
Guess [4,2] level 11 at 1 out of 2
7 is in row 4 before!
Forward guess [4,2] level 11 at 2 out of 2
...
2 is in row 4 before!
Upward guess [3,7] level 9 at 2 out of 2
8 is in row 4 before!
Upward guess [2,4] level 2 at 2 out of 2
Guess [3,3] level 3 at 1 out of 2
Guess [1,1] level 4 at 1 out of 2
Guess [1,2] level 5 at 1 out of 2
Guess [1,9] level 6 at 1 out of 2
Guess [1,4] level 7 at 1 out of 2
Guess [1,5] level 8 at 1 out of 2
Guess [1,7] level 9 at 1 out of 2
Guess [3,7] level 10 at 1 out of 2
Guess [4,2] level 11 at 1 out of 2
Guess [5,3] level 12 at 1 out of 2 549 738 162
623 915 784
187 426 359 438 659 271
951 872 436
762 143 598 395 264 817
276 381 945
814 597 623 Done [steps:1058, solution sum:1].
Run time: 763 milliseconds; steps: 1058, solution sum: 1. Order please:
这次的 run 命令求得了一个解,为求得这个解,一共走了 1058 步,耗时大约为 763 毫秒。
从这次 run 的前期输出信息看,进到 guess level 11 时,因出现冲突,而做了一次平级(forward guess)调整:
Forward guess [4,2] level 11 at 2 out of 2
随后这次平级调整同样出现冲突,且无法再做平级调整,于是做了一次降级(upward guess)调整:
Upward guess [3,7] level 10 at 2 out of 2
从这次 run 的后期输出信息看,最后一次调整是从 level 9 直降到 level 2:
Upward guess [3,7] level 9 at 2 out of 2
8 is in row 4 before!
Upward guess [2,4] level 2 at 2 out of 2
即前面 level 2 时由于 [2,4]: 5 9 猜测的 [2,4] = 5 是错误的,直到此时才调整为 [2,4] = 9,随后又依次逐级进到 level 12 才求出第一个解。
再来一次 run 命令:
...
2 is in col 3 before!
Forward guess [5,2] level 10 at 2 out of 2 Guess [5,3] level 11 at 1 out of 2 5 is in col 7 before!
Forward guess [5,3] level 11 at 2 out of 2 Candidates for [8,7] went wrong
No more solution (solution sum is 1).
794 832 615
623 915 784
158 476 329 487 659 231
932 000 506
561 000 490 005 000 800
009 301 000
006 090 000 Invalid quiz [steps:4496] - no more solution (solution sum is 1)
Run time: 1618 milliseconds; steps: 4496, solution sum: 1. Order please:
上一次 run 已经找到一个解,新的 run 会从上次得到解的位置试图做平级调整、降级调整等手段,尝试找一个新的解。但本例中,第二次 run 走到了末尾都没有找到新解,说明本例只有一个解。
Candidates for [8,7] went wrong
这句输出说明,最后得到的九宫格里 [8,7] 位置不能填 1 到 9 的任何数字(从第 8 行看,不能为 1、3、9,从第 7 列看,不能为 2、3、4、5、6、7、8)。
No more solution (solution sum is 1).
这句说明,当时所处的各级 guess level 都已是该级的最后一个选项值,出现冲突后无法做平级调整或降级调整,即所有可能有解的情形都已经走完了。
这次 run 命令耗时约 1618 毫秒。而从第一个 step 算起,包括两次 run 里面的 step 数,合计为 4496 步(steps)。
SudokuSolver 1.0 实现程序与说明
一个代码文件 sudoku.cpp 总计约 540 行代码。
Cell 结构
1 #include <stdio.h>
2 #include <stdint.h>
3 #include <string>
4 #include <vector>
5 #include <set>
6 #include <stack>
7 #include <iostream>
8 #include <fstream>
9 #include <ctime>
10
11 typedef unsigned char u8;
12 typedef unsigned long ulong;
13
14 struct Cell {
15 u8 val;
16 u8 candidates[10];
17 Cell() {memset(this, 0, sizeof(Cell));}
18 };
一个 Cell 对应九宫格中的一个数字格。分量 val 为该数字格的值,初始值为 0 表示该格的数字待填;分量 candidates 记录待填格的候选值数组,下标从 1 开始(下标为 0 的单元记录候选值个数)。
Snapshot 结构
1 struct Snapshot
2 {
3 Cell seqCell[81];
4 std::set<u8> setBlank;
5 std::vector<std::set<u8> > rowTaken;
6 std::vector<std::set<u8> > colTaken;
7 std::vector<std::set<u8> > blkTaken;
8 u8 state;
9 u8 guessLevel;
10 u8 guessPos;
11 u8 guessValPos;
12 };
一个 Snapshot 记录某一次猜测的上下文,包括:
seqCell,每个数字格的值与候选值信息;
setBlank,所有待填格(值为 0)的下标集合;
rowTaken、colTaken、blkTaken,记录每一行、每一列、每一宫已填数字的集合;
state,当时 quiz 所处状态;
guessLevel、guessPos、guessValPos,猜测级别、猜测数字格对应的下标、猜测数字格的取值在候选数列中的下标。
CQuizDealer 类
1 class CQuizDealer
2 {
3 public:
4 void loadQuiz(std::string& strAbsFile);
5 void showQuiz();
6 void step();
7 void run();
8 static CQuizDealer* instance() {
9 return (NULL == sm_pInst ? (sm_pInst = new CQuizDealer) : sm_pInst);
10 }
11
12 private:
13 enum {STA_UNLOADED = 0, STA_LOADED, STA_INVALID, STA_VALID, STA_DONE};
14 CQuizDealer() : m_state(STA_UNLOADED), m_guessLevel(0), m_guessPos(0),
m_guessValPos(0), m_soluSum(0), m_steps(0) {};
15 void parse();
16 void adjust();
17 void initTakens();
18 void initTaken(std::vector<std::set<u8> >& vec);
19 bool fillTaken(u8 key, u8 val, std::vector<std::set<u8> >& vec);
20 bool adjustTakens(u8 idx, u8 val);
21 bool calcCandidates(u8 idx);
22 bool reCalcCandidates();
23 void guess(u8 idx);
24 void pushIn(u8 idx, u8 valIdx);
25 bool shift(u8 idx, u8 valIdx);
26 void nextGuess();
27 void backGuess();
28 void useSnapshot(Snapshot* pSnap);
29
30 Cell m_seqCell[81];
31 std::set<u8> m_setBlank;
32 std::vector<std::set<u8> > m_rowTaken;
33 std::vector<std::set<u8> > m_colTaken;
34 std::vector<std::set<u8> > m_blockTaken;
35 u8 m_state;
36 u8 m_guessLevel;
37 u8 m_guessPos;
38 u8 m_guessValPos;
39 ulong m_soluSum;
40 ulong m_steps;
41
42 std::stack<Snapshot*> m_stkSnap;
43 static CQuizDealer* sm_pInst;
44 };
CQuizDealer 的成员变量大多和 Snapshot 的成员相对应,m_soluSum 记录总解数,m_steps 记录求解过程中所用的总步数,m_stkSnap 是个堆栈,记录各级猜测的上下文;sm_pInst 是 CQuizDealer 类的单例指针,使用 CQuizDealer::instance() 访问。
main 函数
1 int main()
2 {
3 std::string strOrder;
4 while (!isQuitSet()) {
5 printf("\nOrder please:\n");
6 getline(std::cin, strOrder);
7 dealOrder(strOrder);
8 }
9 return 0;
10 }
其中用到的 isQuitSet() 为:
1 static bool s_bQuit = false;
2 bool isQuitSet() {return s_bQuit;}
3 void setQuit() {s_bQuit = true;}
dealOrder 和 showOrderList 函数
1 void showOrderList()
2 {
3 printf("Sudoku Solver 1.0 2021/9/20 by readalps\n\n");
4 printf("Order List:\n");
5 printf("load-quiz <file>: load quiz from file\n");
6 printf("show: show quiz info\n");
7 printf("step: step forward\n");
8 printf("run: run till the end or a new solution met\n");
9 printf("bye: quit\n");
10 }
11
12 void dealOrder(std::string& strOrder)
13 {
14 std::string strEx;
15 if ("bye" == strOrder)
16 setQuit();
17 else if (matchPrefixEx(strOrder, "load-quiz", strEx))
18 CQuizDealer::instance()->loadQuiz(strEx);
19 else if ("show" == strOrder)
20 CQuizDealer::instance()->showQuiz();
21 else if ("step" == strOrder)
22 CQuizDealer::instance()->step();
23 else if ("run" == strOrder)
24 CQuizDealer::instance()->run();
25 else
26 showOrderList();
27 }
其中用到的 matchPrefixEx 函数实现为:
1 bool matchPrefixEx(const std::string& src, const char* prefix, std::string& strEx)
2 {
3 if (NULL == prefix || 0 == strlen(prefix))
4 return false;
5 size_t len = strlen(prefix);
6 if (0 == strncmp(src.c_str(), prefix, len)) {
7 strEx = src.substr(len);
8 return true;
9 }
10 return false;
11 }
CQuizDealer 类内部实现放到下一篇。
SudokuSolver 1.0:用C++实现的数独解题程序 【一】的更多相关文章
- SudokuSolver 1.0:用C++实现的数独解题程序 【二】
本篇是 SudokuSolver 1.0:用C++实现的数独解题程序 [一] 的续篇. CQuizDealer::loadQuiz 接口实现 1 CQuizDealer* CQuizDealer::s ...
- SudokuSolver 2.0:用C++实现的数独解题程序 【一】
SudokuSolver 2.0 实现效果 H:\Read\num\Release>sudoku.exe Order please: Sudoku Solver 2.0 2021/10/2 by ...
- 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析
SudokuSolver 2.3 程序实现 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析 里新发现了一处可以改进 grp 算法的地方,本次版本实现了对应的改进 grp 算法 ...
- 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析
SudokuSolver 2.2 程序实现 根据 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里分析,对 2.1 版做了一些改进和尝试. CQuizDealer 类声明部 ...
- 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析
SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...
- 用C++实现的数独解题程序 SudokuSolver 2.4 及实例分析
SudokuSolver 2.4 程序实现 本次版本实现了 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析 里发现的第三个不完全收缩 grp 算法 thirdGreenWor ...
- 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析
SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...
- 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析
引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...
- 数独GUI程序项目实现
数独GUI程序项目实现 导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了...我就想自己做一个数独小游戏,也是一个不错的选择. 前期我在网上简单地查看了一些数独游戏的界面,代码.好 ...
随机推荐
- 使用spring向service里面注入dao不成功。
因为原来的程序没有使用spring.后来加spring的时候action有个地方的new没有改!!! new了个新的实现层 不是spring管理的对象.
- UDP实现在线聊天功能
发送端 //发送 public class UDPChat01 { public static void main(String[] args) throws Exception { //开启端口 D ...
- Docker编排工具Docker Compose的使用
一.安装docker compose 官网推荐方式之一: sudo curl -L "https://github.com/docker/compose/releases/download/ ...
- python打包发布自己的pip项目
原文链接:https://blog.csdn.net/Liangjun_Feng/article/details/80037315 一.注册pypi账号 网址:https://pypi.org/ 直接 ...
- 循环神经网络LSTM RNN回归:sin曲线预测
摘要:本篇文章将分享循环神经网络LSTM RNN如何实现回归预测. 本文分享自华为云社区<[Python人工智能] 十四.循环神经网络LSTM RNN回归案例之sin曲线预测 丨[百变AI秀]& ...
- springcloud3(五) spring cloud gateway动态路由的四类实现方式
写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Mem ...
- JS005. 拷贝引用数据类型Array使其指向不同堆的解决方案
一个很常见的语法问题,但专注实现需求时经常会忘记去避免,导致最终问题的出现,再花时间排查.为此专门整理一篇解决方法的博客,也加强一下自己的记忆. TAG: JSON.parse() JSON.stri ...
- CSS003. 盒子水平垂直居中简写属性(place-items)
place-items CSS 中的 place-items 是一个简写属性 ,它允许你在相关的布局(如 Grid 或 Flexbox)中可以同时沿着块级和内联方向对齐元素 (例如:align-it ...
- scrum项目冲刺_day06总结
摘要:今日完成任务. 1.服务器部署完成 2.由于将数据库放到了服务器上,搜索功能需要修改 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成 ...
- 如何画UML,几种简单的模型分析
如何画UML 前言 UML 类 类的关系 1.依赖关系 2.继承关系 3.实现关系 4.关联关系 5.聚合关系 6.组合关系 总结 参考 如何画UML 前言 最近在学习设计模式,其中不免涉及到 UML ...