题目地址:https://edu.cnblogs.com/campus/fzu/SoftwareEngineering2015/homework/859
Github项目地址:SudokuProject
PSP表格记录
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 20 |
Development | 开发 | 500 | 850 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 20 | 20 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 40 | 0 |
· Design | · 具体设计 | 120 | 220 |
· Coding | · 具体编码 | 120 | 300 |
· Code Review | · 代码复审 | 40 | 100 |
· Test | · 测试(自我测试,修改代码,提交修改) | 100 | 40 |
Reporting | 报告 | 40 | 50 |
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 20 | 0 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 50 |
合计 | 550 | 920 |
随机生成数独
1. 解题思路描述
一开始看到这个题目,便想到之前写过的八皇后和马遍历棋盘这两个程序,所以没有查阅资料,我便开始按照这两个程序的思路来写这道题,数独盘81空每空枚举1-9进行判断,若可以就到下一个,若不行就回溯。算法很简单粗暴的写出来了,但是问题很明显:一是做不到随机,二是这种做法在生成数独盘速度较慢,在一些特殊情况下会一直循环。
紧接着便开始上网查找产生随机数的资料,在C/C++中产生随机数(rand,srand用法) ,在写入数字时,不是按照1-9这样的顺序,而是用随机数方法打乱1-9的排列顺序,从这打乱的顺序中取数填入。
在然后测试过程中发现检查数字在该行、该列、该九宫格是否重复上花时太多,于是改进判断方法,记得从数构课上得到经验:通常改进一个程序可从牺牲空间赢时间和牺牲时间赢空间两个方向来考虑。本次要求缩短时间,于是多浪费一些空间,多用几组数组来记录数字在该行、该列、该九宫格是存在,而不是用for循环来验证。
2. 设计实现过程
以下是主要的函数和相应功能:
- void randnumber(int *a); 乱序排列1-9
- void flagbool(int x,int y,int z); //第x行y列写入z,同步rowbool,colbool,tabbool做相应处理
- bool rowbool[9][9]={0}; rowbool[i][j]=1表示第i行已经有数字j
- bool colbool[9][9]={0}; colbool[i][j]=1表示第i列已经有数字j
- bool tabbool[9][9]={0}; tabbool[i][j]=1表示第i个九宫格已经有数字j
- void printSudoku(int a[9][9]);//按数独棋盘形式打印
- bool check(int x,int y,int z); //冲突检查,是否可以将第x行第y列的数设为z
- void fillnumbers(int a[9][9],int step); //深度递归,
3. 代码说明
因为题目要求随机,所以增加了这个随机排列1~9的功能函数,之前用srand((unsigned)(time(0)));但是速度太快,time(0)函数还是同一个值,所以修改为t=time(0); srand((unsigned)(t++));
int t=time(0);
/**随机排列1~9**/
void randnumber(int *a)
{
int temp=0,randnum;
for(int i=0;i<9;i++)
{
a[i]=i+1;
}
srand((unsigned)(t++));
for(int i=0;i<9;i++)
{
randnum=rand()%9;
temp=a[i];
a[i]=a[randnum];
a[randnum]=temp;
}
}
使用该递归函数实现回溯算法,空格枚举随机排列数组index[]中的数,如果不冲突就写入,处理下一个位置,冲突了就退一步。这个算法是参照之前写的八皇后算法。
/*递归函数*/
void fillnumbers(int a[9][9],int step)
{
if(newnum==number) return;
if(step == 81) //是最后一步,输出可行解
{
printSudoku(a);
newnum++;
}
else
{
int x=step/9,y=step%9;
if(a[x][y] != 0) //已经填啦数字,处理下一个
{
fillnumbers(a,step+1);
}
else
{
int index[9];
randnumber(index); //取随机数填入
for(int k=0;k<9;k++)
{
if(check(x,y,index[k])) //尝试第x行、第y列填index[k]
{
a[x][y]=index[k];
flagbool(x,y,index[k]);
fillnumbers(a,step+1);
a[x][y]=0;
clearflagbool(x,y,index[k]);
}
}
}
}
}
使用数组标记数字是否在某行、某列、某小九宫格出现过,之后检验冲突时只要访问这个数组是0还是1就行,不用for来遍历整行、整列、整个小九宫格,大大节约了时间。
bool rowbool[9][9]={0}; //rowbool[i][j]=1表示第i行已经有数字j
bool colbool[9][9]={0}; //colbool[i][j]=1表示第i列已经有数字j
bool tabbool[9][9]={0}; //tabbool[i][j]=1表示第i个九宫格已经有数字j
void flagbool(int x,int y,int z) //第x行y列写入z,同步rowbool,colbool,tabbool做相应处理
{
rowbool[x][z]=1;
colbool[y][z]=1;
int t=x/3*3+y/3;
tabbool[t][z]=1;
}
bool check(int x,int y,int z) //是否可以将第x行第y列的数设为z
{
if(rowbool[x][z]==1) return false; //第m行是否有重复出现数字z
if(colbool[y][z]==1) return false; //第n列是否有重复出现数字z
int t=x/3*3+y/3;
if(tabbool[t][z]==1) return false; //所在小九宫格是否有重复出现数字z
return true;
}
4. 测试运行
- 在cmd打开,进入sudoku.cpp应用程序所在位置,执行编译、运行 ![](http://images2017.cnblogs.com/blog/1057325/201709/1057325-20170910212245132-820139913.png)
- 这是生成的10000个数独 ![](http://images2017.cnblogs.com/blog/1057325/201709/1057325-20170910212258444-181118331.png)
- 用vs2015做的性能分析 ![](http://images2017.cnblogs.com/blog/1057325/201709/1057325-20170912235254500-230950959.png)
不出意料,显然回溯法fillnumbers()函数占用CPU最大 ![](http://images2017.cnblogs.com/blog/1057325/201709/1057325-20170912225828328-1978197316.png)
(9月17日改)
单元测试
对check函数进行单元测试,输入相应参数。(这个搞了好久)
#include "stdafx.h"
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTest2
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此输入测试代码
int *at;
int xt = 1, yt = 2, zt = 3;
ArraysGenerate arryGen;
at = arryGen.toGenerate();
Assert.IsTrue()(check(at,xt,yt,zt));
}
bool check(int a[9][9], int x, int y, int z)
{
for(int j = 0; j < 9; j++)
if(a[x][j] == z)
return false;
for(int i = 0; i < 9; i++)
if(a[i][y] == z)
return false;
int m=(x/3)*3, n=(y/3)*3;
for(int i = m; i < m+3; i++)
for(int j = n; j < n+3; j++)
if (a[i][j] == z)
return false;
return true;
}
};
}
测试结果:
总结分析和读后感#
本次作业总的来说,我并未在算法上投入太多心思,主要是看到后面作业要求时是一脸懵逼,所以在初次按照自己思路敲出的代码后便把重点放在后面几步。花了比较多的时间去熟悉Github,和安装使用vs2015,但是结果不进如人意,也没怎么搞懂,后面也没有太多时间来改进自己的思路,希望在以后能掌握好这两款工具。
这几天也粗浅的看了《构建之法》的前三章,有一些方面也还不理解,但是也有些感触很深——团队开发效率和个人写高质量代码(个人理解是有较高的容错性)。现在的软件项目开发一个人是进行不来的,所以非常侧重团队的合作。本书后面几章讲到了团队合作开发要怎样的流程,工作项目如何分再如何汇总成果等。而程序员要写出高质量代码,熟练的掌握专业工具和良好的开发习惯非常重要。像在第二章中中的单元测试和效能分析工具就非常的好。这两个要点希望自己能尽快掌握下来。