本篇是 SudokuSolver 1.0:用C++实现的数独解题程序 【一】 的续篇。

CQuizDealer::loadQuiz 接口实现

 1 CQuizDealer* CQuizDealer::sm_pInst = NULL;
2
3 void CQuizDealer::loadQuiz(std::string& strAbsFile)
4 {
5 if (m_state != STA_UNLOADED) {
6 printf("A quiz loaded before.\n");
7 return;
8 }
9 if (strAbsFile.empty()) {
10 printf("No quiz file specified.\n");
11 return;
12 }
13 std::ifstream oFile("H:\\s.txt"); // strAbsFile.c_str()
14 if (oFile.fail()) {
15 printf("Fail to open quiz file %s with err %d:%s.\n", strAbsFile.c_str(),
errno, strerror(errno));
16 return;
17 }
18 int lineCount = 0;
19 int row = -1;
20 char szBuf[1024];
21 while (oFile.getline(szBuf, 1024)) {
22 lineCount++;
23 char* pPos = szBuf;
24 if (0 == *pPos || '#' == *pPos)
25 continue;
26 size_t len = strlen(szBuf);
27 if (len < 9) {
28 printf("Invalid line %d: %s.\n", lineCount, szBuf);
29 return;
30 }
31 ++row;
32 int col = 0;
33 for (int idx = 0; idx < len; ++idx) {
34 char ch = szBuf[idx];
35 if (ch < '0' || ch > '9')
36 continue;
37 m_seqCell[row * 9 + col].val = (u8)(ch - '0');
38 if (++col >= 9)
39 break;
40 }
41 if (row >= 8)
42 break;
43 }
44 printf("Quiz loaded.\n");
45 initTakens();
46 m_state = STA_LOADED;
47 }

这里只是把文本文件里的 quiz 加载到 m_seqCell 数组中,对于取值为 0 的 Cell 此时还不求其候选值。

实际运行时,发现一个很蹊跷的问题,输入交互命令 load-quiz H:\s.txt 居然报错:

Order please:
load-quiz H:\s.txt
Fail to open quiz file H:\s.txt with err 22:Invalid argument. Order please:

而在程序里直接采用如下语句形式

std::ifstream oFile("H:\\s.txt");

就能正常了。没弄清楚问题出在哪里。

loadQuiz 接口的实现代码末尾部分调用了 initTakens 接口。

CQuizDealer::initTakens 和 initTaken 接口实现

 1 void CQuizDealer::initTakens()
2 {
3 initTaken(m_rowTaken);
4 initTaken(m_colTaken);
5 initTaken(m_blockTaken);
6 }
7
8 void CQuizDealer::initTaken(std::vector<std::set<u8> >& vec)
9 {
10 std::set<u8> emptySet;
11 for (u8 idx = 0; idx < 9; ++idx)
12 vec.push_back(emptySet);
13 }

initTakens 接口的作用仅仅是预设 每一行(共 9 行)、每一列(共 9 列)、每一宫(共 9 宫)的已填数集合全为空集。

CQuizDealer::showQuiz 接口实现

 1 void CQuizDealer::showQuiz()
2 {
3 if (m_state == STA_UNLOADED) {
4 printf("Quiz not loaded yet.\n");
5 return;
6 }
7 for (u8 row = 0; row < 9; ++row) {
8 if (row == 3 || row == 6)
9 printf("\n");
10 for (u8 col = 0; col < 9; ++col) {
11 if (col == 3 || col == 6)
12 printf(" ");
13 printf("%d", (int)m_seqCell[row * 9 + col].val);
14 }
15 printf("\n");
16 }
17 if (m_state == STA_INVALID) {
18 if (m_soluSum == 0)
19 printf("\nInvalid quiz [steps:%u]\n", m_steps);
20 else
21 printf("\nInvalid quiz [steps:%u] - no more solution (solution sum is %u)\n", m_steps, m_soluSum);
22 return;
23 }
24 if (m_state == STA_DONE) {
25 printf("\nDone [steps:%u, solution sum:%u].\n", m_steps, m_soluSum);
26 return;
27 }
28 if (m_state != STA_VALID)
29 return;
30 printf("\nCandidates:\n");
31 for (std::set<u8>::iterator it = m_setBlank.begin(); it != m_setBlank.end(); ++it) {
32 u8 pos = *it;
33 u8* pVals = m_seqCell[pos].candidates;
34 printCandidates(pos, pVals);
35 }
36 if (m_guessLevel != 0)
37 printf("\nAt guess level %d [%d,%d] %d\n", (int)m_guessLevel, (int)(m_guessPos / 9 + 1), (int)(m_guessPos % 9 + 1), (int)m_guessValPos);
38 }

其中用到的 printCandidates 函数为:

1 void printCandidates(u8 pos, u8* pVals)
2 {
3 u8 sum = pVals[0];
4 printf("[%d,%d]:", (int)(pos / 9 + 1), (int)(pos % 9 + 1));
5 for (u8 idx = 0; idx < sum; ++idx)
6 printf(" %d", (int)pVals[idx + 1]);
7 printf("\n");
8 }

CQuizDealer::step 接口实现

 1 void CQuizDealer::step()
2 {
3 if (m_state == STA_UNLOADED) {
4 printf("Quiz not loaded yet.\n");
5 return;
6 }
7 else if (m_state == STA_LOADED)
8 parse();
9 else if (m_state == STA_VALID)
10 adjust();
11 else if (m_state == STA_DONE) {
12 if (m_stkSnap.empty()) {
13 printf("No more solution (solution sum is %u).\n", m_soluSum);
14 return;
15 }
16 m_state = STA_VALID;
17 nextGuess();
18 m_steps++;
19 }
20 showQuiz();
21 }

m_state 表示 quiz 的状态。初始状态为 STA_UNLOADED,即 quiz 未加载;通过 load-quiz 命令完成 quiz 加载后,状态变为 STA_LOADED,这时接到 step 命令,step接口会调用 parse 接口对加载的 quiz 做合规分析,随后调用 showQuiz 接口输出分析结果。

quiz 通过分析界定为合规,则状态变为 STA_VALID,这时接到 step 命令,step接口会调用 adjust 接口对 quiz 做下一步的调整处理,随后调用 showQuiz 接口输出调整后的结果。

当 quiz 经若干次调整处理后所有单元格都填上了非 0 数字且依然合规,quiz 的状态则会被置为 STA_DONE,即找到了一个解。这时接到 step 命令,step接口会尝试去寻找下一个解(把状态置为 STA_VALID 并调用 nextGuess 接口)。

CQuizDealer::parse 接口实现

 1 void CQuizDealer::parse()
2 {
3 for (u8 row = 0; row < 9; ++row) /// fill rows taken
4 for (u8 col = 0; col < 9; ++col) {
5 if (!fillTaken(row, m_seqCell[row * 9 + col].val, m_rowTaken))
6 return;
7 }
8 for (u8 col = 0; col < 9; ++col) /// fill cols taken
9 for (u8 row = 0; row < 9; ++row) {
10 if (!fillTaken(col, m_seqCell[row * 9 + col].val, m_colTaken))
11 return;
12 }
13 for (u8 blk = 0; blk < 9; ++blk) /// fill blocks taken
14 for (u8 idx = 0; idx < 9; ++idx) {
15 u8 row = block2row(blk, idx);
16 u8 col = block2col(blk, idx);
17 if (!fillTaken(blk, m_seqCell[row * 9 + col].val, m_blockTaken))
18 return;
19 }
20 for (u8 idx = 0; idx < 81; ++idx) { /// fill candidates
21 if (m_seqCell[idx].val != 0)
22 continue;
23 m_setBlank.insert(idx);
24 if (!calcCandidates(idx)) {
25 m_state = STA_INVALID;
26 return;
27 }
28 }
29 m_state = (m_setBlank.empty() ? STA_DONE : STA_VALID);
30 if (m_state == STA_DONE)
31 m_soluSum++;
32 }

parse 接口仅在 quiz 加载后的第一步求解时被调用。该接口的逻辑很简单,就是由输入的 quiz 填充 m_rowTaken、m_colTaken、m_blkTaken、m_setBlank 以及 m_seqCell 里的各待填格的候选值信息。

如果把一个现成的解作为 quiz 输入,在 parse 接口就会被界定为一个解。

block2row 和 block2col 函数实现

1 u8 block2row(u8 blk, u8 idx) {
2 return (blk / 3) * 3 + (idx / 3);
3 }
4 u8 block2col(u8 blk, u8 idx) {
5 return (blk % 3) * 3 + (idx % 3);
6 }

约定上左、上中、上右三宫的序标为 0、1、2,中左、中心、中右三宫的序标为 3、4、5,下左、下中、下右三宫的序标为 6、7、8。宫内 9 个单元格的序标按同样的规则约定为 0 到 8。通过 block2row 和 block2col 函数可以求出宫序标 blk 且宫内序标 idx 的单元格的行序标 row 和列序标 col。

CQuizDealer::fillTaken 接口实现

 1 bool CQuizDealer::fillTaken(u8 key, u8 val, std::vector<std::set<u8> >& vec)
2 {
3 if (val == 0)
4 return true;
5 if (vec[key].find(val) != vec[key].end()) {
6 m_state = STA_INVALID;
7 return false;
8 }
9 vec[key].insert(val);
10 return true;
11 }

如果输入的 quiz 里某一行(列、宫)里非 0 数字存在重复,则会在 parse 接口的某次 fillTaken 调用里发现,并把 quiz 状态置为 STA_INVALID,即不合规。

CQuizDealer::calcCandidates 接口实现

 1 bool CQuizDealer::calcCandidates(u8 idx)
2 {
3 u8 row = idx / 9;
4 u8 col = idx % 9;
5 u8 blk = (row / 3) * 3 + (col / 3);
6 for (u8 val = 1; val < 10; ++val) {
7 if (m_rowTaken[row].find(val) != m_rowTaken[row].end())
8 continue;
9 if (m_colTaken[col].find(val) != m_colTaken[col].end())
10 continue;
11 if (m_blockTaken[blk].find(val) != m_blockTaken[blk].end())
12 continue;
13 u8 sum = m_seqCell[idx].candidates[0] + 1;
14 m_seqCell[idx].candidates[0] = sum;
15 m_seqCell[idx].candidates[sum] = val;
16 }
17 if (m_seqCell[idx].candidates[0] == 0) {
18 printf("Candidates for [%d,%d] went wrong\n", (int)(idx / 9 + 1), (int)(idx % 9 + 1));
19 return false;
20 }
21 return true;
22 }

calcCandidates 接口对指定的单元格(由输入参数 idx,单元格下标指定),依据该单元格所在行、列、宫的已填数集合,计算其候选值集合,填到 m_seqCell[idx].candidates 里。如果计算出该单元格的候选值集合为空,则输出错误提示信息,并返回 false。

CQuizDealer::adjust 接口实现

 1 void CQuizDealer::adjust()
2 {
3 if (m_state != STA_VALID)
4 return;
5 m_steps++;
6 bool changed = false;
7 bool bWrong = false;
8 u8 guessIdx = 0;
9 u8 lowestSum = 10;
10 for (std::set<u8>::iterator it = m_setBlank.begin(); it != m_setBlank.end();) {
11 u8 idx = *it;
12 u8 sum = m_seqCell[idx].candidates[0];
13 if (sum != 1) {
14 if (sum < lowestSum) {
15 lowestSum = sum;
16 guessIdx = idx;
17 }
18 ++it;
19 continue;
20 }
21 m_seqCell[idx].val = m_seqCell[idx].candidates[1];
22 m_seqCell[idx].candidates[0] = 0;
23 if (!adjustTakens(idx, m_seqCell[idx].val)) {
24 bWrong = true;
25 break;
26 }
27 m_setBlank.erase(it++);
28 changed = true;
29 }
30 if (bWrong) {
31 nextGuess();
32 return;
33 }
34 if (m_setBlank.empty()) {
35 m_state = STA_DONE;
36 m_soluSum++;
37 return;
38 }
39 if (!changed) {
40 guess(guessIdx);
41 return;
42 }
43 if (!reCalcCandidates())
44 nextGuess();
45 }

adjust 接口的主要逻辑是遍历考察 m_setBlank 中记录的待填格。如果待填格的候选值唯一,则直接采用该候选值填空,并调用 adjustTakens 接口去调整行、列、宫的已填数集合:若出现冲突,则置出错标志(即 bWrong = true),退出循环体后会调用 nextGuess 尝试对当时的猜测做出调整;若不出现冲突,则把新填值的单元格从 m_setBlank 中剔除,并继续考察其余的待填格。

完成这一趟遍历考察后,若 m_setBlank 为空,即不再有待填格,此时找到了一个解,quiz 状态会被置为 STA_DONE,解的总数加 1;若 m_setBlank 不为空,则调用 reCalcCandidates 重新计算更新各个待填格的候选值信息,出现冲突时会调用 nextGuess 尝试对当时的猜测做出调整。

如果 m_setBlank 中记录的所有待填格的候选值均不唯一,这一趟遍历考察会找出候选值数量最小(lowestSum)且下标最小(guessIdx)的待填格,然后调用 guess 接口去对 guessIdx 对应的待填格做猜测填数处理。

CQuizDealer::reCalcCandidates 接口实现

1 bool CQuizDealer::reCalcCandidates()
2 {
3 for (u8 idx = 0; idx < 81; ++idx)
4 m_seqCell[idx].candidates[0] = 0;
5 for (std::set<u8>::iterator it = m_setBlank.begin(); it != m_setBlank.end(); ++it)
6 if (!calcCandidates(*it))
7 return false;
8 return true;
9 }

CQuizDealer::adjustTakens 接口实现

 1 bool CQuizDealer::adjustTakens(u8 idx, u8 val)
2 {
3 u8 row = idx / 9;
4 u8 col = idx % 9;
5 u8 blk = (row / 3) * 3 + (col / 3);
6 if (m_rowTaken[row].find(val) != m_rowTaken[row].end()) {
7 printf("%d is in row %d before!\n", (int)val, (int)(row + 1));
8 return false;
9 }
10 m_rowTaken[row].insert(val);
11 if (m_colTaken[col].find(val) != m_colTaken[col].end()) {
12 printf("%d is in col %d before!\n", (int)val, (int)(col + 1));
13 return false;
14 }
15 m_colTaken[col].insert(val);
16 if (m_blockTaken[blk].find(val) != m_blockTaken[blk].end()) {
17 printf("%d is in block %d before!\n", (int)val, (int)(blk + 1));
18 return false;
19 }
20 m_blockTaken[blk].insert(val);
21 return true;
22 }

adjustTakens 接口实现里有合规检查,确保一行、一列、一宫里都不能填入重复的数。

CQuizDealer::guess 接口实现

 1 void CQuizDealer::guess(u8 guessIdx)
2 {
3 ++m_guessLevel;
4 m_guessPos = guessIdx;
5 m_guessValPos = 1;
6 pushIn(guessIdx, 1);
7 printf("Guess [%d,%d] level %d at 1 out of %d\n\n", (int)(guessIdx / 9 + 1), (int)(guessIdx % 9 + 1), (int)m_guessLevel, (int)m_seqCell[guessIdx].candidates[0]);
8 if (!shift(guessIdx, 1))
9 nextGuess();
10 }

guess 接口总是进入更深一级的猜测时,对 guessIdx 所指的待填格使用第一个候选值做出猜测。shift 实施猜测填值并做合规检查,检查不通过则调用 nextGuess 做进一步调整处理。

CQuizDealer::pushIn 接口实现

 1 void CQuizDealer::pushIn(u8 guessIdx, u8 valIdx)
2 {
3 Snapshot* pSnap = new Snapshot;
4 pSnap->guessLevel = m_guessLevel;
5 pSnap->guessPos = guessIdx;
6 pSnap->guessValPos = valIdx;
7 for (u8 idx = 0; idx < 81; ++idx)
8 pSnap->seqCell[idx] = m_seqCell[idx];
9 pSnap->rowTaken = m_rowTaken;
10 pSnap->colTaken = m_colTaken;
11 pSnap->blkTaken = m_blockTaken;
12 pSnap->setBlank = m_setBlank;
13 pSnap->state = m_state;
14 m_stkSnap.push(pSnap);
15 }

猜测填值有多种可能,因而需要把当时的上下文生成一个快照,压入堆栈。以便后面调整上下文遍历其它的可能。

CQuizDealer::shift 接口实现

 1 bool CQuizDealer::shift(u8 idx, u8 valIdx)
2 {
3 m_seqCell[idx].val = m_seqCell[idx].candidates[valIdx];
4 m_seqCell[idx].candidates[0] = 0;
5 if (!adjustTakens(idx, m_seqCell[idx].val))
6 return false;
7 std::set<u8>::iterator it = m_setBlank.find(idx);
8 m_setBlank.erase(it);
9 return reCalcCandidates();
10 }

shift 接口实施猜测填值,并在填值后做合规检查。

CQuizDealer::nextGuess 接口实现

 1 void CQuizDealer::nextGuess()
2 {
3 if (m_stkSnap.empty()) {
4 if (m_soluSum == 0)
5 printf("Quiz is invalid.\n");
6 else
7 printf("No more solution (solution sum is %u).\n", m_soluSum);
8 m_state = STA_INVALID;
9 return;
10 }
11 Snapshot* pTop = m_stkSnap.top();
12 u8 idx = pTop->guessPos;
13 u8 sum = pTop->seqCell[idx].candidates[0];
14 if (pTop->guessValPos != sum) {
15 pTop->guessValPos++;
16 printf("Forward guess [%d,%d] level %d at %d out of %d\n\n", (int)(idx / 9 + 1), (int)(idx % 9 + 1), (int)pTop->guessLevel, (int)pTop->guessValPos, (int)sum);
17 useSnapshot(pTop);
18 if (shift(idx, pTop->guessValPos))
19 return;
20 else {
21 nextGuess();
22 return;
23 }
24 }
25 m_stkSnap.pop();
26 delete pTop;
27 backGuess();
28 }

当求解走到某一级猜测填值,但猜测填值后不合规,这时就会调用 nextGuess 接口做进一步的调整。

nextGuess 首先会看快照堆栈是否为空,若为空,则说明求解过程已经走完了,再不会有新的解。

若快照堆栈不空,则检查栈顶的快照,看当前最深的那次猜测的单元格,猜测值是不是使用了最后一个候选值,如果不是,则尝试做平级猜测调整(调用 useSnapshot 和 shift);否则,弹出并丢弃栈顶快照,随后调用 backGuess 做降级猜测调整处理。

CQuizDealer::useSnapshot 接口实现

 1 void CQuizDealer::useSnapshot(Snapshot* pSnap)
2 {
3 m_guessLevel = pSnap->guessLevel;
4 m_guessPos = pSnap->guessPos;
5 m_guessValPos = pSnap->guessValPos;
6 for (u8 idx = 0; idx < 81; ++idx)
7 m_seqCell[idx] = pSnap->seqCell[idx];
8 m_rowTaken = pSnap->rowTaken;
9 m_colTaken = pSnap->colTaken;
10 m_blockTaken = pSnap->blkTaken;
11 m_setBlank = pSnap->setBlank;
12 m_state = pSnap->state;
13 }

CQuizDealer::backGuess 接口实现

 1 void CQuizDealer::backGuess()
2 {
3 if (m_stkSnap.empty()) {
4 if (m_soluSum == 0)
5 printf("Quiz is invalid.\n");
6 else
7 printf("No more solution (solution sum is %u).\n", m_soluSum);
8 m_state = STA_INVALID;
9 return;
10 }
11 Snapshot* pTop = m_stkSnap.top();
12 u8 idx = pTop->guessPos;
13 u8 sum = pTop->seqCell[idx].candidates[0];
14 if (pTop->guessValPos != sum) {
15 pTop->guessValPos++;
16 printf("Upward guess [%d,%d] level %d at %d out of %d\n\n", (int)(idx / 9 + 1), (int)(idx % 9 + 1), (int)pTop->guessLevel, (int)pTop->guessValPos, (int)sum);
17 useSnapshot(pTop);
18 if (shift(idx, pTop->guessValPos))
19 return;
20 else {
21 nextGuess();
22 return;
23 }
24 }
25 m_stkSnap.pop();
26 delete pTop;
27 backGuess();
28 }

backGuess 接口的实现和 nextGuess 类似,只是回退到上一级尝试做平级猜测调整或降级猜测调整。这两个接口都存在递归调用的情形(既有自身递归调用,又有交叉递归调用)。

CQuizDealer::run 接口实现

 1 void CQuizDealer::run()
2 {
3 if (m_state == STA_UNLOADED) {
4 printf("Quiz not loaded yet.\n");
5 return;
6 }
7 clock_t begin = clock();
8 if (m_state == STA_DONE) {
9 if (m_stkSnap.empty()) {
10 printf("No more solution.\n");
11 return;
12 }
13 m_state = STA_VALID;
14 nextGuess();
15 }
16 if (m_state == STA_LOADED)
17 parse();
18 while (m_state == STA_VALID)
19 adjust();
20 showQuiz();
21 std::cout << "Run time: " << clock() - begin << " milliseconds; steps: " << m_steps << ", solution sum: " << m_soluSum << ".\n\n";
22 }

run 接口和 step 接口实现是类似的,只是调用 adjust 的判断条件由 if (m_state == STA_VALID) 换成了 while (m_state == STA_VALID),这样就不再一步一停,而是一直到求出一个解或者不再有解时才停下来。

试验号称世界最难数独题

从网上以“世界最难数独”为关键字查找,一般都会搜出来如下这个题:

以下是对这个题的求解过程:

H:\Read\num\Release>sudoku.exe

Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
show
800 000 000
003 600 000
070 090 200 050 007 000
000 045 700
000 100 030 001 000 068
008 500 010
090 000 400 Order please:
run
Guess [8,7] level 1 at 1 out of 2
Guess [7,7] level 2 at 1 out of 2
Guess [9,8] level 3 at 1 out of 2
...
6 is in col 2 before!
Upward guess [2,7] level 14 at 2 out of 2
6 is in row 6 before!
Upward guess [2,5] level 13 at 2 out of 2 812 753 649
943 682 175
675 491 283 154 237 896
369 845 721
287 169 534 521 974 368
438 526 917
796 318 452 Done [steps:4879, solution sum:1].
Run time: 2467 milliseconds; steps: 4879, solution sum: 1. Order please:
run ...
3 is in col 2 before!
Upward guess [2,8] level 10 at 2 out of 2 9 is in col 6 before!
Upward guess [1,3] level 8 at 2 out of 2 Candidates for [4,8] went wrong
No more solution (solution sum is 1).
829 000 300
513 600 800
476 090 251 054 007 102
002 045 789
087 100 630 001 000 568
008 500 910
090 000 400 Invalid quiz [steps:9683] - no more solution (solution sum is 1)
Run time: 2220 milliseconds; steps: 9683, solution sum: 1. Order please:
bye H:\Read\num\Release>

从求解过程看,该题有唯一解。求出这个解耗时约 2467 毫秒,用了 4879 步(steps);第二次 run 耗时约 2220 毫秒。两次 run 共计 9683 步。

全空数独题

81 个单元格全部为空时是一个特殊的数独题,它的解是最多的,事实上任意一个数独题的解都是它的解。以下是用程序求其前两个解的过程示意:

H:\Read\num\Release>sudoku.exe

Order please:
load-quiz H:\s.txt
Quiz loaded. Order please:
show
000 000 000
000 000 000
000 000 000 000 000 000
000 000 000
000 000 000 000 000 000
000 000 000
000 000 000 Order please:
run
...
Guess [7,5] level 45 at 1 out of 2 Guess [8,1] level 46 at 1 out of 2 Guess [8,3] level 47 at 1 out of 2 123 456 789
456 789 123
789 123 456 231 674 895
875 912 364
694 538 217 317 265 948
542 897 631
968 341 572 Done [steps:69, solution sum:1].
Run time: 65 milliseconds; steps: 69, solution sum: 1. Order please:
run
Forward guess [8,3] level 47 at 2 out of 2 123 456 789
456 789 123
789 123 456 231 674 895
875 912 364
694 538 217 317 265 948
548 391 672
962 847 531 Done [steps:72, solution sum:2].
Run time: 71 milliseconds; steps: 72, solution sum: 2. Order please:

SudokuSolver 1.0:用C++实现的数独解题程序 【二】的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  9. 数独GUI程序项目实现

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

随机推荐

  1. 分享几个下载豆瓣资源的chrome插件

    最近chrome终于以4.69%的市场占有率击败firefox成为中国第二大浏览器.(第一当然是争霸宇宙的IE了) 虽然chrome官方应用程序商店有不少豆瓣的辅助插件,但大多没什么用.属于蛋疼插件. ...

  2. kratos

    技术文章 日志库的使用姿势 通过 layout 探索 kratos 运行原理 发版日志 发布日志 - kratos v2.0.5 版本发布 发布日志 - kratos v2.0.4 版本发布

  3. MyBatis学习总结(四)——字段名与实体类属性名不相同的冲突的解决

    表中的字段名和表对应实体类的属性名称不一定都是完全相同的,这种情况下的如何解决字段名与实体类属性名不相同的冲突.如下所示: 一.准备演示需要使用的表和数据 CREATE TABLE my_user( ...

  4. 110_SSM框架

    目录 需求分析->功能设计->数据库设计 环境要求 环境 要求 数据库环境 基本环境搭建 创建maven项目 pom.xml添加依赖,添加资源导出 idea连接数据库 提交项目到Git 创 ...

  5. 快速模式第三包:quick_inR1_outI2()

    快速模式第三包:quick_inR1_outI2() 文章目录 快速模式第三包:quick_inR1_outI2() 1. 序言 2. quick_inR1_outI2()的处理流程 3. 快速模式第 ...

  6. windows中ren(rename-重命名)命令的使用方法

    脚本(*.dat文件): cd /d %~dp0 ren * *.mp3

  7. Docker修改容器中的时间

    Docker修改容器中的时间 前言 在公司开发时使用 Docker 创建数据库(SQL Server)的实例十分方便,还原数据库也只要设置好共享文件夹,在 SQL Server Management ...

  8. 一文搞懂如何使用Node.js进行TCP网络通信

    摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...

  9. HDU2094产生冠军 (拓扑排序)

    HDU2094产生冠军 Description 有一群人,打乒乓球比赛,两两捉对撕杀,每两个人之间最多打一场比赛. 球赛的规则如下: 如果A打败了B,B又打败了C,而A与C之间没有进行过比赛,那么就认 ...

  10. PTA 面向对象程序设计 6-1 引用作函数形参交换两个整数

    引用作函数形参交换两个整数 设计一个void类型的函数Swap,该函数有两个引用类型的参数,函数功能为实现两个整数交换的操作. 裁判测试程序样例: #include <iostream> ...