软件工程 | https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1 |
作业要求 | https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10494 |
作业目标 | 实现一个数独程序,并进行分析和测试 |
作业正文 | 正文如下 |
参考文献 | 百度资料以及其他大佬们的方法 |
一、Github地址
https://github.com/Yura-hub/homework/tree/master/20177638/src/soduku/src
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟 |
Planning | 计划 | 20min | 20min |
Estimate | 估计这个任务需要多少时间 | 12h | 20h |
Development | 开发 | 1h | 1h |
Analysis | 需求分析 (包括学习新技术) | 30min | 1h30min |
Design Spec | 生成设计文档 | 30min | 30min |
Design Review | 设计复审 | 30min | 3h |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30min | 1h |
Design | 具体设计 | 40min | 8h |
Coding | 具体编码 | 40min | 2h |
Code Review | 代码复审 | 2h | 1h |
Test | 测试(自我测试,修改代码,提交修改) | 20min | 30min |
Reporting | 报告 | 2h | 2h |
Test Repor | 测试报告 | 10min | 30min |
Size Measurement | 计算工作量 | 3h | 3h |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 1h | 1h |
合计 | 12h | 20h |
三、思路描述
-
初始思考
刚拿到题目,我整个人是懵圈的,第一反应是题目好长好长(是真的很长)一时不知从何下手,然后把网页×掉了...×掉..了
第二次鼓起勇气打开作业,沉住气看了一遍,了解了这是个编写数独程序的任务,要求是经过分析消除错误警告,再进行单元测试以及改进...然后我发现我不懂算法....学!于是开始到处找资料...确定用回溯。
-
需求
实现一个命令行程序,不妨称之为Sudoku。
-
规则
- 百度百科简介:
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
- 题目要求:
1.程序能生成不重复的数独终局至文件;
2.程序能读取文件内的数独问题,求一个可行解并将结果输出到文件; - 查找资料:
首先我去网上搜索了一下解决数独问题有哪些方法,网上有很多什么回溯算法、三链数法、侯选数法、频率法等等balabala一堆。最后决定用比较常规的方法——回溯。我并不熟悉这个算法的使用,参考了这里的资料:LeetCode 31:递归、回溯、八皇后、全排列一篇文章全讲清楚
还有在实现过程中遇到了很多不会的,百度了n次。
- 百度百科简介:
四、实现过程
分为了两个部分Sudoku和Lib类,也就是解数独和FileIO两部分。
- 结构图
- 测试用例
五、改进过程
- Code Quality Analysis工具分析
- 性能测试
原谅我使用JProfiler还不熟练...分析的还是看不太明白(顺便bb一句,英语有必要好好学一下..不然就像我一样——看不懂...微笑.jpg)
六、代码说明
- 回溯
/**
* 回溯填充方法
* @param row
* @param col
*/
public void backtrack(int row, int col) {
if(shudoPan[row][col] == 0) {
for(int d = 1; d <= m; d++) {
int idx = 0;
if(boxRow > 0) {
idx = (row / boxRow ) * boxRow + col / boxCol;
}
if(couldPlace(d, row, col)) {
//填充数字,并设置填充限制
boxOccupied[idx][d]++;
rowOccupied[row][d]++;
colOccupied[col][d]++;
shudoPan[row][col] = d;
//是否填充到最后一格
if ((col == m-1) && (row == m-1)) {
sudokuSolved = true;
}
else {
//当到达最后一列的格子,下一个格子跳转到下一行
if (col == m-1) {
backtrack(row + 1, 0);
}else {
backtrack(row, col + 1);
}
}
if(!sudokuSolved) {//移除填充后无法进行后续填充的数
boxOccupied[idx][d]--;
rowOccupied[row][d]--;
colOccupied[col][d]--;
shudoPan[row][col] = 0;
}
}
}
}else {
if ((col == m-1) && (row == m-1)) {
sudokuSolved = true;
}
else {
//当到达最后一列的格子,下一个格子跳转到下一行
if (col == m-1) {
backtrack(row + 1, 0);
}else {
backtrack(row, col + 1);
}
}
}
}
- 解数独
/**
* 解数独方法
*/
public void solveSudoku(int[][] shudoPan) {
setBox();//调用设置宫的行列数方法
//System.out.println("boxRow,boxCol:"+boxRow+" "+boxCol);
// 初始化某数所在行、列、宫
for (int i = 0; i < m; i++) {
for (int k = 0; k < m; k++) {
int num = shudoPan[i][k];
if (num != 0) {
int d = num;
if(boxRow > 0) {
int idx = (i / boxRow ) * boxRow + k / boxCol;
boxOccupied[idx][d]++;
}
rowOccupied[i][d]++;
colOccupied[k][d]++;
}
}
}
backtrack(0, 0);
}
}
- 写入文件
/**
* 写入文件
*/
public void writeFile(boolean isDone){
try {
FileWriter bw = null;
File newFile = new File(outputPath);
if(!newFile.exists()) {
newFile.createNewFile();
}
if(isDone == true) {
bw = new FileWriter(newFile,isDone);
{
for(int i = 0;i < m; i++) {
for(int k = 0; k < m; k++) {
if(k != m-1) {
bw.write(String.valueOf(shudoPan[i][k]));
bw.write(" ");
}else {
bw.write(String.valueOf(shudoPan[i][k]));
bw.write("
");
}
}
}
bw.write("
");
bw.flush();
bw.close();
}
}else {
bw = new FileWriter(newFile,isDone);
bw.write("");
}
}catch(IOException e) {
e.printStackTrace();
}
}
- 读文件
/**
* 读取
* @param inputPath
*/
public void readFile() {
//判断文件是否存在
if(inputPath==null||!(new File(inputPath).exists())){
System.out.println("输入文件不存在……");
System.exit(1);
}
//为了防止错误,用catch捕捉错误并打印
try (FileReader reader = new FileReader("");
BufferedReader br = new BufferedReader(reader)
) {
while(true) {
try {
String line = br.readLine();
if(!line.equals("")) {
hang.add(line);
}else {
continue;
}
}catch(NullPointerException ex) {
break;
}
}
if(hang.size() < m*n) {
System.out.println("读取文件为空……");
System.exit(1);
}
}catch(IOException e) {
e.printStackTrace();
}
}
七、异常处理
参数传入,输入格式不正确,或者找不到目标文件,还有读取对象创建失败都有可能报错。
八、心路历程以及收获
数独的解法我通过查资料,网上找到了许多方法,代码好不容易通过了。
但是...文件输入输出流这里我又卡了....
加了这个以后首先传不了参数,后来又找不到文件,最后改了很久很久,问了班上的大佬(哈哈打扰了很久,非常感谢!!)之后重新建了一遍类还有文件,才通过,关于代码我倒是没有改,不知道之前是错了哪里。做完之后,测试了一下三宫格和五宫格,成功用文本输出了。还存在很多问题没有完善,准备慢慢改,之前的基础不好,很多东西学了就忘了,导致这次作业从开始到结束都遇到了各种大大小小的问题,进行的很困难,中间一度中断过几次。目前这样还有很大的改进空间,比如对异常的处理,还有许多要学习的地方,面对错误现在还不够淡定非常抓狂,最后好好学习..天天向上..感谢大佬感谢百度!!
九、自我评价
作业头是否完整 2' | 2 |
Github地址 1' | 1 |
代码要求经过Code Quality Analysis工具的分析并消除所有的警告 2' | 2 |
PSP表格 2'(估计1'实际1') | 2 |
实际过程 2'(代码组织0.5'、关键函数画出流程图1'、单元测试0.5') | 1 |
改进程序性能 2'(展示关键代码1'、解释思路并说明1') | 0.5 |
结合构建之法谈心路历程和感想 1' | 0.5 |
总分 12' | 9 |