一、Github项目地址
https://github.com/maoshuo1754/sudoku
二、预估时间
PSP2.1 | Personai software process stage | 预估耗时(分钟) |
Planning | 计划 | 30 |
Estimate | 估计这个任务需要多长时间 | 30 |
Development | 开发 | 500 |
Analysis | 需求分析(包括学习新技术) | 180 |
Design Spec | 生成设计文档 | 120 |
Design review | 设计复审 | 60 |
Coding Standard | 代码规范 | 30 |
Design | 具体设计 | 120 |
Coding | 具体编码 | 120 |
Code review | 代码复审 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 600 |
Reporting | 报告 | 300 |
Test Report | 测试报告 | 120 |
Size Measurment | 计算工作量 | 60 |
Postmortem&Process Inprovement Plan | 事后总结,并提供过程改进计划 | 180 |
合计 | 2600 |
三、解题思路描述
数独规则:9*9的方格,满足每一行、每一列、每一个3*3宫内的数字均含1-9,不重复。
刚拿到数独问题,因为以前接触过数独,所以对数独有一定的了解,首先我联想到之前做过的N皇后问题,因为都是N*N的格子,所以我想能不能用同样的方法做,于是尝试了用回溯法写这个程序,因为第一个格子的内容是确定的,所以从第二个格子开始,从1到9尝试不冲突的数字,当填完第81个格子之后也就生成一种终局。
有了这个思路之后很快就写完了代码,发现效果也很不错,至于解数独,只要修改一下输入和生成终局的算法就可以了。整个过程比较顺利。
四、设计实现过程
4.1 总体流程图
4.2 函数组成
整段代码有四个函数,Input(),Output(),check_num(),TraceBack(),其中Inout和Outout是输入输出函数,不必细讲。其中的Check_num函数是判断9*9方格中的某一位置的数字是否符合要求,TraceBack()则是递归函数,其中用Check_num()函数判断当前方格里的数字是否符合要求,如果符合则判断下一方格,否则尝试下一个数字。递归到第81格代表数独方格已经填完,作为解可以输出。
TrackBack()函数流程图
4.3单元测试用例
sudoku.exe -a
sudoku.exe -c
sudoku.exe -c -1
sudoku.exe -c 20
sudoku.exe -c abc
sudoku.exe -c 20 abc
sudoku.exe -s
sudoku.exe -s C:\Users\Joo\Desktop\数独\in.txt
sudoku.exe -s C:\Users\Joo\Desktop\数独\in.txt 20
sudoku.exe -s abc
4.3.2测试结果
五、程序改进
输入为100w时的情况进行测试。
程序运行了46+s,通过VS的性能分析工具列出各个函数所占用CPU的比例,占用最大的是回溯函数Traceback(),占用了99.71%,其中函数体本身的操作占用了20.16%,调用的check_num()函数占用了42.53%,Output()函数占用了36.64%,所以后面两个函数是优化的重点,Traceback()和check_num()两个函数在后面都有介绍。
改进 通过行交换或列交换可以获得新的数独矩阵
通过资料查询发现,通过行交换或列交换可以获得新的数独矩阵,比如对前三行进行排列组合,一共可以得到6个不同的数独,因为题目中要求第一个数字不变,所以我通过交换4-6行,7-9行,4-6列,7-9列,这样的排列组合,通过回溯生成的一个数独可以生成 6^4=1296个数独矩阵,生成100w个就需要回溯生成100w/1296=772个数独矩阵,这样大大降低了回溯算法所占用的时间。
改进后,程序运行了16.664s,且Output()函数占用了CPU的97.37%,所以程序运行的大部分时间都在输出,大大提高了程序的效率。
六、代码说明
代码主要部分为Check_num() 和 TraceBack() 两个函数,下面分别说明
6.1 check_num() 函数
int check_num(int i, int j, int num)//检查在第i行第j列填入数字num是否冲突,冲突的话返回0
{
if (num == 0)
{
return 1;
}
for (int k = 0; k < 9; k++) //检查本行
{
if (a[i][k] == num&&k != j&&a[i][k] != 0)
return 0;
}
for (int k = 0; k < 9; k++) //检查本列
{
if (a[k][j] == num&&k != i&&a[k][j] != 0)
return 0;
}
for (int k = i / 3 * 3; k < i / 3 * 3 + 3; k++)//检查所在九宫格
for (int l = j / 3 * 3; l < j / 3 * 3 + 3; l++)
{
if (a[k][l] == num && !(k == i&&l == j))
return 0;
}
return 1;
}
6.2 TraceBack() 函数
前面已经用流程图的形式解释此函数
void TraceBack(int n) //回溯函数
{
if (sum > SUM - 1) //生成终局总数达到目标,结束整个程序,解数独中SUM值为1
{
exit(0);
}
if (n > 80) //所有81个方格填完,输出此终局并return
{
sum++;
Output(a);
return;
}
//n/9为当前方格所在行数,n%9位列数
if (a[n / 9][n % 9] != 0) //当前方格不为0,代表值固定不能更改,直接进入下一格
{
TraceBack(n + 1);
}
else
{
for (int i = 1; i <= 9; i++)
{
int temp = a[n / 9][n % 9];
//check_num()不为0代表当前值不冲突,可以进入下一格
if (check_num(a, n / 9, n % 9, i))
{
a[n / 9][n % 9] = i;
TraceBack(n + 1);
}
a[n / 9][n % 9] = temp; //还原当前格
}
}
}
七、实际花费时间
PSP2.1 | Personai software process stage | 实际耗时(分钟) |
Planning | 计划 | 30 |
Estimate | 估计这个任务需要多长时间 | 30 |
Development | 开发 | 450 |
Analysis | 需求分析(包括学习新技术) | 180 |
Design Spec | 生成设计文档 | 120 |
Design review | 设计复审 | 60 |
Coding Standard | 代码规范 | 30 |
Design | 具体设计 | 150 |
Coding | 具体编码 | 180 |
Code review | 代码复审 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 270 |
Reporting | 报告 | 300 |
Test Report | 测试报告 | 120 |
Size Measurment | 计算工作量 | 60 |
Postmortem&Process Inprovement Plan | 事后总结,并提供过程改进计划 | 180 |
合计 | 2600 |