八皇后問題(N皇后問題)

八皇后問題是一個經典的遞歸回溯問題。

描述

八皇后問題是在一個 8*8 的棋盤上放置皇后,要求其放置后滿足同一行,同一列,同一對角線上沒有重復的皇后出現。試問有多少種擺盤方式?

思路

我們的主要思路是通過一行一行的放置皇后,來使得每一行都有一個皇后。當然,這些皇后在放置時都必須要滿足規定的要求才行。

因此就會出先如下情況:

  • 放置時不符合規則,繼續檢索同一行的下一列位置是否合理
  • 如果符合規則就將其放置,然后進行下一行的嘗試(遞歸)
  • 如果有某一行沒有可行的解,則退回上一行,消除上一行擺放的皇后,檢索剩余的列,看是否有合理的位置,然后繼續進行。(回溯)
  • 直到所有的行都被放置為止。

需要注意的是,我們在放置皇后時需要檢測其防止和理性的判斷條件為:

  1. 同一列的上方所有行中是否有皇后
  2. 左上方對角線上是否有皇后
  3. 右上方對角線上是否有皇后

算法實現

public class EightQueen {

    private static final int num = 8; // 可以拓展為N皇后問題
    private static int[][] item = new int[num][num];
    private static int methods = 0; // 總方法數

    public static void main(String[] args) {
        buildQueen(0);
        System.out.println(methods);
    }

    /**
     * 構建棋盤的第row行
     *
     * @param row
     */
    private static void buildQueen(int row) {
        if (row == num) {
            methods++;
//            System.out.println("第" + methods + "種解法:");
//            for (int i = 0; i < num; i++) {
//                for (int j = 0; j < num; j++) {
//                    System.out.print(item[i][j] + " ");
//                }
//                System.out.print("\n");
//            }
            return;
        } else {
            for (int col = 0; col < num; col++) { // 每一列進行檢查,試探性放置
                if (isSatisfy(row, col)) {
                    item[row][col] = 1;
                    buildQueen(row + 1);
                    item[row][col] = 0;
                }
            }
        }
    }

    /**
     * 檢查row行col列元素是否滿足要求
     * 因為是一行行的放置皇后,所以不需要檢測同一行是否存在重復皇后
     * 在判斷重復元素時,只需要判斷上半部分的區域即可
     *
     * @param row
     * @param col
     * @return
     */
    private static boolean isSatisfy(int row, int col) {
        for (int i = 0; i < row; i++) {
            if (item[i][col] == 1) { // 同一列的上方元素
                return false;
            }
        }
        for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) { // 左上方斜對角線
            if (item[i][j] == 1) {
                return false;
            }
        }
        for (int i = row, j = col; i >= 0 && j < num; i--, j++) { // 右上方斜對角線
            if (item[i][j] == 1) {
                return false;
            }
        }
        return true;
    }
}

優雅的位運算解法

我們直接從一個例子來講解思路吧。先看看下圖的情況:


img1.jpeg

我們可以看到,前三行已經放置了皇后,我們需要在第四行選擇放置皇后的點。陰影部分表示會出現沖突的格子,而沖突我們主要分為三種:同列沖突、右下方沖突和左下方沖突。

而就這對這種情況而言(此例為八皇后問題,可拓展到N皇后),一行剛好8個格子,對應8位二進制數字。因此我們可以首先定義沖突:

同列沖突: A = 1000 1001;

右下沖突: B = 0001 0010;

左下沖突: C = 0010 0010;

其中1表示沖突的格子,0表示可以放置皇后的格子。因此我們可以輕松得出綜合的沖突情況:

D = (A | B | C) = 1011 1011;

對于我們將要放置的第四行而言,現在有兩個0,意味著有兩個可以放置皇后的位置,我們需要將所有的情況都考慮到,這里有一個神奇的式子:bit = (D + 1) & ~D; 它計算得出的結果是: 0000 0100;

其實它能夠得到最右邊一個可以放置皇后的位置,并用1來表示,其余位是0。 這樣做是有好處的...

我們現在得出 bit = 0000 0100,便能夠輕松得到下一行的沖突 A' = (A | bit); B' = (B | bit) >> 1; C' = (C | bit) << 1; 便能夠很輕易地寫出遞歸式了。

而我們的第4行試探其實并沒有結束,只是從左向右的第一個可以放置的位置進行了試探,那想要取到第二個可以放置的位置怎么辦呢?很簡單,只需要做如下運算:

D = D + bit 將剛才試過的那一位設置為不能放置皇后狀態,然后繼續做 bit = (D + 1) & ~D 即可。

一直循環的試探,知道D 全部為1 為止。

下面是整個程序的代碼:

public class NQueen {

    private static final int N = 8; // 皇后數量,可拓展為N皇后
    private static int count = 0; // 總方法數
    private static int limit;

    public static void main(String[] args) {
        limit = (1 << N) - 1;
        backtracking(0, 0, 0, 0);
        System.out.println(count);
    }

    private static void backtracking(int a, int b, int c, int depth) {
        if (depth == N) {
            count++;
            return;
        }
        int d = a | b | c;
        while (d < limit) {
            int bit = (d + 1) & ~d;
            backtracking(a | bit, limit & ((b | bit) >> 1), limit & ((c | bit) << 1), depth + 1);
            d |= bit;
        }
    }
}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 我今天好不容易上了一次課,然后數據結構老師給我們留大作業,喪心病狂,先解決一個叫八皇后的問題。 題目背景: 【問題...
    阿里高級軟件架構師閱讀 3,009評論 1 5
  • 問題描述 n皇后問題是將n個皇后放置在n*n的棋盤上,皇后彼此之間不能相互攻擊(不同行,不同列,不同對角線)。給定...
    Alfie20閱讀 626評論 0 0
  • 高中時我特別想離開家,在上課的時候就老走神,腦子里全是“我要去流浪,我要去遠方”這種盲目的念頭。但...
    履善閱讀 361評論 0 2
  • 文/古 泉淵 字數:2400左右,建議閱讀時間:4分鐘 01 冬與克爾凱郭爾 冬天忽然來了,氣溫驟降,冷得直叫人哆...
    古泉淵閱讀 812評論 5 4
  • 文/創業人張涵 回顧自己這幾年的經歷,順風順水卻一無所得。有的時候真的非常沮喪,有的時候卻又無比慶幸,這矛盾的兩極...
    dfbc10ae5419閱讀 323評論 0 3