软件工程第二次作业
数独的生成
github项目地址
其中,该项目的开发环境为:
- 操作系统:Windows 8.1
- 开发工具:Visual Studio 2017
- 编程语言:C++
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
分析与理解
拿到这个题时,我的第一想法就是:从第一个位置开始遍历,对于每个位置用1-9数字进行尝试,然后将满足数独3个规则的数填入。这样最终可以求得完全的数独。但这只能说明我把问题看的太过简单。
难点1:[关于独立有效的数独数量的讨论][1]。这篇文章的作者简要讨论了数独的所有可能解的数量。其估计值为6.657*1021个相互独立有效的数独。数独的数量巨大,利用最简单的尝试的可能性就越多,也就意味着其生成数独的效率很低。
难点2:仅仅用尝试的方法并不一定能生成完全数独。想一想我们曾做数独题目的经历。很有可能是我们按照规则填入了一些数,却到最后发现无解的情况。这种问题导致的后果往往是程序无法继续执行。
方法与思路
显然,为了快速有效的解决数独生成问题,上面的两个难点必须同时解决。针对第二个问题我们可以采用回溯法进行处理。即当问题处于无解状态时,修改前一位置所填入的数字,进行回溯。
针对第一个难点,我们可以这样进行优化。首先将9*9的数独棋盘按从左往右依次向下的规律分为9个3*3小方格。其标号分别为1号,2号,3号,4号,5号,6号,7号,8号,9号。这样我们可以先产生一组或几组不同的1-9的随机数人工地填入棋盘当中。我的具体做法是产生3组不同的随机数填入到1号,5号和9号方格中(这3个方格相互独立)。这样一来,算法的效率就会大大提升。但导致的结果就是生成的数独数量会减少。没办法,有得必有失,这毕竟是一种合适的折中解决办法。
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 40 |
Development | 开发 | |
· Analysis | · 需求分析 (包括学习新技术) | 30 |
· Design Spec | · 生成设计文档 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 |
· Design | · 具体设计 | 30 |
· Coding | · 具体编码 | 240 |
· Code Review | · 代码复审 | 80 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 |
Reporting | 报告 | |
· Test Report | · 测试报告 | 30 |
· Size Measurement | · 计算工作量 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 |
合计 | 710 |
#### 设计实现
通过以上分析,问题的解决思路变得清晰明了。当然,最终还是要写出能够解决实际问题的代码。
首先,由于问题仅限于生成数独,所以我只设计了一个类。该类包含了:
- int sudo[9][9];//数独盘 - char Buff[200];//缓冲区 - bool result = false;//完成标志 - void randomgenerate();//产生9个不同的数 - bool Isright(int position);//判断所填数是否符合规则
- void backtrack(int position);//回溯法
- void init();//在1,5,9号方格中填入随机数
具体的处理流程为:
- 调用randomgenerate()方法产生1-9范围的9个不同随机数
2.调用init()方法 填入1号,5号,9号3个独立的方格中(分别生成3组不同的随机数)。 - 调用backtrack(int position)方法填写数独棋盘。
- 若填写完成,改变result为true,数独填写过程结束。
代码说明
判断在当前位置填写该数字是是否符合数独规则
/*
*Isright(int position)是用于判断在当前位置填写该数字是是否符合数独规则
*/
bool sudoku::Isright(int position)
{
int row = position / 9;//行位置
int col = position % 9;//列位置
int i;
//与同一行相比较
for (i = 0; i < 9; i++)
{
if (i != col&&sudo[row][i] == sudo[row][col])
return false;
}
//与同一列相比较
for (i = 0; i < 9; i++)
{
if (i != row&&sudo[i][col] == sudo[row][col])
return false;
}
//是否满足3*3小方格规则
int temprow = row / 3 * 3;//所在小方格的第一行
int tempcol = col / 3 * 3;//所在小方格的第一列
for (i = temprow; i < temprow + 3; i++)
{
for (int j = tempcol; j < tempcol + 3; j++)
{
if (sudo[i][j] == sudo[row][col] && i != row&&j != col)
return false;
}
}
return true;//符合规则时返回true
}
关键代码:回溯法
void sudoku::backtrack(int position)
{
if (position ==81)
{
//建立数独已完成
result = true;//改变完成标志
return;
}
int row = position / 9;//行位置
int col = position % 9;//列位置
if (sudo[row][col] == 0)//当前位置未填写
{
for (int i = 1; i <=9; i++)
{
sudo[row][col] = i;//填数
if (Isright(position))//判断是否符合规则
{
backtrack(position+1);//未完成则递归填写下一位置
if (result)//判断是否填写完成
return;
}
}
sudo[row][col] = 0;//回溯
}
else
{
//当前位置已填写,开始下一位置
backtrack(position+1);
}
}
测试运行
按照老师的要求,数独生成程序需要以个人学号尾数作为数独棋盘的第一个数。由于我的尾数为9,所以就默认生成的第一个数为9。
- 正常情况下测试的结果,输入sudoku.exe -c 3,运行结果如下所示:
- 错误情况下的测试结果 :
- 参数不是数字的运行结果如下所示:
- 参数个数不为3的运行结果如下所示:
----------
- 参数不是数字的运行结果如下所示:
性能分析与改进
当N=5000时,CPU,内存使用率以及函数调用所占时间比如下图所示:
从上图我们可以看到,当N=5000时,输出函数所占用时间比最大,达到了2/5的地步,这说明I/O流是程序的最大瓶颈,这表明采用一定的方法减少I/O的次数将大大减少程序的运行时间。其次便是数独的填充函数,占用了1/5的时间比。主要原因便是回溯法的实现方法为递归,这无疑加大了程序的内存开销和程序运行时间。所以此处是一个待优化点。
另外有一个重要的问题是我实现的程序运行效率不稳定,这必然是算法的设计所导致的结果。由于我使用随机数先填写1,5,9号方格再进行数独的填写,这就会导致一种结果:生成的随机数比较“合适”,则回溯次数将大大降低;生成的随机数不“合适”,回溯次数将大大增长,从而导致程序的效率降低。对于此类问题的解决便是设计一种新的稳定有效的算法,不过目前我还没有更好的想法。
在自定义一个缓冲区减少I/O次数后,程序的运行速度明显提高,这同时也体现在输出函数所占的时间比的下降上。
从图中我们可以看出优化效果很大。
PSP(完整版)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 40 | 40 |
Development | 开发 | 950 | |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 100 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 240 | 560 |
· Code Review | · 代码复审 | 80 | 100 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 | 90 |
Reporting | 报告 | 120 | |
· Test Report | · 测试报告 | 30 | 40 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 50 |
合计 | 1120 |
总结
首先,读了构建之法前3章后我才明白之前我限于个人的软件过程更注重于团体的软限于个人的软件过程更注重于团体的软我的个人编程素质仅仅局我的个人编程素质还很低,距离合格还有很大的距离。
就这次软件项目来看,想要完成一个完整的个人软件工程所需要的时间和知识大大超出我的预期。我需要不断进行编程练习和PSP实践,从而提高自己的个人能力。