1.Github项目地址 :
https://github.com/DFHG10/031702429
2.PSP表格
PSP是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP, 个人开发流程,或称个体软件过程)。
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | 1h | 0.5h |
Estimate | 估计这个任务需要多少时间 | 28h | 26h |
Development | 开发 | 4h | 2h |
Analysis | 需求分析 (包括学习新技术) | 4.5h | 5h |
Design Spec | 生成设计文档 | 2h | 2h |
Design Review | 设计复审 | 2h | 1h |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 1h | 0.5h |
Design | 具体设计 | 2h | 1h |
Coding | 具体编码 | 6h | 8h |
Code Review | 代码复审 | 1.5h | 1h |
Test | 测试(自我测试,修改代码,提交修改) | 0.5h | 1h |
Reporting | 报告 | 1.5h | 2h |
Test Repor | 测试报告 | 0.5h | 0.5h |
Size Measurement | 计算工作量 | 0.5h | 0.5h |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 1h | 1h |
合计 | 28h | 26h |
3.思路描述:
作业刚发布的时候,看到是数独的题,就想到之前做过的八皇后问题,感觉是很类似的,是通过深搜加回溯求解,我就想着这题应该是差不多的。由于之前没怎么做过数独,特意去网站上做了几个数独。做了一圈,发现有很多的做法,网络上提供了三链数法,侯选数法,频率法多种方法。最后思来想去,还是决定用常规方法,也就是回溯(其他方法试过,不过没有成功。。。,留下泪水/(ㄒoㄒ)/~~)。然后去网络上学习vs项目生成,文件管理等等各个没用过的知识。
4.实现过程:
深搜加回溯:我们首先将数独中空缺的部分记录下来,然后依次判断1-9在各个位置上是否能填入,如果能的话则将其填入,进行下一个空缺位置的填入,如果某一个位置所有数字都不能继续填入,那么将本位置还原,回溯到上一层,将上一层所填入的数字清0,重复上述过程,直到所有位置都填入成功,生成数独的一个解。这次是说不考虑多解,所以我是解出数独的一个解就退出求解函数,进行输出。这部分有两个函数,一个判断合法性,一个进行深搜回溯的操作。
文件的输入输出:查找资料之后,决定用c的文件处理进行文件的读写。
错误整理:这部分也不知道会有什么错误,与同学交流之后,想着可以是命令行参数错误,就写了一个判断的函数,判断输入的参数是否错误,这个有待改进。
5.改进思路:
改进思路这部分,主要有两点。一是深搜算法,是从没有填写的格子逐个开始,且每个格子都是从1开始判断,但实际做数独时,对于某些格子我们可以通过同行同列以及宫格之中,已经填写的数,进行直接的判断填写,或者说消除可填写数的“可能性”,这样可以让需判断的格子减少,或者让回溯时次数减少,这样会让算法更好一些。二是对于多解的判断。一般的数独是没有多解的,但如果出现多解,要怎么去完成,这是一个问题。另外,还有一个,就是上面说的改进,也是基于深搜加回溯的基础,能不能换种思路,把平时手动求解时的那些技巧化为编码实现,目前正在尝试。这些目前还只是一些思路,尚处于试验阶段,如果有结果,再进行补充吧。
6.代码说明:
定义全局变量:
int board[10][10];//定义数独二维数组//
int num, x;//定义数独阶数x,盘面数num;
检测合法性函数:检测数字是否能否放在某个格子中
bool Check(int check_number, int check_now_row, int check_now_column,
int check_block_row, int check_block_column)
Check函数用于判断行、列、和宫格的合法性(如果有宫格的话)
check_number:当前待判断数字
int check_now_row:待判断格子行坐标
int check_now_column:待判断格子列坐标
int check_block_row:如果有宫格,为所在宫格左上角第一格行坐标
int check_block_column:如果有宫格,为所在宫格左上角第一格行坐标
行列判断:
for (int i = 0;i <= x;i++)//检查与待检查的坐标同行或同列的位置
{
if (board[check_no_row][i] == check_number || board[i][check_now_column] == check_number)
return false;
}
此外,有对四,六,八,九宫格进行宫格判断,这里举个九宫格的例子:
对于宫格的判断,关键是从当前坐标定位,推断出所在宫格左上角那个格子的坐标,经过纸上画图,得出结论:
左上角行坐标=当前坐标/宫格行数宫格行数
左上角列坐标=当前坐标/宫格列数宫格列数
if (x == 9) {
check_block_row = (check_now_row / 3) * 3;
check_block_column = (check_now_column / 3) * 3;
for (int i = 0;i <= 2;i++)//检查与待检查的坐标同3*3方格的位置
{
for (int j = 0;j <= 2;j++)
{
if (board[check_block_row + i][check_block_column + j] == check_number)
return false;
}
}
}
深搜回溯:
对每个格子进行判断是否有数字,即判断board[now_row][now_column]是否为0;
从第一个为0(即无数字)的格子开始操作,尝试填入数字1~x(x为阶数),并进行合法性分析,调用Check函数;
通过Check函数判断填入的数字是否合法,如果合法,则对下一个空格进行同样的操作;
如果从数字1到数字x均不合法,则说明上一个数填写出错,进行回溯退后上一个数,并将正在操作的格子的数字还原为0;
数独求解完成,则完成。
bool Work(int now_row, int now_column)
{
if (now_row == x)
{
return true;//如果将数独解完,返回true
}
else
{
int next_row, next_column;
int block_row = 0, block_column = 0;
next_column = now_column + 1;
next_row = (next_column >= x ? now_row + 1 : now_row);//如果最后一列,换行
next_column = (next_column >= x ? 0 : next_column);//如果最后一行,列置为0
if (board[now_row][now_column] != 0)//如果当前坐标有数字,则对下一个坐标进行工作
{
if (Work(next_row, next_column)) return true;//如果数独最终有解,则不断向前返回true
}
else
{
for (int i = 1;i <= x;i++)
{
if (Check(i, now_row, now_column, block_row, block_column))
{
board[now_row][now_column] = i;//如果i值合法,则对下一个坐标进行工作
if (Work(next_row, next_column)) return true;
}
}
board[now_row][now_column] = 0;//回溯操作
return false;//如果i的值为1-x均不合法,则返回上一层继续循环
}
return 0;
}
}
命令行判断函数:
int canshu(int argc)//判断命令行参数是否有误
{
if (argc != 9)
{
cout << "参数个数出错!";
return 1;
}
if (x<3 || x>9 || num<0 )
{
cout << "数字出错!";
return 1;
}
return 0;
}
命令行参数:
主函数main可以接收两个参数
int main(int argc, char *argv[])
其中: argc:代表启动程序时,命令行参数个数。C/C++语言规定,可执行程序程序本身的文件名也算一个命令行参数。因此,argc的值至少是1.
argv:是一个指针数组,里面每一个元素都是一个char* 类型的指针,该指针指向一个字符串,即指向命令行参数。如argv[0]指向第一个命令行参数,也就是可执行文件名。argv[1]指向第二个命令行参数……
本题中argv[2],argv[4],argv[6],argv[8]分别对应m,n,输入文件名,输出文件名。注意,我们接受到的参数都是字符型,需要进行转换。
x = atoi(argv[2]);
num = atoi(argv[4]);
然后是文件的读入和写出,在这里其实还好一些,看了书,之后就差不多了解了,这部分有一个问题就是文件使用方式,我先用的“w”,发现只有一个输出,
在这里我用了两个FILE变量,用fopen打开input文件,fscanf读取数据。然后用fopen打开output文件,fprintf输出数据到output,在这里我是输入一个盘面后,立马进行解答的操作,然后输出到output,然后重置数组,不知道会不会出问题,自己做的时候是行的。
FILE* fp1;
FILE* fp2;
fp1 = fopen("input.txt", "r");//只读打开文件
fp2 = fopen("output.txt", "a");// 追加打开文件
fclose(fp2);//关闭文件,防止数据丢失
fclose(fp1//关闭文件,防止数据丢失
7.心得体会:
总结一下,这次的作业过程中,学习到了很多,Visual Studio 项目的创建,.h文件和.cpp文件的关联,Github建立库和文件的上传,VS的C4715警告的解决办法(不是所有控件都返回路径)/(ㄒoㄒ)/~~(这个处理了好久),Code Quality Analysis工具使用,性能分析工具Studio Profiling Tools(这个还是不太会用)还是学到了一些东西,但是花的时间也很多。。。继续努力吧,上次的学习计划也在慢慢步入正轨,加油。
用例测试:
VS的C4715警告:
BOOL MyClass::GetValue()
{
if(……)
return 0;
else if(……)
return 1;
}
上面的函数有一个很明显的漏洞:当if……else if…… 不包括所有的条件在内,也就是说有可能会出现条件不符合if(……),也不符合else if(……)的情况,这时候函数就不知道该返回什么值了。经过修改,把警告消除了。