github项目地址
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 610 | 885 |
Analysis | 需求分析 (包括学习新技术) | 60 | 60 |
Design Spec | 生成设计文档 | 30 | 20 |
Design Review | 设计复审 | 20 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 15 |
Design | 具体设计 | 30 | 30 |
Coding | 具体编码 | 360 | 300 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 420 |
Reporting | 报告 | 120 | 70 |
Test Repor | 测试报告 | 40 | 30 |
Size Measurement | 计算工作量 | 20 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 30 |
Total | 合计 | 760 | 985 |
解决思路
处理数独的目的,就是将合法的数字填满所有的空格。而所谓合法,就是该数字与所填空格纵横格和宫格内(如果存在宫)的所有数字不发生重复。
首先,建立一个9*9的二维数组map作为宫格面盘。对于size阶宫格,有size^2个格子.我们可以为所有的格子进行编号(0——size^2-1),并可以通过编号和size求出相应的横纵坐标。
0(0,0) | ||||||||
---|---|---|---|---|---|---|---|---|
15(4,4) | ||||||||
25 | ||||||||
36 | ||||||||
49 | ||||||||
64 | ||||||||
80(8,8) |
然后可以用深度搜索的方法遍历二维数组,对空格进行枚举(1-size)测试,如果合适就填入,如果不合法,就继续枚举。
/*深度搜索*/
bool sudu::Solve(int n)
{
int num = size * size;
/* 数独解完 */
if (n == num)
{
sign = true;
return 0;
}
/* 当前位非空 */
if (map[n / size][n % size] != 0)
{
Solve(n + 1);
}
/* 试填空位 */
else
{
for (int i = 1; i <= size; i++)
{
/* 满足条件时填入数字 */
if (Check(n, i) == true)
{
map[n / size][n % size] = i;
/* 继续下一个位置 */
Solve(n + 1);
if (sign == true)
return 0;
/*失败还原*/
map[n / size][n % size] = 0;
}
}
}
return false;
}
另一个点就是如何判断一个数对于空格是否合法,通过遍历其纵横格和宫格(如果存在)进行比较有无相同数字即可实现。
*判断num是否可以填入空格n*/
bool sudu::Check(int n, int num)
{
int x = n / size;
int y = n % size;
/* 判断(x,y)所在横行是否合法 */
for (int i = 0; i < size; i++)
{
if (map[x][i] == num)
return false;
}
/* 判断(x,y)所在竖列是否合法 */
for (int i = 0; i < size; i++)
{
if (map[i][y] == num)
return false;
}
/* 判断(x,y)所在九宫格是否合法 */
if (size == 9) {
int a = x / 3 * 3;
int b = y / 3 * 3;
for (int i = a; i < a + 3; i++)
{
for (int j = b; j < b + 3; j++)
{
if (map[i][j] == num)
return false;
}
}
}
/* 判断(x,y)所在的八宫格是否合法 */
else if (size == 8) {
int a = x / 4 * 4;
int b = y / 2 * 2;
for (int i = a; i < a + 4; i++)
{
for (int j = b; j < b + 2; j++)
{
if (map[i][j] == num)
return false;
}
}
}
/* 判断(x,y)所在的六宫格是否合法 */
else if (size == 6) {
int a = x / 2 * 2;
int b = y / 3 * 3;
for (int i = a; i < a + 2; i++)
{
for (int j = b; j < b + 3; j++)
{
if (map[i][j] == num)
return false;
}
}
}
/* 判断(x,y)所在的四宫格是否合法 */
else if (size == 4) {
int a = x / 2 * 2;
int b = y / 2 * 2;
for (int i = a; i < a + 2; i++)
{
for (int j = b; j < b + 2; j++)
{
if (map[i][j] == num)
return false;
}
}
}
return true;
}
我很快依照初始的思路完成了代码的大体结构,可是对于文件的读写始终无法有效地实现。于是我去翻看同学的博文,发现同学有的用面对对象的方法。重新审视我面对过程的代码后,就索性按照面对对象的思路重写了代码。于是有了唯一的对象sudu。
class sudu {
public:
/*设置数独盘*/
void SetSaS(int s);
/*从input文件读入数独*/
long Read(long mark, string);
/*将数独写入output文件*/
void Write(string ofname);
/*判断num是否可以填入空格n*/
bool Check(int n, int num);
/*深度搜索*/
bool Solve(int n);
/*显示数独*/
void Display(void);
/*键入数独,无用已被注释*/
void keyin(void);
private:
/*数独面盘*/
int map[9][9];
/*数独阶级*/
int size;
/*解决标志*/
bool sign;
};
在重写过程中发现了原有代码中的许多小错误,比如在八宫格和六宫格的位置合法判断中将行列弄反。整体而言,重写后代码结构更为清晰。主函数如下:
int main(int argc, char* argv[])
{
/*s代表宫格阶级,t代表待解答盘面数目*/
int siz = 0, tim = 0;
/*imark代表读取文件的偏移量*/
long imark = 0;
int inum, onum;
/*从命令行获取参数*/
for (int i = 0; i < argc; i++)
{
if (strlen(argv[i]) == 1)
{
if (i == 2)
siz = atoi(argv[i]);
if (i == 4)
tim = atoi(argv[i]);
}
else if (argv[i][0] == '-' && argv[i][1] == 'i') {
i++;
inum = i;
}
else if (argv[i][0] == '-' && argv[i][1] == 'o') {
i++;
onum = i;
}
}
/*声明一个长度为tim的动态数独数组*/
sudu* suduzu = new sudu[tim];
/*从文件中依次读取tim个数独,解决后写入另一个文件*/
for (int i = 0; i < tim; i++)
{
suduzu[i].SetSaS(siz);
imark = suduzu[i].Read(imark, argv[inum]);
//suduzu[i].keyin();
cout << "unsolved" << endl;
suduzu[i].Display();
suduzu[i].Solve(0);
cout << "solved" << endl;
suduzu[i].Display();
suduzu[i].Write(argv[onum]);
}
return 0;
}
这样,代码基本完成。而后我发现我的代码只能解出一个数独,百思不得其解。最终找到是因为我将DFS判断的sign声明为全局变量,在一个数独解完后并没有将状态改回去。于是我就将sign放入了sudu类中,成功解决。
关于性能分析
在修改管理员权限后,仍无法得出结果。似乎是因为我没有下载相关组件,现在还在下载(下载速度感人)。
总结
在本次作业,了解了许多的陌生概念,但是也只能说是了解,并不能说是掌握。比如我在用git时,几乎是跟着教程完成的,但是过后脑袋还是一片迷糊。所以说还是要多实践。另一方面,对c++多了一些认识,比如面对对象和文件读写。这里有许多困难,所幸大多是我可以克服的,借助同学,借助网络,借助尝试。