回溯法簡單應用--解數獨

簡單介紹

數獨是當下較爲流行的數學遊戲之一。通常數獨由9x9的格子構成,其中將9x9的格子分爲9個3x3的區域,稱爲“宮”(通常宮與宮之間會用較粗的線來分隔)。遊戲的目標則是在格子中填滿1~9的數字,並且使得數字之間滿足一定的約束條件。

對於標準數獨,其約數條件如下:

  • 在9x9的格子下,每行,每列不得有重複的數字
  • 每個宮內不得有重複的數字

除了標準數獨,還有一系列的升級版本,例如連續數獨,對角線數獨,窗口數獨,殺手數獨等等。這裏暫時只討論標準數獨的回溯解法。

對於同一個數獨題目,存在着有多種解,也可能只有一種解。已知信息和解的數量通常也可以來衡量一個數獨題目的難易程度,通常容易的數獨給出的已知信息很多,又或者可能擁有很多個解。困難的數獨則是給出的已知信息少,而最終解可能只有一個。

下面是關於最簡單的破解數獨的算法之一 —— 回溯法。回溯法採用深度優先搜索的思想,是常用的一種算法。關於回溯法的經典例子也很多,例如找生成樹,0-1揹包問題,8皇后問題,走迷宮,全排列問題等等。本文要說的解數獨也可以採用回溯法。

本文采用的解數獨思想就是最簡單的一個個格子去試探,直到試探到把格子填滿,既可以檢查答案,如果符合則輸出。根據這個思想和回溯法的一般步驟,可以得出解數獨算法的步驟,此外,這裏採用遞歸的形式。

回溯步驟一 問題的解空間

對於一個數獨,每個格子都去試探實際上是一種暴力破解的思想,對於9x9的格子,每個格子可填1~9,如果不做任何限制,問題的解空間樹將會是一棵九叉樹,這樣的搜索空間大小是非常可怕的。

回溯步驟二 確定搜索規則

在討論解空間後,顯然應該意識到,必須確定解空間樹的拓展條件來抑制解空間樹的指數增長。對於數獨來說,搜索規則正好就是數獨填數字的規則。對於一個待填格子,選是1~9,在1~9之中,同行同列同宮都出現過的數字就不必讓其子節點生成了,從該節點開始,不符合數獨規則的搜索結果最終必然不是數獨的解。

回溯步驟三 剪枝

搜索規則實質上就是一個剪枝函數,通過剪枝函數提前剔除不可能的解,能夠有效地控制解空間樹的增長,並且隨着搜索的推進,解空間樹的增長會越來越緩慢。想象一下當你快要將數獨填滿的時候,剩下的格子的選擇必然是越來越少,甚至能夠直接確定某格子只有一個可行的數字。

僞代碼描述

backTracking(int index){
    if(index == 81){
        格子已經填滿,輸出結果
    }
    //x,y表示格子的座標
    x <-- index % 9;
    y <-- index / 9;
    if(grid(x, y)的數字爲題目的信息){
        backTracking(index+1);  //題目給出的數字是確定的,沒必要搜索
    }
    for(i=1; i<=9 ;i++){   
        if(isLegal(x, y, i)){ //逐個數字試探其合法性
            grid(x, y) = i;
            backTracking(index+1);
            grid(x, y) = 0;
        }
    }
}

Java代碼

import java.util.Scanner;

public class Sudoku {

    private static int WIDTH = 3;

    private static int arr[][] = new int[WIDTH * WIDTH][WIDTH * WIDTH];
    private static boolean q[][] = new boolean[WIDTH * WIDTH][WIDTH * WIDTH];

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int val;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                val = scanner.nextInt();
                if (val != 0)
                    generateQuestion(i, j, val);
            }
        }
        for (int i = 1; i < WIDTH * WIDTH; i++) {
            fill(0, i);
        }
    }

    private static void fill(int index, int val) {
        int mWidth = WIDTH * WIDTH;
        if (index == mWidth * mWidth) {
            if (!nullCheck()) {
                printM();
//                System.exit(0);
            }
            return;
        }
        int x = index % (mWidth);
        int y = index / (mWidth);

        if (q[y][x]) {
            fill(index + 1, val);
        } else {
            if (isLegalPos(x, y, val)) {
                arr[y][x] = val;
                if (index + 1 == mWidth * mWidth) {
                    fill(index + 1, 1);
                } else
                    for (int i = 1; i <= mWidth; i++) {
                        fill(index + 1, i);
                    }
                arr[y][x] = 0;
            }
        }
    }

    private static boolean nullCheck() {
        for (int j = 0; j < WIDTH; j++)
            for (int i = 0; i < WIDTH; i++) {
                if (arr[j][i] == 0)
                    return true;
            }
        return false;
    }


    private static boolean isLegalPos(int x, int y, int val) {
        for (int i = 0; i < WIDTH * WIDTH; i++) {
            if (x != i && arr[y][i] == val)
                return false;
            if (y != i && arr[i][x] == val)
                return false;
        }

        int offsetX = (x / WIDTH) * WIDTH;
        int offsetY = (y / WIDTH) * WIDTH;
        for (int i = offsetX; i < offsetX + WIDTH; i++)
            for (int j = offsetY; j < offsetY + WIDTH; j++) {
                if (!(y == j && x == i) && arr[j][i] == val)
                    return false;
            }
        return true;
    }

    private static void printM() {
        int k = WIDTH * WIDTH;
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k - 1; j++) {
                System.out.printf("%d ", arr[i][j]);
            }
            System.out.println(arr[i][k-1]);
        }
    }

    private static void generateQuestion(int y, int x, int val) {
        arr[y][x] = val;
        q[y][x] = true;
    }
}

樣例測試(0表示待填)

輸入1
8 0 0 0 0 0 0 0 0
0 0 3 6 0 0 0 0 0
0 7 0 0 9 0 2 0 0
0 5 0 0 0 7 0 0 0
0 0 0 0 4 5 7 0 0
0 0 0 1 0 0 0 3 0
0 0 1 0 0 0 0 6 8
0 0 8 5 0 0 0 1 0
0 9 0 0 0 0 4 0 0

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

輸入2
0 0 3 0 1 9 0 0 0
9 0 0 8 0 0 0 0 0
0 1 0 6 2 0 0 0 9
0 8 0 0 0 0 0 4 1
5 2 6 0 0 0 9 8 7
4 9 0 0 0 0 0 6 0
6 0 0 0 8 3 0 9 0
0 0 0 0 0 2 0 0 3
0 0 0 4 6 0 8 0 0

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

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