SudokuSolver 1.0:用C++实现的数独解题程序 【二】
本篇是 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++实现的数独解题程序 【二】的更多相关文章
- 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: ...
- SudokuSolver 1.0:用C++实现的数独解题程序 【一】
SudokuSolver 1.0 用法与实现效果 SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有: H:\Read\num\Release>sudoku.exe Or ...
- 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析
引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...
- 数独GUI程序项目实现
数独GUI程序项目实现 导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了...我就想自己做一个数独小游戏,也是一个不错的选择. 前期我在网上简单地查看了一些数独游戏的界面,代码.好 ...
随机推荐
- 关于python使用的那些事儿
时间:2019-04-11 整理:PangYuaner 标题:Python获取并输出当前日期时间 地址:https://www.cnblogs.com/kerwinC/p/5760811.html 实 ...
- 一次PHP大马提权
记一次PHP提权 发现 PHP大马:指木马病毒:PHP大马,就是PHP写的提取站点权限的程序:因为带有提权或者修改站点功能,所以称为叫木马. 自从师哥那里听说过之后,一直感叹于PHP大马的神奇...但 ...
- tomcat配置启动不了
关于ideatomcat配置问题 1.第一步配置tomcat启动器 2.配置启动的网址 3.配置启动器的启动 ---更多java学习,请见本人小博客:https://zhangjzm.gitee.io ...
- 云原生 AI 前沿:Kubeflow Training Operator 统一云上 AI 训练
分布式训练与 Kubeflow 当开发者想要讲深度学习的分布式训练搬上 Kubernetes 集群时,首先想到的往往就是 Kubeflow 社区中形形色色的 operators,如 tf-operat ...
- 远程线程注入DLL突破session 0 隔离
远程线程注入DLL突破session 0 隔离 0x00 前言 补充上篇的远程线程注入,突破系统SESSION 0 隔离,向系统服务进程中注入DLL. 0x01 介绍 通过CreateRemoteTh ...
- msf宏钓鱼
kali下载python脚本,生成rtf文件: 下载脚本:git clone https://github.com/bhdresh/CVE-2017-8759.git 生成rtf文件: python ...
- Vue3.x全家桶+vite+TS-搭建Vue3.x项目
目录 一.搭建基础项目 1.vite创建项目 3.运行项目 2.环境变量设置介绍 vite配置多环境打包 二.配置Router 1.安装路由 2.配置路由 3.引入 三.配置Vuex 1.安装vuex ...
- 设置自启动nginx(适用于其他软件)(LinuxDeploy里的Ubuntu)
LinuxDeploy里的Ubuntu自启动nginx(适用于其他软件) 网上的教程是这样的,基本能用 1.编写脚本(这个文件及其内容安装Nginx后自动生成,没有的话内容自己Google) $ su ...
- vue中data为什么不写成data:{}这样而是写成data(){return {}}类型。
data:{}:这样会直接挂载在vue实例中,变成全局变量,容易造成污染,再次今日该组件页面,会保留上次的变量值,不会被初始化 data(){return {}} :return包裹后数据中变量只在当 ...
- javascript 编码规范 - 正确使用parseInt
题目描述 修改 js 代码中 parseInt 的调用方式,使之通过全部测试用例 示例1 输入 '12' 输出 12 示例2 输入 复制 '12px' 输出 复制 12 示例3 输入 '0x12' 输 ...