用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析
2021/10/17 17:11:33
本文主要是介绍用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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 000 010 045 700 080 100 030 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 及实例分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享