1. 程式人生 > 其它 >遞迴之八皇后問題(回溯演算法)

遞迴之八皇后問題(回溯演算法)

技術標籤:演算法與資料結構java演算法資料結構遞迴演算法面試

一、問題描述

八皇后問題, 是一個古老而著名的問題, 是回溯演算法的典型案例。 該問題是國際西洋棋棋手馬克斯· 貝瑟爾於 1848 年提出,問題的具體描述為:在 8×8 格的國際象棋的棋盤上擺放八個皇后,使其不能互相攻擊(即任意兩個皇后都不能處於同一行、同一列或同一斜線上),問共有多少種擺法 ?

在這裡插入圖片描述

八皇后問題的答案是 92 種解法,那麼這 92 種解法是怎麼得來的呢?

二、解決思路

這個問題的解決思路可以分為以下幾個步驟:

  1. 第一個皇后先放第一行第一列;
  2. 第二個皇后放在第二行第一列、然後判斷是否 OK,如果不 OK,則嘗試放在第二列、第三列…,直到找到一個合適的位置;
  3. 繼續放置第三個皇后,還是第一列、第二列…,直到找到一個合適的位置;
  4. 依次迴圈下去,直到第 8 個皇后也能放在一個不衝突的位置,就算是找到了一個正確解;
  5. 當得到一個正確解時,在棧回退到上一個棧時,就會開始回溯。本次回溯之後,會即將第一個皇后放到第一列前提下的所有正確解全部得到;
  6. 然後回頭從 1 開始,第一個皇后放到第一行第二列,後面繼續迴圈執行 2、3、4 的步驟,直至第一個皇后將第一行的所有列都放置過。至此,便得到了八皇后問題的所有放置解法。

上面的思路其實是有個回溯的思想在裡面的,這個回溯的思想體現在第 5 步。即當第 8 個皇后正確放置之後便開始回溯,回溯的步驟是:首先把第 8 個皇后消去,第 7 個皇后更改自己當前所在位置,重新找到一個合適的位置,由此第 8 個皇后便可以找到一個新的位置,這便誕生出了一種新的解法;接著把第 8 個和第 7 個皇后消去,第 6 個皇后改變到一個合適的位置,再重新擺放第 7 個皇后和第 8 個皇后的位置,由此又可以誕生幾種新的解法。如此迴圈下去,直到第 2 個皇后遍歷完所有的合適的位置,這樣求得的結果就是第 1 個皇后在第一行第一列情況下的所有解法

。這個過程就是一個回溯過程。

上面的回溯思想可以求得第 1 個皇后在 第一行第一列情況下的所有解法。事實上,第 1 個皇后也可以擺放在第一行的任意一列,在這八列情況下的所有解法的累計,就是八皇后問題最終的解。

三、程式碼實現

理論上應該建立一個二維陣列來表示棋盤,但是實際上可以通過演算法, 用一個一維陣列即可表示棋盤。如:

int[8] arr[] = {0, 4, 7, 5, 2, 6, 1, 3} 

對於這個一維陣列 arr,它的下標表示第幾行,也就相當於第幾個皇后;下標對應的值則代表這個皇后所在的列。如 a[1] = 4,代表第 2 個皇后在第二行第五列。

八皇后問題完整的程式碼實現如下:

public class No3_Recursion_EightQueens {

    private static int max = 8;             // 總共 8 個皇后
    private static int[] arr = new int[8];  // 這個一維陣列用於存放8個皇后在棋盤的擺放位置,索引代表行,值代表列
    private static int num = 0;				// 計數器,用於記錄有多少種解法

    public static void main(String[] args) {
        // 從第 0 行,開始擺放
        check(0);
        System.out.printf("最終解法有:%d 種", num);
    }

    /**
     * @Description 開始擺放皇后
     * @Param [n] 當前準備擺放的皇后的索引值(也就是所在行數)
     * @return void
     */
    public static void check(int n){
        // 遞迴首先是退出條件,也就是第 8 個皇后擺放完畢,即開始擺放第 9 個皇后了,就說明成功了
        if (n == max){
            num++;      // 成功的次數 +1
            showArr();	// 擺放完畢,就列印一下吧
            return;
        }
        // 如果不符合退出條件
        // 因為每一個皇后在每一行的擺放位置都有 8 個可能,所以需要遍歷 8 次
        for (int i=0; i<max; i++){
            arr[n] = i;     // 擺放
            // 判斷擺放是否合理
            if (isReasonable(n)){
                // 擺放合理的話,就開始擺放下一個
                check(n+1);
            }
        }
    }

    /**
     * @Description 判斷當前擺放的皇后是否和之前擺放的皇后有衝突
     * @Param [n] 當前正在擺放的皇后的索引值
     */
    public static boolean isReasonable(int n){
        // 需要遍歷,也就是和前面的每一個皇后進行比對當前擺放位置是否合理
        for (int i=0; i<n; i++){
            // arr[i] == arr[n] 代表的是擺放在同一列
            // Math.abs(n -i) == Math.abs(arr[n] - arr[i]) 代表的是擺放在對角線上
            if (arr[i] == arr[n] || Math.abs(n -i) == Math.abs(arr[n] - arr[i])){
                return false;
            }
        }
        return true;
    }

    /**
     * @Description 列印陣列
     */
    public static void showArr(){
        for (int i=0; i<max; i++){
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

執行結果如下:

在這裡插入圖片描述

以最後一個解為例,即 arr = {7, 3, 0, 2, 5, 1, 6, 4},代表著第 1 個皇后在第一行的第八列、第 2 個皇后在第二行的第四列、第3 個皇后在第三行的第一列…,以此類推。