用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析
SudokuSolver 2.2 程序实现
根据 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里分析,对 2.1 版做了一些改进和尝试。
CQuizDealer 类声明部分的修改
- class CQuizDealer
- {
- public:
- ...
void run(ulong tilsteps = 0);- void setOnlyGrpMode() {m_onlyGrp = true;}
- ...
private:- ...
- CQuizDealer() : m_state(STA_UNLOADED), ..., m_onlyGrp(false) {};
- inline void incSteps() {++m_steps;}
- inline bool IsDone(u8 ret) {return (ret == RET_OK || ret == RET_WRONG);}...
- u8 filterOneGroup(u8* pGrp);
- bool completeShrinkByGrp(u8* pGrp, u8* pTimes);
- u8 incompleteShrinkByGrp(u8 valSum, u8* pCelSumExs, u8* pGrp);
- bool sameCandidates(u8 cel1, u8 cel2);
- ...
ulong m_steps;- bool m_onlyGrp;
- ...
- };
新增的 setOnlyGrpMode 接口用于控制只采用第一类收缩算法求解。
filterCandidates 接口实现修改
- u8 CQuizDealer::filterCandidates()
- {
- incSteps();
- u8 ret = RET_PENDING;
- for (u8 row = 0; row < 9; ++row)
- if (ret = filterRowGroup(row))
- return ret;
- for (u8 col = 0; col < 9; ++col)
- if (ret = filterColGroup(col))
- return ret;
- for (u8 blk = 0; blk < 9; ++blk)
- if (ret = filterBlkGroup(blk))
- return ret;
- if (!m_onlyGrp) {
- for (u8 row = 0; row < 9; ++row) {
- ret = filterRowCandidatesEx(row);
- if (IsDone(ret))
- return ret;
- }
- for (u8 col = 0; col < 9; ++col) {
- ret = filterColCandidatesEx(col);
- if (IsDone(ret))
- return ret;
- }
- }
- if (ret == RET_SHRUNKEN) {
- printf("incomplete shrink met, filter again\n");
- return filterCandidates();
- }
- return ret;
- }
增加了 onlyGrp 控制,即上面所说的是否只采用第一类收缩算法求解。末尾增加的条件递归调用,源自上一篇的实例分析。
filterRowGroup 接口实现修改
- u8 CQuizDealer::filterRowGroup(u8 row)
- {
- u8 celIdxs[10] = {0}; // first item denotes sum of zeros
- u8 base = row * 9;
- for (u8 col = 0; col < 9; ++col) {
- if (m_seqCell[base + col].val == 0) {
- celIdxs[0] += 1;
- celIdxs[celIdxs[0]] = base + col;
- }
- }
- if (celIdxs[0] == 0)
- return RET_PENDING;
- u8 ret = filterOneGroup(celIdxs);
- if (ret == RET_OK)
- printf("%u) row %d complete shrunken by group\n", m_steps, (int)row + 1);
- else if (ret == RET_WRONG)
- printf("%u) row %d shrink by group went WRONG\n", m_steps, (int)row + 1);
- else if (ret == RET_SHRUNKEN)
- printf("%u) row %d incomplete shrunken by group\n", m_steps, (int)row + 1);
- return ret;
- }
filterColGroup 和 filterBlkGroup 接口修改类似。
filterOneGroup 接口新实现
- 1 u8 CQuizDealer::filterOneGroup(u8* pGrp)
- 2 {
- 3 u8 times[20] = {0};
- 4 if (completeShrinkByGrp(pGrp, times))
- 5 return RET_OK;
- 6
- 7 u8 size = pGrp[0];
- 8 u8 celSumExs[100] = {0};
- 9 for (u8 idx = 1; idx <= size; ++idx) {
- 10 u8 valSum = m_seqCell[pGrp[idx]].candidates[0];
- 11 u8 base = valSum * 10;
- 12 celSumExs[base] += 1;
- 13 u8 pos = base + celSumExs[base];
- 14 celSumExs[pos] = pGrp[idx];
- 15 }
- 16
- 17 for (u8 idx = 2; idx <= 6; ++idx) {
- 18 u8 ret = incompleteShrinkByGrp(idx, celSumExs, pGrp);
- 19 if (ret != RET_PENDING)
- 20 return ret;
- 21 }
- 22 return RET_PENDING;
- 23 }
filterOneGroup 接口的原有实现只支持完全收缩,现在放到了新增 completeShrinkByGrp 接口里:
- bool CQuizDealer::completeShrinkByGrp(u8* pGrp, u8* pTimes)
- {
- u8 size = pGrp[0];
- for (u8 idx = 1; idx <= size; ++idx) {
- u8 sum = m_seqCell[pGrp[idx]].candidates[0];
- for (u8 inn = 1; inn <= sum; ++inn) {
- u8 val = m_seqCell[pGrp[idx]].candidates[inn];
- pTimes[val] += 1;
- pTimes[val + 9] = pGrp[idx];
- }
- }
- bool ret = false;
- for (u8 val = 1; val <= 9; ++val) {
- if (pTimes[val] == 1) {
- ret = true;
- u8 celIdx = pTimes[val + 9];
- m_seqCell[celIdx].candidates[0] = 1;
- m_seqCell[celIdx].candidates[1] = val;
- }
- }
- return ret;
- }
新增 incompleteShrinkByGrp 接口实现
- 1 u8 CQuizDealer::incompleteShrinkByGrp(u8 valSum, u8* pCelSumExs, u8* pGrp)
- 2 {
- 3 u8 base = valSum * 10;
- 4 if (pCelSumExs[base] < valSum)
- 5 return RET_PENDING;
- 6 u8 itemSum = 0;
- 7 Item items[9];
- 8 for (u8 pos = 1; pos <= pCelSumExs[base]; ++pos) {
- 9 u8 idx = 0;
- 10 for (; idx < itemSum; ++idx)
- 11 if (sameCandidates(pCelSumExs[base + pos], items[idx].celIdxs[0])) {
- 12 items[idx].celIdxs[items[idx].sameSum] = pCelSumExs[base + pos];
- 13 items[idx].sameSum++;
- 14 break;
- 15 }
- 16 if (idx == itemSum) {
- 17 items[itemSum].sameSum = 1;
- 18 items[itemSum].celIdxs[0] = pCelSumExs[base + pos];
- 19 ++itemSum;
- 20 }
- 21 }
- 22 for (u8 idx = 0; idx < itemSum; ++idx) {
- 23 if (items[idx].sameSum > valSum)
- 24 return RET_WRONG;
- 25 }
- 26 bool shrunken = false;
- 27 for (u8 idx = 0; idx < itemSum; ++idx) {
- 28 if (items[idx].sameSum < valSum)
- 29 continue;
- 30 for (u8 pos = 1; pos <= pGrp[0]; ++pos) {
- 31 if (inSet(pGrp[pos], (u8*)&(items[idx])))
- 32 continue;
- 33 u8 isVals[10] = {0};
- 34 u8 cel1 = items[idx].celIdxs[0];
- 35 u8 cel2 = pGrp[pos];
- 36 intersection(m_seqCell[cel1].candidates, m_seqCell[cel2].candidates, isVals);
- 37 if (isVals[0] == 0)
- 38 continue;
- 39 shrunken = true;
- 40 for (u8 valIdx = 1; valIdx <= isVals[0]; ++valIdx)
- 41 if (!removeVal(m_seqCell[cel2].candidates, isVals[valIdx]))
- 42 return RET_WRONG;
- 43 }
- 44 }
- 45 return (shrunken ? RET_SHRUNKEN : RET_PENDING);
- 46 }
里面用到的 Item 结构以及 sameCandidates 接口为:
- struct Item {
- u8 sameSum;
- u8 celIdxs[9];
- Item() {sameSum = 0;}
- };
bool CQuizDealer::sameCandidates(u8 cel1, u8 cel2)
{
u8 sum = m_seqCell[cel1].candidates[0];
if (sum != m_seqCell[cel2].candidates[0])
return false;
for (u8 idx = 1; idx <= sum; ++idx)
if (m_seqCell[cel1].candidates[idx] != m_seqCell[cel2].candidates[idx])
return false;
return true;
}
filterRowCandidatesEx 接口实现小修改
- u8 CQuizDealer::filterRowCandidatesEx(u8 row)
- {
- ...
for (u8 idx = 0; idx < 3; ++idx) {- ret = filterRowByPolicy1(row, idx, vals, blkTakens);
- if (IsDone(ret))
- return ret;
- }
- for (u8 idx = 0; idx < 3; ++idx) {
- ret = filterRowByPolicy2(row, idx, vals, blkTakens);
- if (IsDone(ret))
- return ret;
- }
- return ret;
- }
filterColCandidatesEx 接口的修改类似。
shrinkRowCandidatesP1 接口实现小修改
- u8 CQuizDealer::shrinkRowCandidatesP1(u8* pBlk, u8* pRow, u8 zeroSum, u8 row, u8 colBase)
- {
- ...
- u8 shrunken = 0;
- for (u8 col = colBase; col < colBase + 3; ++col) {
- ...
if (ret != RET_PENDING) {- printf(" worked by row-ply1.\n");
- if (ret == RET_OK)
- shrunken = 1; // complete
- else if (ret == RET_SHRUNKEN && shrunken == 0)
- shrunken = 2; // incomplete
- }
- }
- if (shrunken == 1) {
- ret = RET_OK;
- printf("%u) row %d shrunken ply-1 by blk %d\n", m_steps, (u8)row + 1, (u8)colBase / 3 + 1);
- }
- else if (shrunken == 2)
- ret = RET_SHRUNKEN;
- return ret;
- }
shrinkRowCandidatesP2 接口以及 shrinkColCandidatesP1 和 shrinkColCandidatesP2 接口的修改类似。
其他小修改
- // 1.0 2021/9/20
- // 2.0 2021/10/2
- // 2.1 2021/10/4
- #define STR_VER "Sudoku Solver 2.2 2021/10/10 by readalps\n\n"
- void showOrderList()
- {
- ...
- printf("onlygrp: only using group shrink\n");
- printf("step: step forward\n");
- ...
- }
- void dealOrder(std::string& strOrder)
- {
- ...
else if ("onlygrp" == strOrder)- CQuizDealer::instance()->setOnlyGrpMode();
- else if ("step" == strOrder)
...
实例分析
继续以 SudokuSolver 1.0:用C++实现的数独解题程序 【二】 里试验过的“最难”数独题为例做分析。在第一次 run 命令的输出中有如下信息:
- 506) Forward guess [1,4] level 11 at 2 out of 2
- [2,1]: 1 4 5 9 shrunken to 5 9 worked by row-ply2.
- [2,8]: 4 5 9 shrunken to 5 9 worked by row-ply2.
- 509) Guess [1,3] level 12 at 1 out of 2
第 2 行有两个空位发生了不完全收缩,走的不是第一类收缩算法(by grp),而是第二类收缩算法(by ply)。重新用 runtil 506 命令进到当时的上下文做深入分析:
- ...
506) Forward guess [1,4] level 11 at 2 out of 2- 820 703 601
- 003 600 807
- 070 090 203
- 050 007 104
- 000 045 706
- 007 100 935
- 001 009 568
- 008 500 319
- 090 000 472
- Steps:506
- Candidates:
- [1,3]: 4 5 9 [1,5]: 5 [1,8]: 4 5 9
- [2,1]: 1 4 5 9 [2,2]: 1 4 [2,5]: 1 2 5
- [2,6]: 1 2 4 [2,8]: 4 5 9 [3,1]: 1 4 5 6
- [3,3]: 4 5 6 [3,4]: 4 8 [3,6]: 1 4 8
- [3,8]: 4 5 [4,1]: 2 3 6 9 [4,3]: 2 6 9
- [4,4]: 2 3 8 9 [4,5]: 2 3 6 8 [4,8]: 2 8
- [5,1]: 1 2 3 9 [5,2]: 1 3 8 [5,3]: 2 9
- [5,4]: 2 3 8 9 [5,8]: 2 8 [6,1]: 2 4 6
- [6,2]: 4 6 8 [6,5]: 2 6 8 [6,6]: 2 6 8
- [7,1]: 2 3 4 7 [7,2]: 3 4 [7,4]: 2 3 4
- [7,5]: 2 3 7 [8,1]: 2 4 6 7 [8,2]: 4 6
- [8,5]: 2 6 7 [8,6]: 2 4 6 [9,1]: 3 5 6
- [9,3]: 5 6 [9,4]: 3 8 [9,5]: 1 3 6 8
- [9,6]: 1 6 8
- The foremost cell with 1 candidate(s) at [1,5]
- At guess level 11 [1,4] 2
- Run time: 326 milliseconds; steps: 506, solution sum: 0.
- Order please:
由 The foremost cell with 1 candidate(s) at [1,5] 可知当时 [1,5] 位置可以直接填值。先走完这一步:
- Order please:
- step
- 820 753 601
- 003 600 807
- 070 090 203
- 050 007 104
- 000 045 706
- 007 100 935
- 001 009 568
- 008 500 319
- 090 000 472
- Steps:507
- Candidates:
- [1,3]: 4 9 [1,8]: 4 9 [2,1]: 1 4 5 9
- [2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
- [2,8]: 4 5 9 [3,1]: 1 4 5 6 [3,3]: 4 5 6
- [3,4]: 4 8 [3,6]: 1 4 8 [3,8]: 4 5
- [4,1]: 2 3 6 9 [4,3]: 2 6 9 [4,4]: 2 3 8 9
- [4,5]: 2 3 6 8 [4,8]: 2 8 [5,1]: 1 2 3 9
- [5,2]: 1 3 8 [5,3]: 2 9 [5,4]: 2 3 8 9
- [5,8]: 2 8 [6,1]: 2 4 6 [6,2]: 4 6 8
- [6,5]: 2 6 8 [6,6]: 2 6 8 [7,1]: 2 3 4 7
- [7,2]: 3 4 [7,4]: 2 3 4 [7,5]: 2 3 7
- [8,1]: 2 4 6 7 [8,2]: 4 6 [8,5]: 2 6 7
- [8,6]: 2 4 6 [9,1]: 3 5 6 [9,3]: 5 6
- [9,4]: 3 8 [9,5]: 1 3 6 8 [9,6]: 1 6 8
- The foremost cell with 2 candidate(s) at [1,3]
- At guess level 11 [1,4] 2
- Order please:
走完 507 步时,第 2 行的空位候选值分布情况为:
- [2,1]: 1 4 5 9
- [2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
- [2,8]: 4 5 9
这里,5 和 9 只能填入两个空位,即 [2,1] 和 [2,8] 中,因此,这两个空位只能是一个填 5 另一个填 9。于是,这两个空位里的除 5 和 9 之外的其他候选值都可以排除掉。这和上一篇的实例分析部分发现的 grp 算法可改进之处还不一样,这是新发现的一处可以改进 grp 算法的地方。
用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析的更多相关文章
- 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析
SudokuSolver 2.3 程序实现 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析 里新发现了一处可以改进 grp 算法的地方,本次版本实现了对应的改进 grp 算法 ...
- 用C++实现的数独解题程序 SudokuSolver 2.4 及实例分析
SudokuSolver 2.4 程序实现 本次版本实现了 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析 里发现的第三个不完全收缩 grp 算法 thirdGreenWor ...
- 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析
SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...
- 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析
引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...
- 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析
SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...
- 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 ...
- SudokuSolver 1.0:用C++实现的数独解题程序 【一】
SudokuSolver 1.0 用法与实现效果 SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有: H:\Read\num\Release>sudoku.exe Or ...
- 数独GUI程序项目实现
数独GUI程序项目实现 导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了...我就想自己做一个数独小游戏,也是一个不错的选择. 前期我在网上简单地查看了一些数独游戏的界面,代码.好 ...
随机推荐
- Java实现小程序微信支付
小程序支付流程交互图: 进入小程序,下单,请求下单支付,调用小程序登录API来获取Openid,生成商户订单 // pages/pay/pay.js var app = getApp(); Page( ...
- golang error错误处理
error定义 数据结构 go语言error是一普通的值,实现方式为简单一个接口. // The error built-in interface type is the conventional i ...
- vue3 项目 用 vue-video-player 做直播 ( 亲测可用 )
npm 安装 npm install --save vue-video-player npm install --save videojs-flash 1 <template> 2 < ...
- 20210712考试-2021noip11
这篇总结比我写的好多了建议直接去看 T1 简单的序列 考场:愣了一会,想到以最大值分治.每次枚举最大值两侧更小的区间,st表预处理前缀和和最大值,用桶统计答案. 注意分治时要去掉最大值. const ...
- Python - 面向对象编程 - 小实战(3)
需求 房子(House)有户型.总面积.家具名称列表:新房子没有任何的家具 家具(HouseItem)有名字.占地面积 席梦思(bed) 占地 4 平米 衣柜(bed) 占地 2 平米 餐桌(bed) ...
- Lambda表达式——注重过程的编程思想
一.使用匿名内部类的匿名对象创建线程和Lambda表达式写法 Lambda表达式写法不用去定义一个Runable接口的实现类: 二.方法入参是一个接口或者接口的实现类 三.对某个类的一些对象实例进行排 ...
- Vue中使用 iview 之-踩坑日记
导航列表: 一.iview单选框Select验证问题 二.iview表单v-if引起的问题 三.Upload 手动上传组件 使用是出现的问题 四.Tabs嵌套使用时的问题 五.Tooltip 换行问题 ...
- TypeScript 中命名空间与模块的理解?区别?
一.模块 TypeScript 与ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块 相反地,如果一个文件不带有顶级的import或者expo ...
- 【第十一篇】- Git Gitee之Spring Cloud直播商城 b2b2c电子商务技术总结
Git Gitee 大家都知道国内访问 Github 速度比较慢,很影响我们的使用. 如果你希望体验到 Git 飞一般的速度,可以使用国内的 Git 托管服务--Gitee(gitee.com). G ...
- 查看elasticsearch版本的方法
查看elasticsearch版本的方法: 1.elasticsearch已经启动的情况下 使用curl -XGET localhost:9200命令查看: "version" : ...