2020软件工程作业03

这个作业属于哪个课程2018级软件工程 - 中南林业科技大学涉外学院
这个作业要求在哪里

2020软件工程作业03

这个作业的目标 通过习题熟悉PSP流程
其他参考

【1】数独~编程作业

【2】数独(Sudoku)游戏之我见

 PSP表格

 

 

 注:加号代表在实际操作时未准确计时,只是一个保守的估计。

 

解题思路

作为一个算法菜鸡,看完题目的第一感觉就是.....没看懂。反复阅读后才冒出一个暴力扫描的想法。正当准备开始编码的时候,我突然看到题目描述中提到 “输入保证有且仅有一个解” ,我才意识到,数独宫格是拥有唯一解的(或者只有少数几个数字不同)。这意味着若是开始的几个格子未能填入正确答案,则后面的格子会出现无数可填的情况(又暴露了没玩过数独的事实😱)。若是出现了无数可填的情况则说明之前有答案是错误的,则需要回溯。可以用DFS来逐个试探答案。最终走到最后一个格子时。意味着所有的格子都已填入了正确答案,此时就可以把输出了。

 

设计实现

Sudoku.java

 1 import java.io.*;
 2 
 3 public class Sudoku {
 4     public static void main(String[] args){
 5         String inputPath = "";
 6         String outputPath = "";
 7         Integer numOfBoard = null;
 8         Integer size = null;
 9         for(int i=0;i<args.length;i++){
10             if(args[i].equals("-i")){
11                 inputPath = args[i+1];
12             }
13             if(args[i].equals("-o")){
14                 outputPath = args[i+1];
15             }
16             if(args[i].equals("-m")){
17                size = Integer.valueOf(args[i+1]);
18             }
19             if(args[i].equals("-n")){
20                 numOfBoard = Integer.valueOf(args[i+1]);
21             }
22         }
23 
24         if(inputPath.equals("") && outputPath.equals("") && numOfBoard == null && size == null){
25             System.out.println("Parameter Error.");
26             return;
27         }
28 
29         try {
30             SudokuUtil sudokuUtil = new SudokuUtil(size, numOfBoard, outputPath, inputPath);
31             sudokuUtil.run();
32         }catch (Exception e){
33             System.out.println("Parameter Error,Please Check Your Format.");
34         }
35 
36     }
37 }

 

首先是主函数入口类,这里主要包括的是输入参数验证以及错误处理。

SudokuUtil.java

  1 package New.Learn;
  2 
  3 import java.io.*;
  4 import java.nio.charset.StandardCharsets;
  5 import java.util.ArrayList;
  6 
  7 public class SudokuUtil {
  8 
  9     Integer size;
 10     Integer numOfBoard;
 11     String outputPath;
 12     String inputPath;
 13     ArrayList<Integer[][]> arrayNumList = new ArrayList<Integer[][]>();
 14     //ArrayList用于容纳多个数独盘的结果
 15 
 16     public Integer getSize() {
 17         return size;
 18     }
 19 
 20     public Integer getNumOfBoard() {
 21         return numOfBoard;
 22     }
 23 
 24     public String getOutputPath() {
 25         return outputPath;
 26     }
 27 
 28     public String getInputPath() {
 29         return inputPath;
 30     }
 31 
 32     SudokuUtil(Integer size, Integer numOfBoard, String outputPath, String inputPath) {
 33         this.size = size;
 34         this.numOfBoard = numOfBoard;
 35         this.outputPath = outputPath;
 36         this.inputPath = inputPath;
 37     }
 38 
 39     public String inputFile(String filePath) throws IOException {
 40         File file = new File(filePath);
 41         if (file.isFile() && file.exists()) {
 42             InputStreamReader read =
 43                     new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
 44             BufferedReader bufferedReader = new BufferedReader(read);
 45             String lineTxt = null;
 46             StringBuilder result = new StringBuilder();
 47             while ((lineTxt = bufferedReader.readLine()) != null) {
 48                 result.append(lineTxt);
 49                 result.append("\n");
 50             }
 51             read.close();
 52             return result.toString();
 53         } else {
 54             System.out.println("No such file or directory");
 55             System.exit(0);
 56         }
 57         return null;
 58     }
 59 
 60     public void outputFile(Integer size, ArrayList<Integer[][]> arrayNum, String filePath) throws IOException {
 61         BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
 62         for (Integer[][] num : arrayNum) {
 63             for (int i = 0; i < size; i++) {
 64                 for (int j = 0; j < size; j++) {
 65                     bufferedWriter.write(num[i][j].toString());
 66                     bufferedWriter.write(" ");
 67                 }
 68                 bufferedWriter.newLine();
 69             }
 70             bufferedWriter.newLine();
 71         }
 72         bufferedWriter.close();
 73     }
 74 
 75     public ArrayList<Integer[][]> inData(Integer numOfBoard, Integer size, String input) {
 76         //分割多个数独盘
 77         String[] board = input.split("\n\n");
 78         ArrayList<Integer[][]> inputArrayNum = new ArrayList<Integer[][]>();
 79 
 80         for (int k = 0; k < numOfBoard; k++) {
 81             Integer[][] num = new Integer[size][size];
 82 
 83             String[] line = board[k].split("\n");
 84             for (int i = 0; i < size; i++) {
 85                 String[] single = line[i].split(" ");
 86                 for (int j = 0; j < size; j++) {
 87                     num[i][j] = Integer.valueOf(single[j]);
 88                 }
 89             }
 90             inputArrayNum.add(num);
 91         }
 92         return inputArrayNum;
 93     }
 94 
 95     public void pr(Integer size, Integer[][] num) {
 96         for (int i = 0; i < size; i++) {
 97             for (int j = 0; j < size; j++) {
 98                 System.out.print(num[i][j]);
 99                 System.out.print(" ");
100             }
101             System.out.print("\n");
102         }
103         System.out.println("-----------------------");
104     }
105 
106     public boolean check(Integer size, Integer[][] num, Integer tar, Integer col, Integer line) {
107         for (int i = 0; i < size; i++) {
108             if (num[i][line].equals(tar) || num[col][i].equals(tar)) {
109                 return false;
110             }
111         }
112         if (size == 4) {
113             //可以把宫格看做是一个大的块
114             int cellx = line / 2;
115             int celly = col / 2;
116 
117             for (int j = 0; j < 2; j++) {
118                 for (int k = 0; k < 2; k++) {
119                     if (num[celly * 2 + j][cellx * 2 + k].equals(tar)) {
120                         return false;
121                     }
122                 }
123             }
124         } else if (size == 6) {
125             int cellx = line / 3;
126             int celly = col / 2;
127 
128             for (int j = 0; j < 2; j++) {
129                 for (int k = 0; k < 3; k++) {
130                     if (num[celly * 2 + j][cellx * 3 + k].equals(tar)) {
131                         return false;
132                     }
133                 }
134             }
135         } else if (size == 8) {
136             int cellx = line / 2;
137             int celly = col / 4;
138 
139             for (int j = 0; j < 4; j++) {
140                 for (int k = 0; k < 2; k++) {
141                     if (num[celly * 4 + j][cellx * 2 + k].equals(tar)) {
142                         return false;
143                     }
144                 }
145             }
146         } else if (size == 9) {
147             int cellx = line / 3;
148             int celly = col / 3;
149 
150             for (int j = 0; j < 3; j++) {
151                 for (int k = 0; k < 3; k++) {
152                     if (num[celly * 3 + j][cellx * 3 + k].equals(tar)) {
153                         return false;
154                     }
155                 }
156             }
157         }
158         return true;
159     }
160 
161     public void write(Integer size, Integer[][] num, Integer wy, Integer wx) {
162         Integer nextx = wx + 1;
163         Integer nexty = wy;
164 
165         if (nextx >= size) {
166             nextx = 0;
167             nexty++;
168         }
169 
170         if (wy.equals(size) && wx.equals(0)) {
171             pr(size, num);
172             Integer[][] tempArray = new Integer[size][size];
173             for (int i = 0; i < size; i++) {
174                 for (int j = 0; j < size; j++) {
175                     tempArray[i][j] = num[i][j];
176                 }
177             }
178             //若直接传递num,则该num为递归栈顶值,因为只是传递了num地址
179             //因而需要进行一次深拷贝
180             arrayNumList.add(tempArray);
181             return;
182         }
183         if (!num[wy][wx].equals(0)) {
184             //若该项不为0,则递归走下一步
185             write(size, num, nexty, nextx);
186         } else {
187             for (int x = 1; x <= size; x++) {
188                 if (check(size, num, x, wy, wx)) {
189                     num[wy][wx] = x;
190                     //DFS递归走下一步
191                     write(size, num, nexty, nextx);
192                     num[wy][wx] = 0;
193                 }
194             }
195 
196         }
197 
198     }
199 
200     public void run() throws IOException {
201         String in = inputFile(inputPath);
202         ArrayList<Integer[][]> inArrayNum = inData(numOfBoard, size, in);
203         for (Integer[][] num : inArrayNum) {
204             write(size, num, 0, 0);
205         }
206         outputFile(size, arrayNumList, outputPath);
207     }
208 }

 

SudokuUtil类是真正的计算数独的部分。方法调用关系如下。

 

  •  inputFile 方法是文件读取方法。
  •  inData 方法是将读取的字符串处理成为数组列表。
  •  write 方法就是核心算法DFS部分,通过递归回溯生成最终答案。
  •  check 方法则是用于检测该数字是否能在当前宫格内填入。
  •  inputFile 方法是文件输出方法。

 

程序测试

九宫格

 

 八宫格

 七宫格

 

 六宫格

 

 五宫格

 

 四宫格

 

 

 三宫格

程序性能分析与优化

 JProfiler分析结果

 

 

 

 

 

关于优化

 使用回溯过深的递归调用必然会使用大量的栈空间,这样的算法效率显然是不高的。

  根据某个师傅的博客,还是有不少方法可以优过回溯的。可惜的是这位师傅没有自己造线性规划轮子而是调用的库函数。

 

 

除此之外,暂时未找到其他更优解

心路历程与收获

   这个作业最大的感受就是有点杀鸡用牛刀的感觉。我非常能理解PSP表格中各个项目对应软件开发的各个流程。但我认为PSP更适合做需要长期维护的项目,而不是像这次作业的算法练习题。例如算法题不需要非常详细的设计文档,往往三言两语就能将核心思想交代清楚、算法题不需要反复的review,因为算法题往往目标是更高效的计算某个东西而不是更好的代码可读性(当然这也是非常重要的,但许多算法高手写出来的代码通常可读性较差)和更好的可维护性等等。

  回到这道题上,这道题的编码时间其实是非常短的。但是因为递归不太好调试的原因(这也是我不喜欢递归的原因之一),我一步步调试了很久才调出来一些细节问题。期间参考了【2】数独(Sudoku)游戏之我见 判断方法,比我的妙多了🤣,虽然最后没用。而后在做文件IO和核心整合的时候遇到了一个递归后无法传最终值的问题。直到参考了【1】数独~编程作业 这篇文章使用的方法,瞬间明白了,相当于深拷贝之后再传递出来,再递归栈中传递的对象都是传递地址但递归栈再最后弹出时传递的值会回到栈底即初始状态。

posted @ 2020-08-10 19:18  harsonyoung  阅读(96)  评论(0编辑  收藏  举报