代码地址:
Github : https://github.com/O-VIGIA/031702414
PSP表格:
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | 1h | 0.5 |
Estimate | 估计这个任务需要多少时间 | 25h | 26h |
Development | 开发 | 5h | 1h |
Analysis | 需求分析 (包括学习新技术) | 1h | 1h |
Design Spec | 生成设计文档 | 1h | 1h |
Design Review | 设计复审 | 1h | 0.5h |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 1h | 0.5h |
Design | 具体设计 | 1.5h | 0.5h |
Coding | 具体编码 | 5h | 5h |
Code Review | 代码复审 | 1h | 0.5h |
Test | 测试(自我测试,修改代码,提交修改) | 1.5h | 1h |
Reporting | 报告 | 2.5h | 3h |
Test Repor | 测试报告 | 0.5h | 1h |
Size Measurement | 计算工作量 | 0.5h | 0.5h |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 2.5h | 3h |
合计 | 25h | 19h |
编写历程:
第一阶段:
作业发布的第二天才看到竟然又有作业了,然后就用手机打开博客看一下题目。大致扫了一眼发现是数独的题目,然后就想到了自己之前做过的一道极其相似的深搜回溯OJ题目,感觉常规思路应该不会太难打。果不其然,当天我就面向过程打了个深搜做出了简单的九阶数独问题。思考后我发现原来麻烦的是文件读写和命令行传参,和写博客分析。然后我就开始了自己的规划:
第一步:已经打出了最常规的数独引擎填写算法
思考:
Check函数用来判断是否合法
1 行合法性
2 列合法性
3 对于某些特殊阶数独的小宫格合法性
4 需要判断小宫格合法性的数独通过思考发现计算小宫格左上角坐标的公式
n--格子编号 || level--数独阶数 || height--小宫格高度
width--小宫格宽度 || x--小宫格左上角横坐标 || y--小宫格左上角纵坐标
$\x=n/level /height *height
$\y=n %level /width *width
DFS+回溯 用来填写空白的数独部分
1 对所有格子进行编号n 从0开始从左到右从上到下
2 以公式法计算每个编号的所在列所在行 以及 所在小宫的左上角坐标
3 遍历编号 并对为0的格子 尝试填入数字1~9
4 通过Check函数判断填入的数字是否合法 如果合法对编号n+1继续深搜下去 进行步骤三
5 不合法 退后一位 并将编号第n位的格子 还原为0
代码如下
/* Check函数:判断key填入n时是否满足条件 */ /* ---Check Begin--- */ bool Check(int n,int key) { int x, y, height, width; for (int i=0;i<level;i++)// 判断n所在横列是否合法 { int j=n/level;// j为n竖坐标 if (num[j][i]==key) return false; } for (int i=0;i<level;i++)//判断n所在竖列是否合法 { int j=n%level;//j为n横坐标 if (num[i][j]==key) return false; } /* 若为9,8,6,4阶数独则判断小宫格 */ if (level==9||level==8||level==6||level==4) { /* x为n所在的小九宫格左顶点竖坐标 */ /* y为n所在的小九宫格左顶点横坐标 */ /* height为小宫格高度 */ /* weidth为小宫格宽度 */ switch (level) { case 9: { x = n / 9 / 3 * 3; y = n % 9 / 3 * 3; height = 3; width = 3; break; } case 8: { x = n / 8 / 4 * 4; y = n % 8 / 2 * 2; height = 4; width = 2; break; } case 6: { x = n / 6 / 2 * 2; y = n % 6 / 3 * 3; height = 2; width = 3; break; } case 4: { x = n / 4 / 2 * 2; y = n / 4 / 2 * 2; height = 2; width = 2; break; } } for (int i=x;i<x+height;i++)//判断n所在的小九宫格是否合法 { for (int j=y;j<y+width;j++) { if (num[i][j]==key) return false; } } } return true;//全部合法,返回正确 } /* ---Check End--- */ /* DFS函数 :深搜+回溯 解决数独*/ /* ---DFS Begin--- */ int DFS(int n) { if (n>(level*level-1))//所有的都符合,退出递归 { sign = true; return 0; } if (num[n/level][n%level]!= 0) //当前位不为空时跳过 { DFS(n+1); } else { for (int i=1;i<=9;i++)//否则对当前位进行枚举测试 { if (Check(n,i)==true)//满足条件时填入数字 { num[n/level][n%level] = i; ``` DFS(n+1);//继续搜索 if (sign==true) return 0;//返回时如果构造成功,则直接退出 num[n/level][n%level] = 0;//如果构造不成功,还原当前位 } } } ``` } /* ---DFS Begin--- */ /* init_num 函数:初始化num数组 */ /* ---init_num Begin--- */ void init_num() { for (int i = 0; i < 20; ++i) for (int j = 0; j < 20; ++j) num[i][j] = 0; } /* ---init_num end--- */
第二阶段:
写完了主要引擎,就考虑到了命令行传参的问题,于是 百度。
百度之后发现原来
int main(int argc, char *argv[])
main函数里面的参数原来是 则个意思
简单来说 argc 就代表命令行参数的个数 argv[]数组里面是命令行的参数内容(字符串类型)
然后我就开始了自己的思考:
我们的标准命令行是长
Suduku.exe -m 8 -n 10000 -i input.txt -o ouput.txt
这样用命令行传参我需要 阶数8 个数10000 读入文件名input.txt 输出文件名output.txt 这四个参数
测试后我发现 argv[0]是Suduku.exe
我想那么以此类推 argv[2] argv[4] argv[6] argv[8] 不就正是我想要的参数
测试后果真如此
int main(int argc, char *argv[]) { /*从命令行接收参数 */ level = atoi(argv[2]); int m = atoi(argv[2]); int n = atoi(argv[4]); ``` /*对多个数独进行操作*/ for(int kk=1;kk<=n;++kk){ sign=false; init_num(); sudu sudu1(m);//构造出m阶数独 sudu1.Martrix_input((int*)sudu1.Martrix, m, (char*)argv[6]);//从命令行指定的文件名中读入一个m阶数独 sudu1.Martrix_num_sudu((int*)sudu1.Martrix, m);//数独->数组 DFS(0); //数独数组计算引擎 sudu1.Martrix_sudu_num((int*)sudu1.Martrix,m);//数组->数独 sudu1.Martrix_output((int*)sudu1.Martrix, m, (char*)argv[8]);//在命令行指定的文件名中输出数独的解 } ``` }
第三阶段:
解决了命令行传参问题,下面就是文件流操作。
百度了一下发现 C++ fstream 这个库提供了简便的文件流操作
例如:
ifstream infile; infile.open(filename); infile>>“software”; infile.close();
但是为了读入完一个数独后 还需要读入下一个数独
经过极度疯狂思考之后
我还是去百度了,百度之后发现了tellg()和seekg()这两个文件指针操作
简单来说tellg()就是把读完一个数独后的位置传出来
seekg(offset,ios::beg)就是从你指定的地方偏移offset位 当前位ios::cur 起始位ios::beg 末位ios::end
(这个东西我尝试了好久,好多坑 大坑就是打开文件要用二进制 不如可能会出现一点偏移问题)
我开始了自己的思考:
如果我把先把offset初始化为0
每一次读完数独后的位置用tellg()传出来赋值给offset然后从起始位偏移offset读文件 天命hhhh
写入就简单多了直接用附加写的方式 可以在指定文件名后面附加写入下一个数独的解
outfile.open(filename,ios::app);//以后继方式打开文件以便继续写
第四阶段:
这时我忽然需要建立起自己的数独类 因为接受完参数和数据 我就能初始化类了
然后就开始了
不知道为什么我会把这个命令为term
反正就开始了他就长这样(某种原因不能全发代码)
class sudu { public: int row;//row=col行列相等 int *Martrix = new int[row*row];//创建时new一个数组 void Martrix_input(int *Martrix,int row,const char* filename); void Martrix_output(int *Martrix,int row,const char* filename); void Martrix_sudu_num(int *Martrix,int row); void Martrix_num_sudu(int *Martrix,int row); sudu(int a); ~sudu(); };
思想:
sudu(int a) 用level->a来构造一个level阶的数独
*Martrix 用来存放从文件中读出的一个数独数据 在析构函数~sudu()里delete[]掉
Martrix_input 从文件中读取一个等级位level的数独数据
Martrix_output 向文件中写入经过引擎后数独的解
Martrix_sudu_num 咸鱼函数一号 将构造好的数独数据从Martrix传入num 以便本豆芽菜解决问题
Martrix_num_sudu 咸鱼函数二号 将num中已经解决完的数独数据传入Martrix 以便调用类输出文件函数
第五阶段
整理代码,理顺cpp和.h的互相调用,也就是
stdafx.cpp stdafx.h Sudoku.cpp
这三个东西的关系
stdafx.h是用来定义各种资源
stdafx.cpp用来实现stdafx.h中的资源
Sudoku.cpp就跟引用库函数一样引用stdafx.h中的各种资源
然后提交到Git的时候注意建一个 .gitignore 文件用来忽略上传的其他文件
第六阶段
检查分析代码及优化算法
当然我最喜欢的是优化算法和一些数独的有趣故事 这两样我会先看
!!究极算法!!
各种测试.png
!终于到了让我开心的地方
先上大的:
未测先飘
一 数独:因卡因之问
不知道是哪个就两个一起
芬兰数学家因卡拉,花费3个月时间设计出了世界上迄今难度最大的数独游戏,而且它只有一个答案。因卡拉说只有思考能力最快、头脑最聪明的人才能破解这个游戏。这是英国《每日邮报》2012年6月30日的一篇报道。
8 0 0 0 0 0 0 0 0 0 0 3 6 0 0 0 0 0 0 7 0 0 9 0 2 0 0 0 5 0 0 0 7 0 0 0 0 0 0 0 4 5 7 0 0 0 0 0 1 0 0 0 3 0 0 0 1 0 0 0 0 6 8 0 0 8 5 0 0 0 1 0 0 9 0 0 0 0 4 0 0 0 0 5 3 0 0 0 0 0 8 0 0 0 0 0 0 2 0 0 7 0 0 1 0 5 0 0 4 0 0 0 0 5 3 0 0 0 1 0 0 7 0 0 0 6 0 0 3 2 0 0 0 8 0 0 6 0 5 0 0 0 0 9 0 0 4 0 0 0 0 3 0 0 0 0 0 0 9 7 0 0
9阶答案 传说级因卡因之问1: 8 1 2 7 5 3 6 4 9 9 4 3 6 8 2 1 7 5 6 7 5 4 9 1 2 8 3 1 5 4 2 3 7 8 9 6 3 6 9 8 4 5 7 2 1 2 8 7 1 6 9 5 3 4 5 2 1 9 7 4 3 6 8 4 3 8 5 2 6 9 1 7 7 9 6 3 1 8 4 5 2 传说级因卡因之问2: 1 4 5 3 2 7 6 9 8 8 3 9 6 5 4 1 2 7 6 7 2 9 1 8 5 4 3 4 9 6 1 8 5 3 7 2 2 1 8 4 7 3 9 5 6 7 5 3 2 9 6 4 8 1 3 6 7 5 4 2 8 1 9 9 8 4 7 6 1 2 3 5 5 2 1 8 3 9 7 6 4
二 数独 博客标准99数独测试
原题及答案见博客作业
三 数独 骨灰级88数独
0 8 0 7 5 2 0 0 0 0 4 0 0 0 0 0 0 3 0 0 0 6 0 0 5 2 0 0 0 0 0 1 0 0 0 0 0 0 6 0 0 0 0 0 0 0 5 2 0 4 6 0 0 8 0 0 0 0 3 0 0 1 8 0 0 4 0 0 0 3 0 0 0 3 0 8 6 0 0 0 0 0 0 0 0 0 0 2 0 0 1 0 7 0 0 5 0 0 0 0 0 1 0 0 1 0 5 2 0 0 7 3 0 6 0 3 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 8 0 0 0 6 3 0 8 0 0 4 0 0 5 1 0 0 0 0 6 0 0 0 0 0 3 3 0 0 0 2 5 0 0 0 0 0 7 6 0 0 5 0 0 0 1 0 0 7 0
8阶答案 初级: 4 8 1 7 5 2 3 6 1 6 4 3 8 7 2 5 7 3 5 2 1 6 4 8 5 2 8 6 4 3 7 1 8 7 2 1 3 5 6 4 3 1 7 8 6 4 5 2 2 4 6 5 7 8 1 3 6 5 3 4 2 1 8 7 高级: 6 4 2 5 1 3 8 7 5 3 7 8 6 2 4 1 7 1 3 4 8 5 6 2 8 2 1 6 7 4 3 5 4 5 8 7 3 1 2 6 1 8 5 2 4 6 7 3 2 6 4 3 5 7 1 8 3 7 6 1 2 8 5 4 骨灰级: 5 3 1 8 7 2 6 4 6 2 7 4 5 3 8 1 7 1 6 3 4 8 5 2 4 8 2 5 1 6 3 7 1 6 5 2 8 7 4 3 3 7 4 6 2 5 1 8 8 4 3 7 6 1 2 5 2 5 8 1 3 4 7 6
四 数独 标准级66数独
0 0 0 2 3 0 0 0 0 0 0 0 2 0 0 0 0 4 0 0 1 0 0 0 0 5 0 0 1 3 0 0 3 0 6 0 0 0 4 0 0 0 0 0 2 0 6 0 5 0 0 3 0 0 0 0 0 0 0 4 0 3 0 0 0 0 0 6 0 0 0 1 5 0 0 0 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 4 0 0 6 4 0 0 0 1 0 0 0 2
6阶答案: 1 6 4 2 3 5 3 2 5 6 4 1 2 3 6 1 5 4 5 4 1 3 2 6 6 5 2 4 1 3 4 1 3 5 6 2 6 5 4 1 2 3 3 1 2 4 6 5 5 4 6 3 2 1 1 2 3 6 5 4 2 3 1 5 4 6 4 6 5 2 3 1 5 6 4 3 2 1 1 3 2 5 6 4 6 4 1 2 5 3 2 5 3 1 6 4 3 2 6 4 1 5 4 1 5 6 3 2
五 数独 入门级44数独
0 0 0 0 0 2 0 3 0 0 0 0 1 0 4 0 4 0 0 0 0 0 2 0 0 0 0 1 0 1 0 0 1 0 0 0 4 0 1 0 0 0 0 0 0 0 0 2
4阶答案: 3 1 2 4 4 2 1 3 2 4 3 1 1 3 4 2 4 2 1 3 1 3 2 4 2 4 3 1 3 1 4 2 1 3 2 4 4 2 1 2 2 4 3 1 3 1 4 2