数独是一种考验眼力和逻辑的小游戏,关键在这个“独”字上,横竖不能重复,方块不能重复。今天我给大家介绍一种利用“循环+递归+回溯”的办法来用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啦等等。