利用程序随机构造N个已解答的数独棋盘
高级软件工程第二次作业:利用程序随机构造N个已解答的数独棋盘,代码如下:
1 package SudokuGame; 2 /** 3 * 解决这个问题使用的是回溯+剪枝的算法 4 * 基本思想:不断地将每个格子可填入的数字减少,如果当前格子没有数字可填入,就回溯到上一格 5 * 实现算法:首先初始化数独,将数独的第一行以随机数字填入,从第二行开始的每个格子都从1开始找可以填入的最小的数字, 6 在没有数字可填时,就开始回溯。 此算法在回溯的同时将不可能的结果排除,不会陷入死循环 7 */ 8 9 import java.io.File; 10 import java.io.FileWriter; 11 import java.io.IOException; 12 import java.util.Scanner; 13 14 public class sudokuV2 { 15 16 public static void main(String[] args) { 17 sudokuV2 sudoku = new sudokuV2(); 18 int[][] square = new int[9][9]; //存放数独的二维数组 19 20 String pathName = "sudotiku.txt"; 21 File file = new File(pathName); 22 if(file.exists()){ 23 file.delete(); //此句话在每次执行程序时先删除已经存在的文件sudotiku.txt 24 } 25 //开始生成数独 26 sudoku.go(square); 27 28 } 29 30 /** 31 * 将square中生成的数独写到文件sudotiku.txt中 32 * @param square 33 */ 34 public void writerToFile(int [][] square) { 35 String pathName = "sudotiku.txt"; 36 File file = new File(pathName); 37 38 try { 39 if(!file.exists()) {//若文件不存在就创建一个新文件 40 file.createNewFile(); 41 } 42 FileWriter fWriter = new FileWriter(file, true); 43 44 //此处参数如果直接是整形,则会被当成ASCII码,最后输出时转换成ASCII码对应的字符 45 //所以此处将int型转换成String输出 46 for(int i = 0; i < 9; i++) { 47 for(int j = 0; j < 9; j++) { 48 fWriter.write(String.valueOf(square[i][j]) + "\t"); 49 } 50 fWriter.write("\r\n"); 51 } 52 fWriter.write("\r\n"); 53 fWriter.close(); 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } 57 } 58 59 /** 60 * 接收用户输入的数字,若不符合要求则重新输入 61 * @return 返回用户输入的数字 62 */ 63 public int getNumber() { 64 int n; //用来接收用户想要生成的数独解的个数 65 do{ 66 System.out.print("请输入要生成的数独的个数:(1~1000000之间)"); 67 Scanner input = new Scanner(System.in); 68 n = input.nextInt(); 69 if(n <= 0 || n >1000000) { 70 System.out.println("输入错误,请重新输入!"); 71 } 72 }while(n <= 0 || n >1000000); 73 return n; 74 } 75 76 /** 77 * 初始化九宫格,全部元素置0 78 * 并将数独的第一行填入随机数字 79 */ 80 public void init(int[][] square) { 81 /** 将大九宫格数字全部置0 */ 82 for (int i = 0; i < 9; i++) { 83 for (int j = 0; j < 9; j++) { 84 square[i][j] = 0; 85 } 86 } 87 /** 将第一行填入随机数字 */ 88 for(int i = 0,j =0; j < 9; j++) { 89 square[i][j] = findValueInFirst(square, i, j); 90 } 91 } 92 93 /** 94 * 找到数独第一行可以填入的数字,并返回该数字 95 * @param square 96 * @param row 97 * @param col 98 * @return value 返回找到的合适的数字 99 */ 100 public int findValueInFirst(int[][] square, int row, int col) { 101 int value = 0; 102 103 do{ 104 value = (int)(Math.random() * 100) % 9 + 1 ; 105 if(rowCheck(square, row, col, value) == true) { 106 //如果value的值与第一行已有的值不重复,则返回value,将其存入square数组中 107 return value; 108 } 109 }while(true); 110 } 111 112 /** 113 * 生成数独,从第二行开始,每个格子的可填入数字都从1开始试探,不符合则增加1 114 * @param square 115 */ 116 public void go(int[][] square) { 117 int n; 118 n = getNumber(); //用来接收用户想要生成的数独的个数 119 for(;n >= 1;n--) { //控制生成数独的个数 120 init(square); 121 /* 开始生成数独**/ 122 int number ; 123 for (int i = 1; i < 9; i++) { 124 for (int j = 0; j < 9;) { 125 number = 0; 126 number = findValue(square, i, j); 127 if(number > 0) { 128 square[i][j] = number; 129 j++; 130 }else { 131 //没有找到可填入的值,开始回溯 132 if(j == 0) {//如果当前格子在第一列,则回溯到上一行的最后一格,此时需要将当前格子的数字置为0 133 square[i][j] = 0; 134 i--; 135 j = 8; 136 }else { //如果当前格子不在第一列,则回溯到同行的前一格 137 square[i][j] = 0; 138 j--; 139 } 140 } 141 } 142 } 143 writerToFile(square); 144 show(square); 145 System.out.println(); 146 } 147 } 148 149 /** 150 * 寻找可以填入格子的数字 151 * @param square 152 * @param row 153 * @param col 154 * @return 155 */ 156 public int findValue(int[][] square, int row, int col) { 157 158 int temp = square[row][col]; 159 if(temp == 0) { 160 //如果square当前的格子值为0,则说明在一个新的格子里,从1开始寻找可以填入的值 161 temp = 1; 162 }else { 163 //如果square当前的格子的不为0,则说明进入了回溯程序,将temp+1 164 temp++; 165 } 166 167 boolean isChange = false; //记录temp是否有改变,用来判断循环是否继续 168 169 do { 170 /* tempValue用来记录进入循环时temp的值,以方便查看temp的值是否有变化 **/ 171 int tempValue = temp; 172 if(rowCheck(square, row, col, temp) == false) { 173 //行内检查不唯一则temp在原来基础上+1 174 temp++; 175 isChange = true; 176 } 177 if(colCheck(square, row, col, temp) == false) { 178 //列内检查不唯一则temp在原来基础上+1 179 temp++; 180 isChange = true; 181 } 182 if(smallCheck(square, row, col, temp) == false) { 183 //小九宫格内检查不唯一则temp在上一步基础上+1 184 temp++; 185 isChange = true; 186 } 187 if(temp >= 10) { 188 //此时1~9均不能填入格子,需要进入回溯 189 return -1; 190 } 191 if(temp == tempValue) { 192 //全部检查完成后,若没有改变,则表示找到可以填入的值,返回temp 193 return temp; 194 } 195 }while(isChange == true); //isChange变成true,说明temp改变了,此时没有找到可以填入格子的值 196 return -1; 197 } 198 199 /** 200 * 输出数独 201 * @param square 202 */ 203 public void show(int[][] square) { 204 for (int i = 0; i < 9; i++) { 205 for (int j = 0; j < 9; j++) { 206 System.out.print(square[i][j] + "\t"); 207 } 208 System.out.println(); 209 } 210 } 211 212 213 /** 214 * 行检查:判断将要填入的数字是否与该行的元素有重复 215 * @param square 二维数组组成的大九宫格 216 * @param row 当前格子的行坐标 217 * @param col 当前格子的列坐标 218 * @param value 当前准备填入格子的数字 219 * @return 若value与该行已有的元素重复则返回false,不重复返回true 220 */ 221 public boolean rowCheck(int[][] square, int row, int col, int value) { 222 for(int j = 0; j < col ;j++ ) {//判断新填入的元素是否与该行的元素有重复 223 if(value == square[row][j]) { 224 return false; 225 } 226 } 227 return true; 228 } 229 230 /** 231 * 列检查:判断将要填入的数字是否与该列的元素有重复 232 * @param square 二维数组组成的大九宫格 233 * @param row 当前格子的行坐标 234 * @param col 当前格子的列坐标 235 * @param value 当前准备填入格子的数字 236 * @return 若value与该列已有的元素重复则返回false,不重复返回true 237 */ 238 public boolean colCheck(int[][] square, int row,int col,int value) { 239 for(int i = 0; i < row ;i++ ) {//判断新填入的元素是否与该列的元素有重复 240 if( value == square[i][col] ) { 241 return false; 242 } 243 } 244 return true; 245 } 246 247 /** 248 * 小九宫格检查:判断新填入的元素是否与其所在的小九宫格的元素有重复 249 * 想法:根据该格子所在的行和列判断所属的小九宫格的位置,然后分成九个小块分别判断 250 分别有九组 (0,0),(0,1),(0,2) 251 (1,0),(1,1),(1,2) 252 (2,0),(2,1),(2,2) 253 * @param square 二维数组组成的大九宫格 254 * @param row 当前格子的行坐标 255 * @param col 当前格子的列坐标 256 * @param value 当前准备填入格子的数字 257 * @return 若value与该小九宫格已有的元素重复则返回false,不重复返回true 258 */ 259 public boolean smallCheck(int[][] square, int row,int col,int value) { 260 261 int x = row / 3; //(x,y)的组合表示小九宫格的位置 262 int y = col / 3; 263 264 for(int i = 3 * x; i < 3 * x + 3 ;i++ ) { 265 for (int j = 3 * y; j < 3 * y + 3; j++) { 266 if(i == row && j == col) { 267 continue; 268 } 269 if(value == square[i][j]) { 270 return false; 271 } 272 } 273 } 274 return true; 275 } 276 }
以上为程序运行的全部代码。生成数独后将其写入文件“sudotiku.txt”中,也会在屏幕上显示。以下是运行结果:
请输入要生成的数独的个数:(1~1000000之间)3
9 3 5 7 1 8 2 4 6
1 2 4 3 5 6 7 8 9
6 7 8 2 4 9 1 3 5
2 1 3 4 6 5 8 9 7
4 5 6 8 9 7 3 1 2
7 8 9 1 2 3 5 6 4
3 4 1 6 7 2 9 5 8
5 6 2 9 8 1 4 7 3
8 9 7 5 3 4 6 2 1
4 5 1 9 7 8 2 3 6
2 3 6 1 4 5 7 8 9
7 8 9 2 3 6 1 4 5
1 2 3 4 5 7 6 9 8
5 4 7 6 8 9 3 1 2
6 9 8 3 1 2 4 5 7
3 6 2 5 9 1 8 7 4
8 1 5 7 2 4 9 6 3
9 7 4 8 6 3 5 2 1
4 8 1 5 2 7 6 9 3
2 3 5 1 6 9 4 7 8
6 7 9 3 4 8 1 2 5
1 2 3 4 5 6 7 8 9
5 4 7 8 9 1 2 3 6
8 9 6 2 7 3 5 1 4
3 1 2 6 8 4 9 5 7
7 5 4 9 3 2 8 6 1
9 6 8 7 1 5 3 4 2
程序运行的正确性以及性能分析:程序可以正确地运行出结果,计算1000个数独完全解大概用时926ms,理论上来说此程序可以得出9!个不同结果。另外,代码已经上传到coding,请老师指点。
学习过程及遇到的问题:
1、在做这次作业时,最开始我想到的方案是:用一个二维数组,每次填入格子中的值都是1~9中的随机数value,如果该数不符合数独要求,则value循环+1再填入,1~9都不能填入则回溯到上一格。此方法遇到了一个死循环,因为每次回溯到上一格后,该格子总是可以填入一个值,再开始下一格,因此导致死循环。
解决方法:在网上看到了目前这个算法,此算法每次都会排除不合适的数字(包括当前格不能填入的数字和填入此数字后导致后面的格子不能正确填入的数字),这样可以控制不陷入死循环。(PS:学习到别人的算法后,有试着再在每个格子填入随机数的基础上改进,但无可避免的落入死循环,这点不知道老师能否给出指点。)
2、本次作业从自己开始编程、遇到死循环再到寻找解决方案完成作业大概用了三到四天时间,通过本次作业锻炼了自己的编程能力,同时也看到了自己在算法设计上的不足,学习了别人的算法后才发现原来可以用这样的方式解决问题,还是受益匪浅。
关于课外任务:
问题:在你一生中身体最健康,精力最旺盛的时候,能在大学学习和研究,是一生中少有的机会。请说明一下,你已经具备的专业知识、技能、能力有哪些?离成为一个合格的 IT专业毕业生,在专业知识、技能、能力上还差距哪些?请看这个技能调查表, 从表中抽取 5 - 7 项你认为对你特别重要的技能, 记下你目前的水平, 和你想在课程结束后达到的水平 (必须至少列出 5 项)。链接: http://www.cnblogs.com/xinz/p/3852177.html
目前来说,我觉得我的专业知识与技能水平是比较差的,有些专业基础课之前没有学过,这些要利用课外的时间尽快补上。以下几项技能我觉得是比较重要的,希望课程结束之后可以在以下几个方面有较大的提升。