數獨是一種考驗眼力和邏輯的小游戲,關鍵在這個“獨”字上,橫豎不能重復,方塊不能重復。今天我給大家介紹一種利用“循環+遞歸+回溯”的辦法來用Java程序替我們完成數獨。
先給代碼隨后講解:
1 import java.util.HashMap;
2 import java.util.Map; 3 4 public class T2 { 5 public static final int N=3; 6 public static void main(String[] args) { 7 int x[][]={ 8 {0,2,0,0,0,9,0,1,0,0}, 9 {5,0,6,0,0,0,3,0,9,0}, 10 {0,8,0,5,0,2,0,6,0,0}, 11 {0,0,5,0,7,0,1,0,0,0}, 12 {0,0,0,2,0,8,0,0,0,0}, 13 {0,0,4,0,1,0,8,0,0,0}, 14 {0,5,0,8,0,7,0,3,0,0}, 15 {7,0,2,3,0,0,4,0,5,0}, 16 {0,4,0,0,0,0,0,7,0,0}, 17 }; 18 19 function(x,0,0); 20 21 } 22 23 private static void function(int[][] x, int r, int c) { 24 if (r>=x.length) { 25 show(x); 26 return; 27 } 28 if (c==0&&(r==x.length/N||r==x.length/N*2||r==x.length)) { 29 if (!checkedbox(x,r)) { 30 return; 31 }; 32 33 } 34 if (c>=x.length) { 35 function(x, r+1, 0); 36 return; 37 } 38 39 if (x[r][c]==0) { 40 for (int i = 1; i <= x.length; i++) { 41 if (checked(x,r,c,i)) { 42 x[r][c]=i; 43 function(x, r, c+1); 44 x[r][c]=0; 45 } 46 } 47 }else{ 48 function(x, r, c+1); 49 } 50 } 51 private static boolean checkedbox(int[][] x, int r) { 52 for (int k = 0; k < x.length; k+=x.length/N) { 53 Map<Integer, Integer> map=new HashMap<>(); 54 for (int i = r-N; i < r; i++) { 55 for (int j = k; j < k+x.length/N; j++) { 56 if (map.containsKey(x[i][j])) { 57 return false; 58 } 59 map.put(x[i][j], 1); 60 } 61 } 62 63 } 64 return true; 65 } 66 67 private static boolean checked(int[][] x, int r, int c, int i) { 68 for (int j = 0; j < x.length; j++) { 69 if (x[j][c]==i) { 70 return false; 71 } 72 if (x[r][j]==i) { 73 return false; 74 } 75 } 76 return true; 77 } 78 79 private static void show(int[][] x) { 80 for (int i = 0; i < x.length; i++) { 81 for (int j = 0; j < x.length; j++) { 82 System.out.print(x[i][j]+" "); 83 } 84 System.out.println(); 85 } 86 System.out.println(); 87 } 88 89 }
類里有五個函數,一是主函數不多說;二是遞歸的主體函數function,是解決數獨的關鍵,體現循環+遞歸+回溯的主要邏輯;三和四都是是一個辨識函數,一些較為復雜的判斷邏輯把它抽出來寫成辨識函數可以增加代碼的可讀性;五是一個打印函數很簡單。
function函數的實際意義是填寫x[r][c]的數字,更確切的說是填寫x數組的(r,c)坐標以后的所有數字,內部基本的結構是4個並列的if,第一個if是說如果我填的行號超過了最大值就打印整個數組;第二個if是說當我在填寫第3、6、9行開頭數字的時候要檢查一下上邊的三行的方塊里是不是有重復的數字;第三個if是說我填寫到最后一個元素的時候,要轉到下一行開頭去;第四個if是說當前的坐標是0的時候才開始循環填寫,否則跳過去填寫下一個數字。
checkedbox函數利用了一個很巧妙的查重思想----循環嵌套map集合的先查后裝思想。當然這個思想你肯定沒聽說過,因為這個名字是我起的。注意我在第59行給map裝值的時候是把數組的值裝在map的鍵里,這樣是為了第56行使用map集合的containsKey函數。當然我這樣給方塊查重是有點秀操作了,其實用一個長度為9的數組也可以。
checked函數和show函數都非常簡單,就不多提了。
我給出的數獨例子是一個只有一個結果的數獨,是比較難的,有興趣的話可以去搜一搜別的數獨來測試一下這個代碼吧!!
這個方法可以把一個數獨的所有解法全部列舉出來
拓展:
這個循環+遞歸+回溯的模式可以解決所有按照規則填寫數字的問題,比如說:九宮格填寫1-9橫豎斜相加相等啦、十六宮格填寫1-16啦等等。