一、GitHub连接

传送门

二、PSP表格

||||||||||||||
|:--|:--|:--|:--|
| PSP2.1| Personal Software Process Stages |预估耗时(分钟)|实际耗时(分钟)|
| Planning |计划|60|30|
| Estimate |估计这个任务需要多少时间|30|20|
| Development |开发|240|300|
|Analysis |需求分析 (包括学习新技术)|120|180|
| Design Spec |生成设计文档|60|30|
| Design Review |设计复审|30|20|
| Coding Standard |代码规范 (为目前的开发制定合适的规范)|30|30|
| Design |具体设计|60|60|
| Coding |具体编码|240|180|
| Code Review |代码复审|180|240|
| Test |测试(自我测试,修改代码,提交修改)|180|240|
| Reporting |报告|60|60|
| Test Repor |测试报告|20|20|
| Size Measurement |计算工作量|10|10|
| Postmortem & Process Improvement Plan |事后总结, 并提出过程改进计划|30|30|
| |合计|1370|1450|

三、思路描述

    一开始看到数独,并没有考虑很多,用着半生不熟的JAVA语言直接上手。因为题目要求是三到九宫格,就打算以不变应万变,从最麻烦的九宫格入手,其他的就在九宫格的基础上改改。结果一个宫格一个类,一共写了八个类!自己看着代码都头昏。但是自己思考一个快捷的算法又有点困难。于是我就想,数独算法应该比较成熟,网上或许有很多资料可以查。然鹅,数独的生成算法确实挺多,求解算法看起来很高端,模拟人类求解数独,采用区块摒除法、数组法、四角对角线、唯一矩形、全双值坟墓、单数链、异数链及其他数链的高级技巧等等。我百度完这些方法后,满脑子都是大写的懵逼。万分绝望下我拿起数据结构的书翻了翻,入眼就是“回溯法解决八皇后问题”,而里面的第一行介绍就是,可以利用回溯法解决数独问题。我仔细看完八皇后的求解,总觉得思路和一开始写的有一点点相似,都是设置一个A数组,初始化为-1,再一一检测后置0,但是到了结尾,文章提供了递归法检验数组求解是否正确的方法,我就寻思着,能不能把它改改,编写成求解数组的方法呢?于是就有了现在的程序。具体思路在代码说明部分。
    再写完程序后,我又找到一个关键词,舞蹈链,据说这是个更快捷的方法求解数独,但是苦于没有时间研究,只能作罢。

四、单元测试

1、测试读取文件方法

BufferedReader br = new BufferedReader(new FileReader("D:\\test.txt"));//构造一个BufferedReader类来读取文件
 String s = null;
 int[][] chess = new int[9][9];
 
 for(int k = 0; k < chess.length ;k++ )
 for(int j =0; j < chess[k].length; j ++)
 {
 chess[k][j] = 0;
 }
 
 int i = 0;
 while((s = br.readLine())!=null){//使用readLine方法,一次读一行
   String[] temp = s.split(" ");
   System.out.println(temp[2]);
   for(int j=0;j<temp.length;j++) {
   System.out.println(temp[j]);
   chess[i][j]=Integer.parseInt(temp[j]);
   
   }i++;
  
 }
 System.out.println(chess.toString());
 for(int k = 0; k < chess.length ;k++ ){
 for(int j =0; j < chess[k].length; j ++)
 {
 System.out.print(chess[k][j]+ " ");
 }
 System.out.println();
 }
 

2、测试递归方法solve()

@Test
public boolean solve(int[][] chess, int i, int j, int m) {  //同一行按列搜,若j=9,则跳到下一行搜,若宫格填完返回true

		if (j == m) {
			if (i == m - 1)
				return true;
			i++;
			j = 0;
		}

		if (chess[i][j] != 0) {
			return solve(chess, i, j + 1, m);
		}

		for (char k = 1; k <= m; k++) {
			if (isValid(chess, i, j, k, m)) {
				chess[i][j] = k;
				if (solve(chess, i, j + 1, m))
					return true;
				else
					chess[i][j] = 0;
			}
		}
		return false;
	}

3、测试写入文本方法printFuntxt()

@Test
private static void printFuntxt(int[][] totalchess, int n, int m, String fileout) {

    	File file=new File(fileout);
    	try (PrintWriter output = new PrintWriter(file)) {

    		for (int i = 0; i < m * n; i++) {

    			if(i % m == 0 && i != 0)
                	output.println();

                for (int j = 0; j < m; j++) {
                	if (totalchess[i][j] == 0) {
                		System.out.println("无解");
                		return;
                	}
                	if (j != m-1)
                		output.print(totalchess[i][j] + " ");
                	else if(i!= m * n - 1)
                		output.print(totalchess[i][j] + "\n");
                	else
                		output.print(totalchess[i][j]);
                }
    		}
		}
    		catch (FileNotFoundException e) {
			e.printStackTrace();
    		}
    	}

测试结果

·正面测试

正面测试

·边界测试

边界测试
边界测试

五、性能改进

性能分析
性能分析
性能分析
性能分析

虽然看不太懂性能分析图,但是还是可以看得出,开销最大的是读取文件的函数。第一个版本经过改进后,Classes的开支明显减少。

六、代码说明

主要思路流程如下:
流程图

其中最核心的就是递归和判断宫格是否可以填充。

程序使用solve()方法来进行递归。
先从第i行开始,检测第j个位置是否可以填k值,若不行,则j+1,按照j=0,1……m-1的次序。

·如果可以填,则将这个格子置为k,判断下一个格子。如果j=m-1,则跳到下一行继续判断。

·如果不可以填,则将这个位置置0。

·如果所有格子都填完了,则返回true。

public boolean solve(int[][] chess, int i, int j, int m) {  //同一行按列搜,若j=9,则跳到下一行搜,若宫格填完返回true

		if (j == m) {
			if (i == m - 1)
				return true;
			i++;
			j = 0;
		}

		if (chess[i][j] != 0) {
			return solve(chess, i, j + 1, m);
		}

		for (char k = 1; k <= m; k++) {
			if (isValid(chess, i, j, k, m)) {
				chess[i][j] = k;
				if (solve(chess, i, j + 1, m))
					return true;
				else
					chess[i][j] = 0;
			}
		}
		return false;
	}

接着用isvalid()方法判断这个位置是否可以填值。
在检测第j个位置是否可以填k值时,分别按行、列、某些阶数的宫图还需要按宫进行判断,若行数不为零且已经填了k值时,则返回false,k值加1,进行下一轮的判断。

public boolean isValid(int[][] chess, int i, int j, char c,int m) {  //有效空格

		for (int k = 0; k < m; k++) {

			if (chess[i][k] != 0 && chess[i][k] == c)   //按行搜
				return false;

			if (chess[k][j] != 0 && chess[k][j] == c)   //按列搜
				return false;

			if (m == 4) {   //按宫搜
				if (chess[i / 2 * 2 + k / 2][j / 2 * 2 + k % 2] != 0 
						&& chess[i / 2 * 2 + k / 2][j / 2 * 2 + k % 2] == c)
					return false;
			} else if(m == 6) {
				if (chess[i / 2 * 2 + k / 3][j / 3 * 3 + k % 3] != 0 
						&& chess[i / 2 * 2 + k / 3][j / 3 * 3 + k % 3] == c)
					return false;
			} else if(m == 8) {
				if (chess[i / 4 * 4 + k / 2][j / 2 * 2 + k % 2] != 0 
						&& chess[i / 4 * 4 + k / 2][j / 2 * 2 + k % 2] == c)
					return false;
			} else if(m == 9) {
				if (chess[i / 3 * 3 + k / 3][j / 3 * 3 + k % 3] != 0 
						&& chess[i / 3 * 3 + k / 3][j / 3 * 3 + k % 3] == c)
						return false;
			}
		}
		return true;
	}

关于文件的读取,是将文件按行读取,存入一个叫totalchess的二维数组中。

public static int[][] readFile(File file, int[][] totalchess) {     //把文件读入一个大数组
		  String s = null;
		  for (int k = 0; k < totalchess.length; k++) {
				 for (int j = 0; j < totalchess[k].length; j++) {
					 totalchess[k][j] = 0;
				 }
		  }
		  try {
			  int i = 0;
			  BufferedReader br = new BufferedReader(new FileReader(file)); //构造一个BufferedReader类来读取文件
			  while ((s = br.readLine()) != null) { //使用readLine方法,一次读一行
				  if (s.equals(""));
				  else {
					  String[] temp = s.split(" ");
					  for (int j = 0; j < temp.length; j++) {
						  totalchess[i][j] = Integer.parseInt(temp[j]);
				  	}
					  i++;
				  }
			  }
			 br.close();
		  } catch (Exception e) {
		   e.printStackTrace();
		  }
		  return totalchess;
  }

而在solveSudoku方法中,如果solve方法返回值为true,即宫格已经填写完毕,将填写完成的chess数组存入totalchess数组中一边最后一次性写入文本。

public void solveSudoku(int[][] chess, int m, int[][]totalchess) {

		if (chess == null || chess.length != m || chess[0].length != m) {
			System.out.println("error!");
			return;
		}

		if (solve(chess, 0, 0, m)) {   // 打印结果
			for (int i = 0; i < m; i++) {
				for (int j = 0; j < m; j++) {
					totalchess[y][j] = chess[i][j];
					//System.out .print(totalchess[y][j]);
				}
				//System.out .println();
				y++;
			}
		}
	}

最后是把totalchess数组写入文本。

 private static void printFuntxt(int[][] totalchess, int n, int m, String fileout) {

    	File file=new File(fileout);
    	try (PrintWriter output = new PrintWriter(file)) {

    		for (int i = 0; i < m * n; i++) {

    			if(i % m == 0 && i != 0)
                	output.println();

                for (int j = 0; j < m; j++) {
                	if (totalchess[i][j] == 0) {
                		System.out.println("无解");
                		return;
                	}
                	if (j != m-1)
                		output.print(totalchess[i][j] + " ");
                	else if(i!= m * n - 1)
                		output.print(totalchess[i][j] + "\n");
                	else
                		output.print(totalchess[i][j]);
                }
    		}
		}
    		catch (FileNotFoundException e) {
			e.printStackTrace();
    		}
    	}

这与第一个版本相比,最大的优点就是能将七个阶级的宫根据if语句在一个方法中进行实现,而不需要创建七个类,代码冗长繁琐。

七、异常处理

·命令行输入格式不正确

if (args.length != 8) {
			System.out.println("Invalid input.");
			System.exit(0);
		}

命令行输入格式不正确

·文本输入数组越界

 try {
			  int i = 0;
			  BufferedReader br = new BufferedReader(new FileReader(file)); //构造一个BufferedReader类来读取文件
			  while ((s = br.readLine()) != null) { //使用readLine方法,一次读一行
				  if (s.equals(""));
				  else {
					  String[] temp = s.split(" ");
					  for (int j = 0; j < temp.length; j++) {
						  totalchess[i][j] = Integer.parseInt(temp[j]);
				  	}
					  i++;
				  }
			  }
			 br.close();
		  } catch (Exception e) {
		   e.printStackTrace();
		  }

文本输入数组越界

·输入文本为空

if ((filein == null) || !filein.exists() || filein.length() == 0) {
			System.out.println("输入文件不存在");
			System.exit(1);
		}

边界测试

八、心得体会

   这次作业让我学到了很多,首先是文本的读写与命令行的使用,在这块上我耗了很长的时间,其次是一些插件的使用,比如jprofile、checkstyle和juint,关于单元检测里juint的使用,还不是很熟练,但相信以后会慢慢熟悉的。在单元检测这块内容时候,也懵了很久,自己的方法都是相互调用的,怎么进行检测?在仔细看完构建之法后,终于知道单元检测就是把模块提取出来,可以自己加上输入输出语句,进行检测,然后在保证某些模块接口稳定的情况下,借助它对其他接口模块进行单元测试。总之就是,累与学无止境。
posted on 2019-09-25 16:20  昵称被使用啦  阅读(195)  评论(4编辑  收藏  举报