Github项目地址:https://github.com/2016bits/sudoku.git
附加题Github地址:https://github.com/2016bits/interface_of_sudoku.git
实现一个能够生成数独终局并且能求解数独问题的控制台程序。
实现一个命令行程序:
sudoku.exe -c 20
2 6 8 4 7 3 9 5 1
3 4 1 9 6 5 2 7 8
7 9 5 8 1 2 3 6 4
5 7 4 6 2 1 8 3 9
1 3 9 5 4 8 6 2 7
8 2 6 3 9 7 4 1 5
9 1 7 2 8 6 5 4 3
6 8 3 1 5 4 7 9 2
4 5 2 7 3 9 1 8 6
sudoku.exe -c abc
9 x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
x x x x x x x x x
sudoku.exe -s absolute_path_of_puzzlefile
9 0 8 0 6 0 1 2 4
2 3 7 4 5 1 9 6 8
1 4 6 0 2 0 3 5 7
0 1 2 0 7 0 5 9 3
0 7 3 0 1 0 4 8 2
4 8 0 0 0 5 6 0 1
7 0 4 5 9 0 8 1 6
8 9 0 7 4 6 2 0 0
3 0 5 0 8 0 7 0 9
9 5 8 3 6 7 1 2 4
2 3 7 4 5 1 9 6 8
1 4 6 9 2 8 3 5 7
6 1 2 8 7 4 5 9 3
5 7 3 6 1 9 4 8 2
4 8 9 2 3 5 6 7 1
7 2 4 5 9 3 8 1 6
8 9 1 7 4 6 2 3 5
3 6 5 1 8 2 7 4 9
PSP2.1 | Personal Software Process Stages | 预估耗时 | 实际耗时 |
Estimate | 估计这个任务需要多少时间 | 1month | 15days |
Analysis | 需求分析(包括学习新技术) | 7days | 1days |
Design Spec | 生成设计文档 | 3days | 1days |
Design Review | 设计复核(审核设计文档) | 3days | 1days |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 1days | 2days |
Design | 具体设计 | 10days | 1days |
Code Review | 具体编码 | 1days | 6days |
Test | 测试 | 3days | 1days |
Others | 其他 | 2-3days | 2days |
先审题,发现该题目包括两个部分:生成终局和求解数独,这两部分可以完全分开来做,其公用部分为输入、输出和数独规则的判断,所以可以将其分为两个不同的函数来执行。首先,实现输入的部分:本题与以往题目不同的地方就在于其输入,本题是通过在终端用命令调用exe文件,同时在此过程中传递参数。经百度,了解到main(int argc, char* argv[])中的参数argc和argv就是用来接收终端传来的参数,argc表示参数的个数,argv表示参数的数组。同时,该程序的输入和输出都是在文件中进行的,百度后了解到要用头文件fstream,用ifstream和ofstream来实现输入输出。下面具体分析生成终局和求解数独的做法:
如果完全按照搜索来做,复杂度会相当高,所以查阅相关资料后,结合数独的相关特点,总结了以下几点:
暂时除了深搜没有太好的方法,所以用一个结构体(或类)保存所有空格的行、列、3*3方阵,从而一个个进行试探,如果1-9均不合适,则返回上一层,若到达最后一个数字也没有冲突,则输出该结果。
总体采用了面向对象的思想:
在主函数里面新建对象sudo,通过命令行传来的参数来判断对象具体调用的方法。
生成移动规则并遍历:
char rule0[10][5] = {"036", "063"};
char rule1[10][5] = {"258", "285", "528", "582", "825", "852"};
char rule2[10][5] = {"147", "174", "417", "471", "714", "741"};
int count = 0; //记录终局个数
bool flag = true;
do {
a[8] = '3'; //插入学号 :(2+0)% 9 + 1 = 3
//平移的2*6*6种方式
for (int i = 0; i < 2 && flag; ++i) {
for (int j = 0; j < 6 && flag; ++j) {
for (int k = 0; k < 6 && flag; ++k) {
Movesudo(rule0[i], rule1[j], rule2[k], a, fout);
++count;
if (count >= num) {
flag = false;
break;
}
}
}
}
} while (next_permutation(a, a + 8)); //使用STL全排列函数
根据移动规则将终局输出到文件:
//开始移位并存储在文件中
for (int i = 0; i < 9; ++i) {
int step = steps[i];
fputc(a[(8+step)%9], fout);
for (int j = 1; j < 17; ++j) {
fputc(' ', fout);
fputc(a[((16-j)/2 + step) % 9], fout);
++j;
}
fputc('\n', fout);
}
fputc('\n', fout);
读取文件中的数独题目并存储:
while (!fin.eof()) {
//读取题目
int count = 1; //空格的编号
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
fin >> array[i][j];
if (array[i][j] == 0) {
space[count].row = i;
space[count].col = j;
space[count++].anum = (i / 3) * 3 + (j / 3);
}
}
}
num_space = count - 1;
ok = false;
Tryspace(1);
fout << endl;
}
深搜:
//逐个试探空格中的数字
if (ok) {
return;
}
if (now > num_space) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 8; ++j) {
fout << array[i][j] << " ";
}
fout << array[i][8] << endl;
}
return;
}
int row = space[now].row; //当前空格的所在行
int col = space[now].col; //当前空格的所在列
int anum = space[now].anum; //当前空格的所在方格数
for (int i = 1; i <= 9; ++i) {
bool flag = true; //检测是否符合数独规则
array[row][col] = i; //试探该空格的值
for (int j = 0; j < 9 && flag; ++j) {
//检测该行是否出现重复的数字
if (array[row][j] == i && j != col) {
flag = false;
break;
}
}
for (int j = 0; j < 9 && flag; ++j) {
//检测该列是否出现重复的数字
if (array[j][col] == i && j != row) {
flag = false;
break;
}
}
for (int j = (anum / 3) * 3; j <= (anum / 3) * 3 + 2 && flag; ++j) {
for (int k = (anum % 3) * 3; k <= (anum % 3) * 3 + 2 && flag; ++k) {
//检测该方格里是否出现重复的数字
if (array[j][k] == i && (j != row || k != col)) {
flag = false;
break;
}
}
}
if (flag) {
//向下一个空格试探
Tryspace(now + 1);
}
}
array[row][col] = 0;
通过这个数独项目的学习和制作,我学习了一些在完整软件制作过程中的一些基本技巧,学会了使用性能分析软件来指导自己对代码进行优化。而且,还借此机会学习了CSDN和GitHub的使用。帮助自己养成在开发前进行计划,安排和设计的习惯,为自己以后的软件开发打下了良好的基础。