[Java] 數獨生成和求解

思路

1.生成數獨

數獨的生成整體思路是挖洞法。
首先在二維數組第一行隨機填充1-9 9個數字,而後將這9個數字隨機分佈到整個二維數組中,而後使用求解數獨的算法對此時的數組進行求解,獲得一個完整的數獨,而後按照用戶輸入的提示數量進行隨機挖洞,獲得最終的數獨題目。
這種方法理論上能夠隨機生成(81!/72! = 9.5e+16)種不一樣的數獨題目,足夠人類玩上幾百年了。java

2.求解數獨

求解數獨使用的是計算機最擅長的暴力搜索中的回溯法。並結合人求解數獨的思惟過程增長了一點改進。
在每一層搜索中,首先計算每一個格子能夠填充的值的個數(我命名爲不肯定度),若是有格子不肯定度爲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的程序運行結果。算法

clipboard.png

clipboard.png

clipboard.png

相關文章
相關標籤/搜索