數獨的生成整體思路是挖洞法。
首先在二維數組第一行隨機填充1-9 9個數字,而後將這9個數字隨機分佈到整個二維數組中,而後使用求解數獨的算法對此時的數組進行求解,獲得一個完整的數獨,而後按照用戶輸入的提示數量進行隨機挖洞,獲得最終的數獨題目。
這種方法理論上能夠隨機生成(81!/72! = 9.5e+16)種不一樣的數獨題目,足夠人類玩上幾百年了。java
求解數獨使用的是計算機最擅長的暴力搜索中的回溯法。並結合人求解數獨的思惟過程增長了一點改進。
在每一層搜索中,首先計算每一個格子能夠填充的值的個數(我命名爲不肯定度),若是有格子不肯定度爲1,則直接填上數字就好,不然對不肯定度最小的格子使用可能的數字逐個填充,並進入下一次遞歸。若是發現不肯定度爲0的格子,作說明以前的過程有問題,須要進行回溯。git
package sudo; import java.util.Scanner; /** * @description 數獨生成和求解 * @limit 支持從1-80的數字提示數量 * @method 深度優先搜索/回溯法 * @author chnmagnus */ public class Sudo { private int[][] data = new int[9][9]; //muti_array private int lef; //the number of zero in array private int tip; //the number of nozero_digit in array /** * 構造函數 * 初始化變量 */ public Sudo(){ lef = 0; for(int i=0;i<9;++i){ for(int j=0;j<9;++j){ data[i][j] = 0; } } } /** * 生成數獨 * 方法:挖洞法 */ public void genSudo(){ System.out.println("Please input the number of digits provided:"); Scanner scan = new Scanner(System.in); tip = scan.nextInt(); scan.close(); /*將1-9 9個數字放在二維數組中隨機位置*/ lef = 81 - 9; for(int i=0;i<9;++i){ data[0][i] = i+1; } for(int i=0;i<9;++i){ int ta = (int)(Math.random()*10)%9; int tb = (int)(Math.random()*10)%9; int tem = data[0][ta]; data[0][ta] = data[0][tb]; data[0][tb] = tem; } for(int i=0;i<9;++i){ int ta = (int)(Math.random()*10)%9; int tb = (int)(Math.random()*10)%9; int tem = data[0][i]; data[0][i] = data[ta][tb]; data[ta][tb] = tem; } /*經過9個數字求出一個可行解*/ solveSudo(); lef = 81 - tip; for(int i=0;i<lef;++i){ int ta = (int)(Math.random()*10)%9; int tb = (int)(Math.random()*10)%9; if(data[ta][tb]!=0) data[ta][tb] = 0; else i--; } } /** * 求解數獨 * @return 是否有解的boolean標識 */ public boolean solveSudo(){ if(dfs()){ System.out.println("Solve completed."); return true; }else{ System.out.println("Error:There are no solution."); return false; } } /** * 輸出數獨數組 */ public void printSudo(){ System.out.println("-----------------"); for(int i=0;i<9;++i){ for(int j=0;j<9;++j){ if(data[i][j]>0) System.out.print(data[i][j]+" "); else System.out.print("* "); } System.out.print('\n'); } System.out.println("-----------------"); } /** * 計算某格子的可填數字個數,即不肯定度 * @param r * @param c * @param mark * @return 不肯定度 */ private int calcount(int r,int c,int[] mark){ for(int ti=0;ti<10;++ti) mark[ti] = 0; for(int i=0;i<9;++i){ mark[data[i][c]] = 1; mark[data[r][i]] = 1; } int rs = (r/3)*3; int cs = (c/3)*3; for(int i=0;i<3;++i){ for(int j=0;j<3;++j){ mark[data[rs+i][cs+j]] = 1; } } int count = 0; for(int i=1;i<=9;++i){ if(mark[i]==0) count++; } return count; } /** * 供solve調用的深度優先搜索 * @return 是否有解的boolean標識 */ private boolean dfs(){ if(lef==0) return true; int mincount = 10; int mini = 0,minj = 0; int[] mark = new int[10]; /*找到不肯定度最小的格子*/ for(int i=0;i<9;++i){ for(int j=0;j<9;++j){ if(data[i][j]!=0) continue; int count = calcount(i,j,mark); if(count==0) return false; if(count<mincount){ mincount = count; mini = i; minj = j; } } } /*優先處理不肯定度最小的格子*/ calcount(mini,minj,mark); for(int i=1;i<=9;++i){ if(mark[i]==0){ data[mini][minj] = i; lef--; dfs(); if(lef==0) return true; data[mini][minj] = 0;//回溯法 lef++; } } return true; } /** * main函數 * @param args */ public static void main(String[] args) { Sudo su = new Sudo(); su.genSudo(); su.printSudo(); su.solveSudo(); su.printSudo(); } }
如下四幅圖分別是輸出爲0,20,60的程序運行結果。算法