利用程序隨機構造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 20String 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
目前來說,我覺得我的專業知識與技能水平是比較差的,有些專業基礎課之前沒有學過,這些要利用課外的時間盡快補上。以下幾項技能我覺得是比較重要的,希望課程結束之後可以在以下幾個方面有較大的提升。
利用程序隨機構造N個已解答的數獨棋盤