个人项目作业
1.github项目地址
https://github.com/easylliu/gitlearning/tree/master/Sudoku
2.开发时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 50 |
Estimate | 估计这个任务需要多少时间 | 10 | 15 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 120 | 180 |
Design Spec | 生成设计文档 | 无 | 无 |
Design Review | 设计复审 (和同事审核设计文档) | 无 | 无 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 无 | 30 |
Design | 具体设计 | 30 | 60 |
Coding | 具体编码 | 120 | 240 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | ||
Test Report | 测试报告 | 30 | 50 |
Size Measurement | 计算工作量 | 10 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 560 | 975 |
3.解题思路描述
(1).数独终局生成
开始在看到题目时想到的是通过初始点一直往后延伸推导,后来想了一会感觉有点复杂;在思考无果后通过上网查找资料了解到生成终局的两种方法
随机法,矩阵转换法;但相比之下感觉矩阵转换法更加直接,随机法不确定性加大了难度;而由于第一个数已经确定为9,因此第一行第一列不能参与变换,这样通过一个数独变换能产生共计5184种;但我们的要求最多有1000000种,因此想产生200个种子数独,从8个数中取出四位,共有70种取法,而每种取法又对应三种数字交换方式共计210种符合要求,这样产生的数独数就已经超过1000000种符合要求了。
(2).求解数独
在求解数独上,开始自己思考是想通过那些已确定的那些数字出发,沿着他们所在行列块进行推导,但随后感觉有点困难;通过网上查找感觉方法差不多,相对较为直接的就是回溯法求解了,从最开始点一直递归遍历找到所有存在的可能性,在通过行列和3*3块的验证来确定是否符合条件,简单粗暴,在实现上较为简单,初始计划打算就使用了这种方法。
4.设计实现过程
(1).数独终局生成
根据思路想到,首先产生的一个种子出发,首先通过其行列变换最多产生5184个数独,如果还不足的话从那70种取法中随机选出先交换数字产生新的种子数独,然后再行列变换,如此种种,在一定程度上还可以保证随机性,直到产生够所要的数独数即可结束。通过这种思路构建函数也就清晰起来,在数独产生的函数中通过种子数独不断调用矩阵变换函数,然后可将矩阵变换函数拆解成行变换函数和列变换函数,如此这样,函数之间的联系清晰明朗,实现起来目标也很明确。总得来说在这一部分产生4个函数,其直接的关系也是层层调用。通过这样的分析对函数的作用了解也清晰许多,通过他们直接的联系和他们的用途设计单元测试就比较轻松了。
(2).求解数独
通过暴力解法,对从0到81的所有空白进行1到9的选择,然后判断适合符合行列和块的要求,然后递归往下延伸,当后面推不出来递归返回继续延伸推导,直到求解出正确的结果结束。
相对来说这种接法也相对较为直接,从0到81,先填入数字,在判断是否符合行列和块的要求,然后递归向后延伸。如此这样分析,相对复杂点的就是递归的使用,想都来说3个函数足以。主函数调用填空递归函数,递归函数在填入数字后调用检查函数判断是否符合行列块的要求,如此实现。然后相对来说,对递归的单元测试也相对有些麻烦,通过从对函数功能的理解出发,结合和其他函数的联系设计单元测试会更加全面。
5.性能改进
改进:由于产生数独和求解数独的最大值已到达1000000,在输出到文件和读取时的效率问题就显得尤为重要了,因此在这方面花费时间能减就减,因此我就从每次写入到文件的数量上做出了改进,再往文件里写入数独时,我改进到每次写入两百个数独,这样减少写入的次数,提高效率。
由图片中可以看到,调用次数最多的为检查行列块的合理性函数,这也是暴力求解产生的负面效果,其次在面对文件操作时话费的时间较多,仍待改善。
6.代码说明
主要分做两个部分,一个是产生终局数独,一个是求解数独。
这四个为生成终局数独的四个关键函数:
主要就是sudo_create产生种数独通过调用矩阵变换得到扩展的数独,矩阵变换由行变换和列变换组成;
void sudo_create(int sudo_count, char sudo[9][10], char changenum_array[70][5]); //生成数独
int matrix_change(int count, int sudo_count, char sudo[9][10], char output[1000000]); //矩阵变换
void line_range(int init, int n, char mid_sudo[9][10], char sudo[9][10]); //行交换
void column_range(int init, int n, char mid_sudo[9][10], char sudo[9][10]); //列变换
其中最为关键一个:产生种子,调用扩展
void sudo_create(int sudo_count, char sudo[9][10], char changenum_array[70][5]) {
int index_changenum[70] = { 0 }; //是否随机到标识符
char sudo_middle[9][10]; //中间种子数独
int randnum = 0, i, j;
int count = 0; //已生成的数独数
char a, b, c, d;
for (i = 0; i<9; i++) { //初始化
strcpy_s(sudo_middle[i], sudo[i]);
}
count = matrix_change(count, sudo_count, sudo_middle, output); //根据一个种子转换输出
while (count < sudo_count) {
if (count == -1) break;
randnum = rand() % 70 + 0;
if (index_changenum[randnum] == 0) { //未随机到
a = changenum_array[randnum][0];
b = changenum_array[randnum][1];
c = changenum_array[randnum][2];
d = changenum_array[randnum][3];
for (i = 0; i < 9; i++) {
for (j = 0; j < 9; j++) {
if (sudo[i][j] == a) {
sudo_middle[i][j] = b;
}
else if (sudo[i][j] == b) {
sudo_middle[i][j] = a;
}
else if (sudo[i][j] == c) {
sudo_middle[i][j] = d;
}
else if (sudo[i][j] == d) {
sudo_middle[i][j] = c;
}
else
sudo_middle[i][j] = sudo[i][j];
}
}
count = matrix_change(count, sudo_count, sudo_middle, output);
if (count == -1) break;
for (i = 0; i < 9; i++) {
for (j = 0; j < 9; j++) {
if (sudo[i][j] == a) {
sudo_middle[i][j] = c;
}
else if (sudo[i][j] == b) {
sudo_middle[i][j] = d;
}
else if (sudo[i][j] == c) {
sudo_middle[i][j] = a;
}
else if (sudo[i][j] == d) {
sudo_middle[i][j] = b;
}
else
sudo_middle[i][j] = sudo[i][j];
}
}
count = matrix_change(count, sudo_count, sudo_middle, output);
if (count == -1) break;
for (i = 0; i < 9; i++) {
for (j = 0; j < 9; j++) {
if (sudo[i][j] == a) {
sudo_middle[i][j] = d;
}
else if (sudo[i][j] == b) {
sudo_middle[i][j] = c;
}
else if (sudo[i][j] == c) {
sudo_middle[i][j] = b;
}
else if (sudo[i][j] == d) {
sudo_middle[i][j] = a;
}
else
sudo_middle[i][j] = sudo[i][j];
}
}
count = matrix_change(count, sudo_count, sudo_middle, output);
if (count == -1) break;
}
}
//cout << output;
}
这三个为求解数独的主要函数:
主要就是solve_sudo函数调用填空进行递归,填入一个数后就判断其合理性
int solve_sudo(char output[], int count);//求解数独
bool solve_blank(int sudo_num);//填空
bool matrix_judge(int line, int column, int choose);//判断合理性
关键一个函数:递归调用,逐步展开
//填数
bool solve_blank(int sudo_num) {
int line, column, i;
line = sudo_num / 9;
column = sudo_num % 9;
if (sudo_num >= 81)
return true;
if (sudo1[line][column] == 0) {
for (i = 1; i <= 9; i++) {
sudo1[line][column] = i;
if (matrix_judge(line, column, i)) {
if (solve_blank(sudo_num + 1)) return true;
}
sudo1[line][column] = 0;
}
}
else {
return solve_blank(sudo_num + 1);
}
return false;
}