回溯算法思想與八皇后問題解的個數

八皇后問題:

在8*8的國際象棋棋盤上,皇后是威力較大的棋子,它能夠攻擊到與本身同行、同列以及同一斜線上的棋子,以下圖,全部橙色格子上的棋子,均可能會被皇后攻擊:java

而八皇后問題就是在8*8的棋盤上,找到合適的位置放置8個皇后,讓它們不會相互攻擊,並且須要找出這樣的放法共有多少種。算法

回溯法的思想:

回溯法就是當咱們肯定了一個問題的解空間的結構後,從根節點出發,以深度優先的方式去遍歷解空間,找到合適的解。因此用此方法分析八皇后問題以下:數組

解空間的結構:

將棋盤看做0-7的平面直角座標系,八皇后問題的解就是尋找八個點的座標(i,j)。先拋開具體問題的約束來看,咱們要尋找的第一個座標有64中可能,當第一個座標肯定後,第二個座標也有64中可能,第三個一樣如此......所以,全部可能的解造成了一個64叉樹(類比二叉樹的說法),這就是有可能的解(64^8種可能),解空間結構是64叉樹形狀的。jvm

基於此解空間的結構,才能以深度優先的方式去遍歷解空間,並尋找合適的解。工具

問題的解:

當咱們結合問題對解的約束來看,八皇后問題的解就是這個64叉樹上某些從根節點到葉子節點的路徑上的座標。具體約束就是皇后的攻擊規則(任意兩點不能在同一直線或斜線上)。ui

回溯遍歷解空間:

也就是將皇后一個個擺上去,當咱們擺到第n個皇后時,若發現按照約束條件,任何位置都不能擺放,那就說明第n-1個皇后在當前位置不能獲得正確的解,因此須要從新肯定第n-1個皇后的位置。若第n-1個皇后再無其餘位置可擺放,則須要從新肯定第n-2個皇后的位置......spa

這就是回溯遍歷解空間,在算法實現時,可使用遞歸或迭代進行回溯遍歷,分別被稱爲遞歸回溯和迭代回溯。code

八皇后問題算法解決:

算法使用名爲queen的二維int數組表示棋盤,數組的索引表示0-7的座標,值爲0表示空白,值爲1表示皇后的擺放位置。遞歸

因爲任意一行不能有兩個皇后,因此每一行必須擺放一個,所以程序在每一行依次嘗試:索引

package com.yawn.queen;

/**
 * @author yawn
 */
public class QueenTest {

    private static final int SIZE = 8;

    private static int[][] queen = new int[SIZE][SIZE];

    public static void main(String[] args) {
        for (int i = 0; i < SIZE; i++) {
            // 每一行都從0位置開始嘗試放置
            place(i, 0);
        }
        printQueen("結果爲");
    }

    /**
     * 第i行的放置
     */
    private static void place(int i, int start) {
        // 是否放置成功
        boolean placed = false;
        for(int j = start; j < SIZE; j++) {
            if (check(i, j)) {
                queen[i][j] = 1;
                placed = true;
                printQueen("第 " + i + " 行放置");
                break;
            }
        }
        if (!placed) {
            int index = seek(i - 1);
            // 去掉已經放置的皇后
            queen[i-1][index] = 0;
            printQueen("回溯到第 " + (i-1) + " 行,從 " + (index+1) + " 開始");
            place(i-1, index+1);
            place(i, 0);
        }
    }


    /// 如下爲工具方法,內容單一,淺顯易懂
    /**
     * 輸出棋盤
     */
    private static void printQueen(String action) {
        System.out.println("---> " + action);
        for (int[] cells : queen) {
            for (int cell : cells) {
                System.out.print(cell + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 尋找第i行放置皇后的位置
     */
    private static int seek(int i) {
        for (int j = 0; j < SIZE; j++) {
            if (queen[i][j] == 1) {
                return j;
            }
        }
        return -1;
    }

    /**
     * 檢查queen[i][j] 是否能夠放置皇后
     */
    private static boolean check(int i, int j) {
        for(int m = 0; m < SIZE; m++) {
            for(int n = 0; n < SIZE; n++) {
                if (queen[m][n]==1 && (i == m || j == n || Math.abs(i - m) == Math.abs(j - n))) {
                    // queen[m][n]==1 :(m,n)位置有皇后
                    // i == m :同行
                    // j == n :同列
                    // Math.abs(i - m) == Math.abs(j - n) :同一斜線(兩個位置連線的斜率絕對值爲1)
                    // 若是知足if條件,則(i,j)不可放置皇后
                    return false;
                }
            }
        }
        return true;
    }
}

求解過程當中的輸出片斷以下:

---> 第 5 行放置
1	0	0	0	0	0	0	0	
0	0	0	0	1	0	0	0	
0	1	0	0	0	0	0	0	
0	0	0	0	0	0	0	1	
0	0	0	0	0	1	0	0	
0	0	1	0	0	0	0	0	
0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	
---> 回溯到第 5 行,從 3 開始
1	0	0	0	0	0	0	0	
0	0	0	0	1	0	0	0	
0	1	0	0	0	0	0	0	
0	0	0	0	0	0	0	1	
0	0	0	0	0	1	0	0	
0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	
---> 第 5 行放置
1	0	0	0	0	0	0	0	
0	0	0	0	1	0	0	0	
0	1	0	0	0	0	0	0	
0	0	0	0	0	0	0	1	
0	0	0	0	0	1	0	0	
0	0	0	1	0	0	0	0	
0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0

咱們能夠看出:第5行放置在了(5,2)位置,而後嘗試放置第六行,結果第6行沒法放置,因此回溯到第5行,從(5,3)位置繼續嘗試放置,並在(5,3)放置成功。

八皇后問題解的個數:

以上代碼爲咱們找到了問題的一個解,但咱們想知道一共有多少解存在,這就須要咱們稍微修改代碼,具體以下:

在以上代碼中,若找到一個可行的解以後,程序就會執行結束。但咱們若要找到全部的解,就須要在找到可行解後,繼續讓程序嘗試下一個解便可。具體作法就是當最後一行均可以放置好皇后時,咱們只記錄這個解,而後再讓程序嘗試當前位置的下一個位置,而不退出程序。

當遍歷完全部可能的解以後,就能夠中止程序。因爲在遞歸調用過程當中,會產生很深的方法棧,致使棧滿報錯,因此棋盤不宜設置太大:

八皇后問題解的個數具體實現以下:

package com.yawn.queen;

/**
 * @author yawn
 */
public class QueenTest2 {

    private static final int SIZE = 12;
    private static int count = 0;

    private static int[][] queen = new int[SIZE][SIZE];

    public static void main(String[] args) {
        for (int i = 0; i < SIZE; i++) {
            place(i, 0);
        }
    }

    /**
     * 第i行的放置
     */
    private static void place(int i, int start) {
        if (i==0 && start== SIZE) {
            System.out.println("結果集已經遍歷完畢,共找到結果數爲:" + count);
            System.exit(0);
        }
        // 第i行是否放置成功
        boolean rowPlaced = false;
        // 遍歷第i行每一個位置是否能夠放置皇后
        for(int j = start; j < SIZE; j++) {
            if (check(i, j)) {
                queen[i][j] = 1;
                rowPlaced = true;
                // 最後一行放置成功,則獲得一個結果,打印結果後從下一位置繼續回溯遍歷結果集
                if (i== SIZE -1) {
                    count++;
                    printQueen("第" + count + "個結果,從(" + (SIZE-1) + "," + (j+1) + ")繼續回溯尋找...");
                    // 去掉已經放置的皇后
                    queen[i][j] = 0;
                    place(i, j+1);
                }
                break;
            }
        }
        // 若是第i行沒有放置成功,則回溯到第i-1行(從新放置第i-1行)
        if (!rowPlaced) {
            int index = seek(i - 1);
            reset(i-1, index);
            // 回溯放置第i-1行
            place(i-1, index+1);
            // 直到第i-1行放置成功,再開始放置第i行(最終將全部結果集遍歷完畢後,只能使用exit(0)退出程序)
            place(i, 0);
        }
    }


    /// 如下爲工具方法,內容單一,淺顯易懂
    /**
     * 輸出棋盤
     */
    private static void printQueen(String string) {
        System.out.println("---> " + string);
        for (int[] cells : queen) {
            for (int cell : cells) {
                System.out.print(cell + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 去掉(i,j)位置已經放置的皇后
     */
    private static void reset(int i, int j) {
        queen[i][j] = 0;
    }

    /**
     * 尋找第i行放置皇后的位置
     */
    private static int seek(int i) {
        for (int j = 0; j < SIZE; j++) {
            if (queen[i][j] == 1) {
                return j;
            }
        }
        return -1;
    }

    /**
     * 檢查queen[i][j] 是否能夠放置皇后
     */
    private static boolean check(int i, int j) {
        for(int m = 0; m < SIZE; m++) {
            for(int n = 0; n < SIZE; n++) {
                if (queen[m][n]==1) {
                    if (i == m || j == n || Math.abs(i - m) == Math.abs(j - n)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}
---> 第91個結果,從(7,4)繼續回溯尋找...
0	0	0	0	0	0	0	1	
0	0	1	0	0	0	0	0	
1	0	0	0	0	0	0	0	
0	0	0	0	0	1	0	0	
0	1	0	0	0	0	0	0	
0	0	0	0	1	0	0	0	
0	0	0	0	0	0	1	0	
0	0	0	1	0	0	0	0	
---> 第92個結果,從(7,5)繼續回溯尋找...
0	0	0	0	0	0	0	1	
0	0	0	1	0	0	0	0	
1	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	
0	0	0	0	0	1	0	0	
0	1	0	0	0	0	0	0	
0	0	0	0	0	0	1	0	
0	0	0	0	1	0	0	0	
結果集已經遍歷完畢,共找到結果數爲:92

最終棋盤規格爲8*8時,共有92個解。

原文參考做者的博客:

http://www.jvm123.com/2019/08/hui-su-fa-si-xiang/

相關文章
相關標籤/搜索