一、PSP(个人软件过程)
PSP1.1 | Personal Software Process Stages | 预估耗时(minutes) | 实际耗时(minutes) |
---|---|---|---|
|
|||
Planning |
计划 | 30 | 45 |
|
|||
· Analysis | · 需求分析 (包括学习新技术) | 120 | 140 |
· Code Review | · 代码复审 | 20 | 25 |
· Coding | · 具体编码 | 210 | 230 |
· Coding Standard | · 代码规范 | 20 | 10 |
· Design | · 具体设计 | 30 | 30 |
· Design Review | · 设计复审 | 5 | 5 |
· Design Spec | · 生成设计文档 | 10 | 15 |
· Estimate | · 估计任务所需时间 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 总结 | 30 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Test | · 测试(自我测试,Debug,提交修改) | 110 | 125 |
· Test Report | · 测试报告 | 20 | 30 |
合计 | 625 | 690 |
二、项目要求
1.目标:随机生成N个已解答完毕的的数独棋盘矩阵,在控制台中键入"xxxx.exe -c N"格式的命令后将矩阵输出到当前路径下的‘sudotiku.txt’文件中。
2.限制条件:N值为0~1000000,矩阵不重复。
三、算法思路
1.利用回溯法来决定矩阵中每个数字的填法(编写sudomatrixgenerator函数):首先确定数独矩阵中第一行的数字(利用random头文件包含下的shuffle函数进行1~9的随机排列),从第二行第一个数字开始,尝试依次填入数字,填入后依据数独规则进行可行性判断。如果可以填入该数字,则对下一格进行相同的判断。如果某一格对于任何数字的填入都违反了数独规则,则进行回溯,重新填上一格的数字。
当获得一个可行结果时,算法终止。
2.在主函数中根据键入的参数值多次调用sudomatrixgenerator函数,将所生成的数独矩阵写入文本文件中,并设置异常捕获。
四、具体源码
#include <iostream> #include <chrono>//std下的一个子命名空间,为持续时间类服务chrono::system_clock #include <random>//shuffle随机排列函数 default_random_engine #include <algorithm>//使用for_each循环 #include <functional>//定义了多个类模板 #include<fstream> using namespace std; void sudomatrixgenerator( int num) //数独矩阵生成函数 { int field[9][9] = { 0 }; //随机生成一行1~9 auto init = [](int* list) //使用auto进行变量类型的自动匹配 { for_each(list, list + 9, [=](int &i) //用来遍历list进行操作 =for(int i=0;i<9;i++) { i = &i - list + 1; } ); unsigned seed = chrono::system_clock::now().time_since_epoch().count();//调用当前系统时间作为随机种子seed的初始值 shuffle(list, list + 9, default_random_engine(seed));//生成随机序列,将list至list+9区间内的数值随机排列 }; init(field[0]); //初始化第一行元素 int trylist[9]; init(trylist); //用于确定数字的尝试顺序 auto judge = [&field](int i, int j, int num) -> bool //判断填入的数字是否合法 { for (int k(0); k < j; k++) //判断同一行中是否有重复元素 if (field[i][k] == num) return false; for (int k(0); k < i; k++) //判断同一列中是否有重复元素相同 if (field[k][j] == num) return false; int count = j % 3 + i % 3 * 3; //判断整个3*3区域中是否有重复元素 while (count--) if (!(field[i - i % 3 + count / 3][j - j % 3 + count % 3] - num)) return false; return true; }; function<bool(int, int, int*)>//类模板 fill = [&trylist, &fill, &field, judge](int y, int x, int* numloc) -> bool //用简单回溯方法进行数字的填入 { if (y > 8) return true; if (judge(y, x, *numloc)) { field[y][x] = *numloc; if (fill(y + (x + 1) / 9, (x + 1) % 9, trylist)) return true; } field[y][x] = 0; if (numloc - trylist >= 8) return false; if (fill(y, x, numloc + 1)) return true; }; fill(1, 0, trylist);//确定某位置要填入的数字 //根据参数输出相应的数独矩阵 for (int k = 0; k <= num;k++) { for (int i(0); i < 9; i++) { for (int j : field[i]) cout << j << " "; cout << endl; } cout << endl;//每个矩阵相隔一行 } return; } //总程序入口处 int main(int argc, char *argv[]) { int N;//要输出的矩阵个数 bool check(char *c)//用来判断在命令行中输入的第三个参数是否为数字 { int len = strlen(c);//获取字符串长度 for (int i = 0; i < len; i++) { if (!isdigit(c[i])) return false; } return true; } if (!(argc == 3 && !strcmp(argv[1], "-c") && check(argv[2]))) {//判断输入的命令格式是否符合规范 cout << "参数输入错误!" << endl; return 1; } N = atoi(argv[2]);//将命令行中获取到的第三个字符转换为数字 ofstream out;//定义文件流对象 try { out.open("sudotiku.txt", ios::trunc); //文件不存在则创建,文件存在则清空其中的数据再输入数据 } catch (const std::exception&)//异常捕获 { cout << "打开文件:sudoku.txt 失败!!"; } sudomatrixgenerator(N);//生成N个数独棋盘矩阵 out.close();//关闭sudotiku.txt文件 return 0; }
五、测试运行
cmd窗口下键入命令:
输出至sudotiku.txt中:
测试结果基本无误,未产生重复矩阵。
六、性能分析
n=20的cpu时间:12.541秒
cpu占用:
各函数占用:
七、心得体会
1.本次学习时长大致为11hours,在此次数独棋盘程序编写的过程当中,回溯法的运用无疑是一大关键,其实可以把回溯法看成是递归调用的一种特殊形式。但对于CS的学生来说,从来没使用过回溯法来解决问题(比如迷宫问题和八皇后问题)的情况是很少见的,不过往往是“对症下药”,针对特定的问题进行解答。这些天看了看《算法设计与分析》回溯法相关内容,觉得对回溯法抽象的很好。如果说算法是解决问题步骤的抽象,那么这个回溯法的框架就是对大量回溯法算法的抽象,再结合以前数据结构这门课程里面的深度优先搜索策略来看,运用回溯法解数独问题会在逻辑上更容易接受,同时自己也对C++11的特性有了更加深入的了解,复习了对象与类的基本方法,学会了如何利用git提交源码至Coding服务器上。
2.所遇到的问题:如何快速的确定数独矩阵中第一行的数字 相应解决方法是查阅C++相关书籍(如C++ primer)以及阅读某CSDN博主的博客后尝试运用shuffle方法(附该博主博客地址https://blog.csdn.net/elloop/article/details/50397618)
另一问题是C++函数库的使用问题,在程序调试阶段报错为“无法打开某源文件xxxx”,后证实(以VS2017举例)可在编译器中项目属性一栏的平台工具集中设置其版本为VS2010或以下,再次build即可解决。