SudokuSolver 2.3 程序实现

用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析 里新发现了一处可以改进 grp 算法的地方,本次版本实现了对应的改进 grp 算法。

CQuizDealer 类声明部分的修改

增加了两个私有接口:

    bool sameCandidates(u8 cel1, u8 cel2);
u8 anotherGreenWorld(u8* pGrp);
u8 incompleteShrinkByAGW(u8 times, u8* pTimesVals, u8* pValsCells, u8* pGrp);

anotherGreenWorld,这个略显随意和突兀的命名,来自 Brian Eno 于 1977 年发行的专辑《Another Green World》。

filterOneGroup 接口实现的小修改

把末尾的

return RET_PENDING;

改为

return anotherGreenWorld(pGrp);

anotherGreenWorld 接口实现

 1 u8 CQuizDealer::anotherGreenWorld(u8* pGrp)
2 {
3 u8 valsCells[100] = {0};
4 u8 size = pGrp[0];
5 for (u8 idx = 1; idx <= size; ++idx) {
6 u8 valSum = m_seqCell[pGrp[idx]].candidates[0];
7 for (u8 vidx = 1; vidx <= valSum; ++vidx) {
8 u8 val = m_seqCell[pGrp[idx]].candidates[vidx];
9 u8 pos = val * 10 + valsCells[val];
10 valsCells[pos] = pGrp[idx];
11 valsCells[val] += 1;
12 }
13 }
14
15 u8 timesVals[100] = {0};
16 for (u8 val = 1; val < 10; ++val) {
17 u8 times = valsCells[val];
18 if (times == 0)
19 continue;
20 u8 pos = times * 10 + timesVals[times];
21 timesVals[pos] = val;
22 timesVals[times] += 1;
23 }
24
25 for (u8 times = 2; times <= 6; ++times) {
26 if (times > timesVals[times])
27 continue;
28 u8 ret = incompleteShrinkByAGW(times, timesVals, valsCells, pGrp);
29 if (ret != RET_PENDING)
30 return ret;
31 }
32 return RET_PENDING;
33 }

incompleteShrinkByAGW 接口实现

 1 u8 CQuizDealer::incompleteShrinkByAGW(u8 times, u8* pTimesVals, u8* pValsCells, u8* pGrp)
2 {
3 u8 combi[10] = {0};
4 combi[0] = pTimesVals[times];
5 for (u8 idx = 0; idx < times; ++idx)
6 combi[idx + 1] = idx;
7 u8 base = times * 10;
8 while (true) {
9 u8 celSet[10] = {0};
10 u8 valSet[10] = {0};
11 if (matchValsCells(times, combi, pTimesVals, pValsCells, celSet, valSet)) {
12 valSet[0] = times;
13 bool shrunken = false;
14 for (u8 idx = 1; idx <= times; ++idx) {
15 u8 cel = celSet[idx];
16 u8 candiSum = m_seqCell[cel].candidates[0];
17 if (candiSum == times)
18 continue;
19 if (candiSum < times) {
20 printf("AGW: [%d,%d] candidates %d lower than times %d!\n", (int)(cel / 9 + 1), (int)(cel % 9 + 1), (int)candiSum, (int)times);
21 return RET_WRONG;
22 }
23 shrunken = true;
24 for (u8 pos = 1; pos <= candiSum; ++pos) {
25 u8 val = m_seqCell[cel].candidates[pos];
26 if (!inSet(val, valSet)) {
27 removeVal(m_seqCell[cel].candidates, val);
28 printf("%d shrunken out of [%d,%d] by AGW\n", (int)val, (int)(cel / 9 + 1), (int)(cel % 9 + 1));
29 }
30 }
31 }
32 if (shrunken) {
33 printCelSetValSet(celSet, valSet);
34 return RET_SHRUNKEN;
35 }
36 }
37 if (!move2NextCombi(times, combi))
38 break;
39 }
40 return RET_PENDING;
41 }

辅助函数 move2NextCombi 的实现

 1 bool move2NextCombi(u8 times, u8* pCombi)
2 {
3 if (pCombi[1] == pCombi[0] - times)
4 return false;
5 u8 bound = pCombi[0] - 1;
6 for (u8 idx = times; idx > 0; --idx) {
7 if (pCombi[idx] < bound - (times - idx)) {
8 pCombi[idx] += 1;
9 for (u8 tail = idx + 1; tail <= times; ++tail)
10 pCombi[tail] = pCombi[tail - 1] + 1;
11 return true;
12 }
13 }
14 return true;
15 }

辅助函数 matchValsCells 的实现

 1 bool matchValsCells(u8 times, u8* pCombi, u8* pTimesVals, u8* pValsCells, u8* pCelSet, u8* pValSet)
2 {
3 u8 base = times * 10;
4
5 for (u8 idx = 1; idx <= times; ++idx) {
6 u8 pos = base + pCombi[idx];
7 u8 val = pTimesVals[pos];
8 pValSet[idx] = val;
9 if (pCelSet[0] == 0) {
10 pCelSet[0] = times;
11 memcpy(pCelSet + 1, &(pValsCells[val * 10]), times);
12 continue;
13 }
14 if (0 != memcmp(pCelSet + 1, &(pValsCells[val * 10]), times))
15 return false;
16 }
17 return true;
18 }

辅助函数 printCelSetValSet 的实现

 1 void printCelSetValSet(u8* pCelSet, u8* pValSet)
2 {
3 printf("CelSet: ");
4 for (u8 idx = 1; idx <= pCelSet[0]; ++idx) {
5 u8 cel = pCelSet[idx];
6 printf("[%d,%d] ", (int)(cel / 9 + 1), (int)(cel % 9 + 1));
7 }
8 printf("ValSet: ");
9 for (u8 idx = 1; idx <= pValSet[0]; ++idx) {
10 printf("%d ", (int)pValSet[idx]);
11 }
12 printf("\n");
13 }

版本信息调整

// 1.0 2021/9/20
// 2.0 2021/10/2
// 2.1 2021/10/4
// 2.2 2021/10/10
#define STR_VER "Sudoku Solver 2.3 2021/10/17 by readalps\n\n"

实例分析

继续以 SudokuSolver 1.0:用C++实现的数独解题程序 【二】 里试验过的“最难”数独题为例做分析。第二次 run 命令的输出中还有两处 “ply” 信息,如下所示:

1286) col 9 complete shrunken by group
[9,4]: 2 3 7 8 shrunken to 3 8 worked by row-ply1.
[9,5]: 1 2 3 6 7 8 shrunken to 1 3 8 worked by row-ply1.
[9,6]: 1 2 3 6 8 shrunken to 1 3 8 worked by row-ply1.
[6,6]: 2 6 9 shrunken to 6 9 worked by col-ply2.
[7,6]: 2 9 shrunken to 9 worked by col-ply2.
1288) col 6 shrunken ply-2 by vblk 1

一处是第 9 行的 row-ply1,另一处是紧随其后,第 6 列的 col-ply2。依次来分析一下。

先用 runtil 1286 命令进入当时的上下文:

1286) col 9 complete shrunken by group
860 000 003
023 600 000
070 090 206 050 007 000
010 045 700
080 100 030 541 000 368
038 504 910
090 000 400 Steps:1287
Candidates:
[1,3]: 4 5 9 [1,4]: 2 4 7 [1,5]: 1 2 5 7
[1,6]: 1 2 [1,7]: 1 5 [1,8]: 4 5 7 9
[2,1]: 1 4 9 [2,5]: 1 5 7 8 [2,6]: 1 8
[2,7]: 1 5 8 [2,8]: 4 5 7 8 9 [2,9]: 1 4 5 7 9
[3,1]: 1 4 [3,3]: 4 5 [3,4]: 3 4 8
[3,6]: 1 3 8 [3,8]: 4 5 8 [4,1]: 2 3 4 6 9
[4,3]: 2 4 6 9 [4,4]: 2 3 8 9 [4,5]: 2 3 6 8
[4,7]: 1 6 8 [4,8]: 2 4 8 9 [4,9]: 1 2 4 9
[5,1]: 2 3 6 9 [5,3]: 2 6 9 [5,4]: 2 3 8 9
[5,8]: 2 8 9 [5,9]: 2 9 [6,1]: 2 4 6 7 9
[6,3]: 2 4 6 7 9 [6,5]: 2 6 [6,6]: 2 6 9
[6,7]: 5 6 [6,9]: 2 4 5 9 [7,4]: 2 7 9
[7,5]: 2 7 [7,6]: 2 9 [8,1]: 2 6 7
[8,5]: 2 6 7 [8,9]: 2 7 [9,1]: 2 6 7
[9,3]: 2 6 7 [9,4]: 2 3 7 8 [9,5]: 1 2 3 6 7 8
[9,6]: 1 2 3 6 8 [9,8]: 2 5 7 [9,9]: 2 5 7
The foremost cell with 2 candidate(s) at [1,6] At guess level 5 [1,2] 2
Run time: 270 milliseconds; steps: 1287, solution sum: 1.

1286 步时,第 9 列满足完全收缩的 grp 算法,因而有至少一个空位的候选值收缩为单值,进行填值后调整关联空位的候选值,步数加一,当前输出的实际上是走完 1287 步后的上下文。现在把关注点放到第 9 行,当时 quiz 已填值情况为:

860 000 003
023 600 000
070 090 206
050 007 000
010 045 700
080 100 030
541 000 368
038 504 910
090 000 400

从下一步的输出信息看,第 9 行施用了 row-ply1,且 [9,4]、[9,5]、[9,6] 三个空位都得到了收缩,可推知是左下宫和右下宫的已填值交集作用于第 9 行的第二节所致,即:

{5, 4, 1, 3, 8} ∩ {3, 6, 8, 9, 1} = {1, 3, 8}

所以,[9,4]、[9,5]、[9,6] 三个空位必然要填入 {1, 3, 8} 里的三个数。而第 9 行当时各个空位的候选值分布情况为:

[9,1]: 2 6 7
[9,3]: 2 6 7 [9,4]: 2 3 7 8 [9,5]: 1 2 3 6 7 8
[9,6]: 1 2 3 6 8 [9,8]: 2 5 7 [9,9]: 2 5 7

这就很好地对应上了下一步的输出信息:

[9,4]: 2 3 7 8 shrunken to  3 8 worked by row-ply1.
[9,5]: 1 2 3 6 7 8 shrunken to 1 3 8 worked by row-ply1.
[9,6]: 1 2 3 6 8 shrunken to 1 3 8 worked by row-ply1.

单纯考虑第 9 行当时各个空位的候选值分布情况,这次的不完全收缩也能这样推导出来:

[9,1]、[9,3]、[9,8]、[9,9] 这四个空位的候选值集合的并集为 {2, 5, 6, 7},第 9 行的七个空位的待填值集合为 {2, 5, 6, 7, 1, 3, 8},因而另三个空位的待填值必然为 {1, 3, 8}。

可以依此再实现一个补充的不完全收缩 grp 算法(比如叫做 thirdGreenWorld~),这也进一步推进了 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里的那个推测:grp 算法增强后有可能就不需要使用第二类收缩算法。这一点要明确出来,需要数学上的严格推导。

接着来看第 6 列的 col-ply2。即:

[6,6]: 2 6 9 shrunken to  6 9 worked by col-ply2.
[7,6]: 2 9 shrunken to 9 worked by col-ply2.

当时 quiz 已填值情况为:

860 000 003
023 600 000
070 090 206

050 007 000
010 045 700
080 100 030

541 000 368
038 504 910
090 000 400

第 6 列纵向跨越第 2 宫、第 5 宫、第 8 宫,[6,6] 和 [7,6] 分别属于其中的后两宫。对第 6 列施行 col-ply2,可以推定是把第 2 宫里的 6 和 9 考虑往第 6 列的第二节和第三节的空位上填。

从上面的全体空位候选值分布信息里提取出第 6 列各空位的候选值分布情况,为:

[1,6]: 1 2
[2,6]: 1 8
[3,6]: 1 3 8
[6,6]: 2 6 9
[7,6]: 2 9
[9,6]: 1 2 3 6 8

其中,[9,6] 空位因为刚才的第 9 行的 row-ply1,其候选值集合中只剩下 1、3、8。第 6 列的第二节和第三节共有三个空位,即 [6,6]、[7,6] 和 [9,6],往这三个空位里填 6 和 9,而 [9,6] 的候选值里没有 6 和 9,因而 6 和 9 必然要填入 [6,6] 和 [7,6],即有:

[6,6]: 2 6 9 shrunken to  6 9 worked by col-ply2.
[7,6]: 2 9 shrunken to 9 worked by col-ply2.

这里第 6 列的 col-ply2 得以发生是借助了第 9 行的 row-ply1。第 6 列的 col-ply2 实施的收缩效果在现有的 grp 算法一样可以应对,即:

第 6 列的各空位中,只有 [6,6] 有候选值6,因而直接推定 [6,6] = 6,进而用同样的方法推定 [7,6] = 9,以及 [1,6] = 2,等等。

用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析的更多相关文章

  1. 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析

    SudokuSolver 2.2 程序实现 根据 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里分析,对 2.1 版做了一些改进和尝试. CQuizDealer 类声明部 ...

  2. 用C++实现的数独解题程序 SudokuSolver 2.4 及实例分析

    SudokuSolver 2.4 程序实现 本次版本实现了 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析 里发现的第三个不完全收缩 grp 算法 thirdGreenWor ...

  3. 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析

    SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...

  4. 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析

    引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...

  5. 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析

    SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...

  6. SudokuSolver 1.0:用C++实现的数独解题程序 【二】

    本篇是 SudokuSolver 1.0:用C++实现的数独解题程序 [一] 的续篇. CQuizDealer::loadQuiz 接口实现 1 CQuizDealer* CQuizDealer::s ...

  7. SudokuSolver 2.0:用C++实现的数独解题程序 【一】

    SudokuSolver 2.0 实现效果 H:\Read\num\Release>sudoku.exe Order please: Sudoku Solver 2.0 2021/10/2 by ...

  8. SudokuSolver 1.0:用C++实现的数独解题程序 【一】

    SudokuSolver 1.0 用法与实现效果 SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有: H:\Read\num\Release>sudoku.exe Or ...

  9. 数独GUI程序项目实现

    数独GUI程序项目实现 导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了...我就想自己做一个数独小游戏,也是一个不错的选择. 前期我在网上简单地查看了一些数独游戏的界面,代码.好 ...

随机推荐

  1. 【SpringMVC】RESTFul简介以及案例实现

    RESTful 概念 REST:Representational State Transfer,表现层资源状态转移. 资源 资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成.每个资源 ...

  2. 高并发HHTP实践

    当今,正处于互联网高速发展的时代,每个人的生活都离不开互联网,互联网已经影响了每个人生活的方方面面.我们使用淘宝.京东进行购物,使用微信进行沟通,使用美图秀秀进行拍照美化等等.而这些每一步的操作下面, ...

  3. Android系统编程入门系列之应用内键值对数据的简单保存

    在应用程序间及与用户的通信交互过程中,会产生并传递一系列数据.针对这些数据,有部分是只在应用程序中使用的缓存数据,还有一部分是在不同位置多次或长时间使用的持久化数据. 对于缓存数据来说,通常以代码中定 ...

  4. 使用kubeadm安装kubernetes 1.21

    文章原文 配置要求 至少2台 2核4G 的服务器 本文档中,CPU必须为 x86架构 CentOS 7.8 或 CentOS Stream 8 安装后的软件版本为 Kubernetes v1.21.x ...

  5. 聚类算法与K-means实现

    聚类算法与K-means实现 一.聚类算法的数学描述: 区别于监督学习的算法(回归,分类,预测等),无监督学习就是指训练样本的 label 未知,只能通过对无标记的训练样本的学习来揭示数据的内在规律和 ...

  6. 洛谷P3104 Counting Friends G 题解

    题目 [USACO14MAR]Counting Friends G 题解 这道题我们可以将 \((n+1)\) 个边依次去掉,然后分别判断去掉后是否能满足.注意到一点, \(n\) 个奶牛的朋友之和必 ...

  7. Java 字符串格式化和工具类使用

    前言 我们在做项目时候经常需要对字符串进行处理,判断,操作,所以我就总结了一下java 字符串一些常用操作,和推荐比较好用我在自用的工具类,毕竟有轮子我们自己就不用重复去写了,提供开发效率,剩下的时间 ...

  8. fwm环境APP菜品数据加载失败的优化操作

    1)在项目的.env文件中添加如下一行: RESPONSE_CACHE_ENABLED=true 2)拷贝 laravel-worker.conf.example,将laravel字段替换为域名,并执 ...

  9. 硕盟SM-H2V1 HDMI转VGA 笔记本台式主机HDMI转VGA显示器转接头

    硕盟SM-G2V1  HDMI转VGA高清转换器一款采用优质芯片的HDMI转VGA转换器,快速传输众享1080P的高清画质显示,而且HDMI转VGA高清转换器,采用24k镀金工艺,耐磨.耐腐蚀性强,这 ...

  10. Excel表格中单击一个单元格如何将整行整列变色

    视图->阅读模式 开启阅读模式后 就会显示如下情景,是的你点击任意单元格后,显示整行/整列