这个作业属于哪个课程 | https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1 |
这个作业要求在哪里 | https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10494 |
这个作业的目标 | 实现一个九宫格的命令行程序 |
作业正文 | 正文 |
参考文献 | 百度 CSDN. |
Github地址:https://github.com/199989/test3.1
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(h) | 实际耗时(h) |
---|---|---|---|
Planning | 计划 | 3 | 3 |
Estimate | 估计这个任务需要多少时间 | 29 | 26 |
Development | 开发 | 26 | 21 |
Analysis | 需求分析 (包括学习新技术) | 4 | 6 |
Design Spec | 生成设计文档 | 1 | 1 |
Design Review | 设计复审 | 1 | 0.5 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 1 | 0.5 |
Design | 具体设计 | 1 | 1 |
Coding | 具体编码 | 15 | 10 |
Code Review | 代码复审 | 1 | 1 |
Test | 测试(自我测试,修改代码,提交修改) | 2 | 1 |
Reporting | 报告 | 3 | 5 |
Test Repor | 测试报告 | 1 | 1 |
Size Measurement | 计算工作量 | 1 | 1 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 1 | 3 |
合计 | 32 | 29 |
设计、实现
第一眼看到题目发现竟然是数独题,当时想这和大一大二有什么区别怎么做来做去还是算法题,在认真读完博客后发现这可不仅仅只是像以往一般只需写出算法然后控制台能够输出真确答案,往oj上一提交ac后就能两腿一伸不再管的算法题啊!
代码分两个模块分别为数独算法模块(Sudoku)、文件读取模块(FileIO)。
Sudoku模块
求解数独第一眼我看到是蒙蔽的,毕竟咱就是个算法渣渣,所以通过在网络上搜索一番后恍然大悟,原来利用回溯法便能轻松解决!
backTrace()
/**
* 回溯算法
*
* @param i 行号
* @param j 列号
* @param outSudokus 输出链表
*/
public void backTrace(int i, int j, LinkedList<int[][]> outSudokus) { if (i == rank-1 && j == rank) { int[][] array = new int[9][9]; for(int ai = 0; ai < rank; ai++) { for(int aj = 0; aj < rank; aj++) { array[ai][aj] = matrix[ai][aj]; } } outSudokus.add(array); return; } //已经到了列末尾了,还没到行尾,就换行 if (j == rank) { i++; j = 0; } //如果i行j列是空格,那么才进入给空格填值的逻辑 if (matrix[i][j] == 0) { for (int k = 1; k <= rank; k++) { //判断给i行j列放1-9中的任意一个数是否能满足规则 if (check(i, j, k)) { //将该值赋给该空格,然后进入下一个空格 matrix[i][j] = k; backTrace(i, j + 1, outSudokus); //初始化该空格 matrix[i][j] = 0; } } } else { //如果该位置已经有值了,就进入下一个空格进行计算 backTrace(i, j + 1, outSudokus); } }
这段代码就是利用了回溯法求解数独的核心,其原理就是通过dfs检测到每一个为零的格子后对每一个格子进行填数,填充过程为:对当前所在格从19依次填充,若检测函数检测到此数能够填充便进行填充,随后进入下一格知道某一格出现19都无法填充的情况便开始回溯将之前走过最近的路径都归为0后重新进行判断。所以,一条路走到黑然后能走遍所有路径的backTrace()支持多解的情况
chcek()
/**
* 判断给某行某列赋值是否符合规则
*
* @param row 被赋值的行号
* @param line 被赋值的列号
* @param number 赋的值
* @return */ public boolean check(int row, int line, int number) { //判断该行该列是否有重复数字 for (int i = 0; i < rank; i++) { if (matrix[row][i] == number || matrix[i][line] == number) { return false; } } //当盘面为4,6,8,9时需要对小宫格进行判断 int tempRow = 0; int tempLine = 0; switch(rank) { case 4: tempRow = row / 2; tempLine = line / 2; for(int i = 0; i < 2; i++) { for(int j = 0; j < 2; j++) { if(matrix[tempRow * 2 + i][tempLine * 2 + j] == number) { return false; } } } break; case 6: tempRow = row / 2; tempLine = line / 3; for(int i = 0; i < 2; i++) { for(int j = 0; j < 3; j++) { if(matrix[tempRow * 2 + i][tempLine * 3 + j] == number) { return false; } } } break; case 8: tempRow = row / 4; tempLine = line / 2; for(int i = 0; i < 4; i++) { for(int j = 0; j < 2; j++) { if(matrix[tempRow * 4 + i][tempLine * 2 + j] == number) { return false; } } } break; case 9: tempRow = row / 3; tempLine = line / 3; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (matrix[tempRow * 3 + i][tempLine * 3 + j] == number) { return false; } } } break; default: break; } return true; }
如果说backTrace()是Sudoku的灵魂,那么check()便是backTrace()的伴侣,没有check()的backTrace()就是徒有其表,只会到处瞎转。check()的作用其实也很简单,就是检测当前数字是否能够填进当前格子检测原理为判断将填入数字是否在当前行、列及小宫格中存在。
或许你会对backTrace()中的参数感到迷惑,不急我们下文详解
FileIO模块
好吧我承认算法渣渣的我输入输出流也不怎么会,不会怎么办?去学啊!于是又开始了我的学习+魔改+查api之路,终于是实现了读取txt文件及将内容输出到txt中代码,接着又传来一个噩耗“文件里有多个盘”,???黑人问号,原来我之前没看清楚一个txt文件中是有多个盘的!好吧,继续改,改改。。。最终终于有了如下代码
readFile()
/**
* 读入TXT文件
* 返回一个整型数组包含里面所有数字
*/
public int[] readFile( String pathName) {
int[] array = new int[5000]; int count = 0; try { FileReader reader = new FileReader(pathName); BufferedReader br = new BufferedReader(reader); int number = 0; while ((number = br.read()) != -1) { if((number-48) >= 0) { array[count++] = number - 48; } } br.close(); } catch (IOException e) { e.printStackTrace(); } return array; }
这个方法就是读入文件中的所有数字然后返回一个整型数组。
writeFile()
/**
* 写入TXT文件
*
*/
public void writeFile(String pathName, LinkedList<int[][]> sudokus, int rank) { try { File writeName = new File(pathName); writeName.createNewFile(); FileWriter writer = new FileWriter(writeName); BufferedWriter out = new BufferedWriter(writer); for(int k = 0; k < sudokus.size(); k++) { int[][] array = new int[9][9]; array = sudokus.get(k); for(int i = 0; i < rank; i++) { for(int j = 0; j < rank; j++) { out.write(array[i][j] + 48); out.write(" "); } out.write("\n"); } out.write("\n"); } out.close(); } catch (IOException e) { e.printStackTrace(); } }
写到这个方法或许你就懂了为什么上面backTrace()会有一个链表参数了吧,这个链表就是用来储存所有解的容器!然后在writeFile()中将所有的解打印出来!
或许你的问题又来了,readFile()返回的是一个一维数组我们该怎么操作呢? 这时候我们的splitArray()方法便应运而出啦!
splitArray()
/**
* 将整型readerFile读出的数组分解为不同盘面
* 用LinkedList进行存储二位数组
* 返回一个LinkedList
* @param suCnt面数
* @param rank阶数
*/
public LinkedList<int[][]> splitArray(int[] array, int rank, int suCnt) { LinkedList<int[][]> sudokus = new LinkedList<int[][]>(); int count = 0; for(int k = 0; k < suCnt; k++) { int[][] temp = new int[9][9]; for(int i = 0; i < rank; i++) { for(int j = 0; j < rank; j++) { temp[i][j] = array[count++]; } } sudokus.add(temp); } return sudokus; }
测试结果

心路历程
通过这次作业,才真正意识到什么叫做艰难,真的是步履维艰啊。