Java學習:解數獨的小程序

之前看了java,然後上個月迷上了九宮格數獨,玩了幾天,覺得實在有趣,就想着能不能用編程來解決,於是就自己寫了個,哈哈 還真解決了。


算法如下,需要預先給出幾個固定的值,目前解決的一個最難的數獨是大概26個已知值的情況,理論上應該能解決任意已知值的數獨,不過不知道會不會迭代棧溢出……因爲在26個已知值的情況下就迭代了3000多次了,囧,結果顯示如下:



這是已知值:

1 1 2
1 4 8 
1 5 5
1 6 1
1 7 7
1 8 3
2 1 1
2 2 6
2 4 4
3 5 9
3 7 8
3 8 4
4 7 9
5 8 7
6 1 3
6 6 8
6 7 4
6 8 6
7 3 7
7 6 4
7 7 1
8 3 3
8 6 7
9 3 4
9 4 6
9 7 3
9 8 2

(PS:如9 8 2表示第9行第二列的值是2)

將上面的數字保存到num.txt文件中,再把底下附的源代碼保存爲Sudoku.java。然後在cmd命令行模型下輸入:

javac Sudoku.java
java Sudoku <num.txt 

即可得到結果。

這個解法是我之前看到八皇后排列問題的解法後結合應用的,在數獨中採用了這種解法,不過應該算是比較暴力的拆解,所以我後面命名成violentBreak。。。

雖然只是一個很小的事,但是能嘗試着用編程去解決日常遇到的事,突然覺得很開心,學以致用!


java源代碼:

import java.lang.System.*;
import java.util.ArrayList;
import java.util.Scanner;


/**This class named Sudoku can auto calculate Sudoku but
*you should input some nums which are already known.
*For instance: 
*1 5 3
*2 4 7
*There two rows means [1][5]=3  [2][4]=7
*i.e. In row 1 col 5 is value:5
*you can write all known num into one txt file
*and input into this program .
*such as java Sudoku < num.txt
*/

/**代碼邏輯解析:
*  1、建立一個9X9矩陣保存數獨正確的值
   2、建立一個9X9列表,每個列表裏保存着該位置,數獨可以填入的可能值
      如ArrayList[1][1]={1,2,3}即1,1這個位置的數獨可能可以填入其中一個
      當矩陣該位置已保存了正確值時,清空對應位置的ArrayList
   3、當列表ArrayList裏只有一個可能值時,那個值就是數獨的正確值,將該值填入數獨矩陣
      並更新ArrayList

   PS:以上算法只能用於求困難級別的數獨,因爲我在玩遊戲的時候發現,每個時刻必定會有
       一個數獨空位,是隻能填入一個值的,所以這個算法才能運行
       當一個數獨(即已知位置的值變少時),可能會出現所有的空位都最起碼有兩個值時,
       需要改進算法,通過代入來判斷這個數獨是否成立

    4、於是後期我加入了暴力破解法,在上面的步驟執行後無法得出數獨,即使用暴力破解
*     
*/



public class Sudoku{
    //弄十的原因是爲了好記憶,0,0不用,只用1-9的list
    private ArrayList<Integer>[][] availableNum=new ArrayList[10][10];
    private int[][] correctNum=new int[10][10];
    private Scanner scan=new Scanner(System.in);
    private int countNum=0;
    

    {
        for(int i=0;i<10;i++){
            for(int j=0;j<10;j++){
                availableNum[i][j]=new ArrayList<>();
            }
        }

        for(int row=1;row<10;row++){
            for(int col=1;col<10;col++){
                for(int i=1;i<10;i++)
                    availableNum[row][col].add(new Integer(i));
            }
        }

        //先都初始化爲-1,即此時沒有填充數字
        for(int i=0;i<10;i++)
            for(int j=0;j<10;j++)
                correctNum[i][j]=-1;


    }

    public Sudoku(){}

    //在對應數獨位置插入正確值
    public void insert(int row,int col,int value){
        correctNum[row][col]=value;
        availableNum[row][col]=null;
        delete(row,col,value);

    }
    //每插入一個數值,就刪除相應的行列和小框框3X3數獨裏對應的ArrayList裏可能的該值
    public void delete(int row,int col,int value){
        //delte row
        for(int i=1;i<10;i++){
            if (availableNum[row][i]!=null)
                availableNum[row][i].remove(new Integer(value));
        }

        //delete column
        for(int i=1;i<10;i++){
            if (availableNum[i][col]!=null)
                availableNum[i][col].remove(new Integer(value));
        }

        //delete box num
        int[] itsCenter=judgeCenterPos(row,col);
        for(int temp1=itsCenter[0]-1;temp1<=itsCenter[0]+1;temp1++)
            for(int temp2=itsCenter[1]-1;temp2<=itsCenter[1]+1;temp2++)
                if(availableNum[temp1][temp2]!=null){
                    availableNum[temp1][temp2].remove(new Integer(value));
                }

    }
    //判斷插入的值時處於哪個小框框數獨裏
    public int[] judgeCenterPos(int row,int col){
        int[] itsCenter=new int[2];
        for(int centerRow=2;centerRow<9;centerRow+=3)
            for(int centerCol=2;centerCol<9;centerCol+=3){
                if( Math.abs(row-centerRow)<=1 && 
                    Math.abs(col-centerCol)<=1 ){
                    itsCenter[0]=centerRow;
                    itsCenter[1]=centerCol;
                    return itsCenter;
                }

            }
        System.out.println("Some unchecked error was happened");
        return itsCenter;

    }

    //判斷空格里所能填的數字是不是只能有一個,當返回-1時通過檢測報錯
    public int[] judgeIfOnlyOne(){

        for(int row=1;row<10;row++)
            for(int col=1;col<10;col++){
                if(availableNum[row][col]!=null)
                    if(availableNum[row][col].size()==1)
                        return new int[]{row,col};
            }

        return new int[]{-1,-1};

    }

    // 判斷爲唯一,但是空格里還有多於1個的數時,我們直接將哪個正確的值填入
    public void insertIfCan(){

        for(int row=1;row<=7;row+=3){
            for(int col=1;col<=7;col+=3){
                for(int z=1;z<10;z++){
                    int count=0;
                    Integer temp=new Integer(z);
                    int itemp=0,jtemp=0;
                    outer:
                    for(int i=row;i<row+3;i++){
                        for(int j=col;j<col+3;j++){
                            if(availableNum[i][j]!=null){
                                if(availableNum[i][j].contains(temp)){
                                    count++;
                                    itemp=i;
                                    jtemp=j;
                                    if (count>1)
                                        break outer;
                                }
                            }
                        }
                    }
                    if(count==1 && itemp!=0){
                        insert(itemp,jtemp,z);
                    }
                }
                
            }
        }
    }






    //判斷數獨的矩陣是否填滿,沒有則繼續
    public boolean judgeMatrixFull(){
        for(int i=1;i<10;i++)
            for(int j=1;j<10;j++)
                if(correctNum[i][j]==-1)
                    return false;
        return true;
    }

    //先輸入已知位置的數字
    public void inputNumKnown(){
        while(scan.hasNextInt()){
            int row=scan.nextInt();
            int col=scan.nextInt();
            int value=scan.nextInt();
            insert(row,col,value);
            delete(row,col,value);
        }
    }

    

    //打印數獨結果
    public void printSudoku(){
        printSudoku(correctNum);
        
    }

    public void printSudoku(int[][] arr){
        System.out.println("Sudoku result:");
        for(int i=1;i<10;i++){
            for(int j=1;j<10;j++)
                System.out.print(arr[i][j]+" ");
            System.out.println(" ");
        }
    }

    public void printList(){
        for(int i=1;i<10;i++)
            for(int j=1;j<10;j++){
                System.out.print(i+" "+j+":");
                if(availableNum[i][j]!=null)
                    for(int z=0;z<availableNum[i][j].size();z++){
                        System.out.print(availableNum[i][j].get(z)+" ");
                    }
                System.out.println(" ");
            }
    }




    //暴力破解
    public void violentBreak(){
        int i=1,j=1;
        outer:
        for(;i<10;i++)
            for(;j<10;j++)
                if(correctNum[i][j]!=-1)
                    break outer;

        violentInsert(i,j,correctNum[i][j],correctNum);
    }


    public void violentInsert(int row,int col,int value,int[][] arr){
        countNum++;
        int[][] tempMatrix=new int[10][10];

        for(int i=1;i<10;i++)
            for(int j=1;j<10;j++)
                tempMatrix[i][j]=arr[i][j];

        tempMatrix[row][col]=value;
        //不能insert的話說明填滿了
        int[] insertPos=canInsert(tempMatrix);
        if(insertPos[0]==-1){
            System.out.println("all insert is done.This is the last Sudoku:");
            printSudoku(tempMatrix);
            return;
        }


        for(int val=1;val<=10;val++){
            if(val==10){
                tempMatrix=null; //讓JVM回收垃圾
                //System.out.println("value=10 happened.");
                return;
            }
            if(judgeIfViolentInsert(insertPos[0],insertPos[1],val,tempMatrix)){
                //System.out.println("insert happened.");
                violentInsert(insertPos[0],insertPos[1],val,tempMatrix);
            }
        }

    }

    public int[] canInsert(int[][] tempMatrix){
        int[] pos={-1,-1};
        for(int i=1;i<10;i++)
            for(int j=1;j<10;j++){
                if(tempMatrix[i][j]==-1){
                    pos[0]=i;
                    pos[1]=j;
                    return pos;
                }
            }
        return pos;
    }


    public boolean judgeIfViolentInsert(int row,int col,int value,int[][] tempMatrix){
        for(int j=1;j<10;j++)
            if(value==tempMatrix[row][j])
                return false;

        for(int i=1;i<10;i++)
            if(value==tempMatrix[i][col])
                return false;


        int[] itsCenter=judgeCenterPos(row,col);
        for(int temp1=itsCenter[0]-1;temp1<=itsCenter[0]+1;temp1++)
            for(int temp2=itsCenter[1]-1;temp2<=itsCenter[1]+1;temp2++)
                if(value==tempMatrix[temp1][temp2])
                    return false;

        return true;
    }


    //數獨開始運算的函數
    public void start(){
        int[] nextInsert=new int[2];
        int count=0;
        this.inputNumKnown();

        while(!judgeMatrixFull()){
            nextInsert=judgeIfOnlyOne();
            if(nextInsert[0]==-1){
                this.insertIfCan();
                count++;
                if(count==15){
                    System.out.println("Cannot fullfill this sodoku through finding the only one left.");
                    System.out.println("count:"+count);
                    break;
                }
                continue;

            }
            int value=availableNum[nextInsert[0]][nextInsert[1]].get(0);
            insert(nextInsert[0],nextInsert[1],value);
        }

        printSudoku();
        //滿了就不用暴力破解了
        if(judgeMatrixFull())
            return;
        System.out.println("Now we should break this Sudoku by violent method.");
        violentBreak();
        System.out.println("Recursion times:"+countNum);
    }




    public static void main(String[] args){

        Sudoku test1=new Sudoku();
        test1.start();
        
        int[] a=new int[2];
        System.out.println(a);
        System.out.println(a[0]);

    }

}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章