關于八皇后問題以及回溯遞歸思想

大家好,我是“Stephen·謝”,本文以古老的八皇后問題的文字解釋和代碼實現,將遞歸回溯的思想概念介紹給大家。


國際象棋中的皇后比中國象棋里的大車還厲害,皇后能橫向,縱向和斜向移動,在這三條線上的其他棋子都可以被吃掉。所謂八皇后問題就是:將八位皇后放在一張8x8的棋盤上,使得每位皇后都無法吃掉別的皇后,(即任意兩個皇后都不在同一條橫線,豎線和斜線上),問一共有多少種擺法。此問題是在1848年由棋手馬克思·貝瑟爾提出的,后面陸續有包括高斯等大數學家們給出自己的思考和解法,所以此問題不只是有年頭了,簡直比82年的拉菲還有年頭,我們今天不妨嘗嘗這老酒。

我們先舉例來理解一下這個問題的場景到底是什么樣子的,下面的綠色格子是一個皇后在棋盤上的“封鎖范圍”,其他的皇后不能放置在這些綠格子中:

一個皇后的封鎖范圍

我們再放入一個皇后,看一下兩個皇后的“封鎖范圍”(綠格子不能放):

兩個皇后的封鎖范圍

如此繼續下去,能安放下一位皇后的位置越來越少,那么我們最終如何能安放完這8位皇后呢?

首先我們看一下特別暴力的方法:從8x8的格子里選8個格子,放皇后,然后測試是否滿足條件,若滿足則結果加1,否則換8個格子繼續試。很顯然,64選8,并不是個小數字,十億級別的次數,夠暴力。如果換成圍棋的棋盤,畫面就會太美而不敢算。

稍加分析,我們可以得到另一個不那么暴力的方法:顯然,每行每列最多只能有一位皇后,如果基于這個事實再進行暴力破解,那結果會好很多。安排皇后時,第一行有8種選法,一旦第一行選定,假設選為(1,i),那么第二行只能選(2,j),其中,j不等于i,所以有7種選法。以此類推,需要窮舉的情況有8!=40320種,比十億級別的小很多了。

這看起來已經不錯了,但嘗試的次數還是隨著問題規模按階乘水平提高的,我們仍然不滿意,所以,“遞歸回溯”的思想就被提出了,專治這種問題。

為了理解“遞歸回溯”的思想,我們不妨先將4位皇后打入冷宮,留下剩下的4位安排進4x4的格子中且不能互相打架,有多少種安排方法呢?如果按照上面方式窮舉,需要4!=24次嘗試嗎?

現在我們把第一個皇后放在第一個格子,被涂黑的地方是不能放皇后的:

放第1個皇后

第二行的皇后只能放在第三格或第四格,比如我們放在第三格:

放第2個皇后

這樣一來前面兩位皇后已經把第三行全部鎖死了,第三位皇后無論放在第三行的哪里都難逃被吃掉的厄運。于是在第一個皇后位于第一格,第二個皇后位于第三格的情況下此問題無解。所以我們只能返回上一步,來給2號皇后換個位置:

給2號皇后換個位置

此時,第三個皇后只有一個位置可選。當第三個皇后占據第三行藍色空位時,第四行皇后無路可走,于是發生錯誤,則返回上層調整3號皇后,而3號皇后也別無可去,繼續返回上層調整2號皇后,而2號皇后已然無路可去,則再繼續返回上層調整1號皇后,于是1號皇后往后移一格位置如下,再繼續往下安排:

回溯重新安排1號皇后

上面的圖例正是回溯遞歸思想的展現,然而知易行難,在代碼中我們怎樣來實現這種算法呢?實現的代碼有很多種,我們找一個最簡單的來舉例吧:

遞歸回溯的核心代碼

我們來重點看一下這段代碼(這段代碼雖短,但真的非常非常重要,是整個算法的核心和靈魂):

第一次進來,row=0,意思是要在第一行擺皇后,只要傳進來的row參數不是8,表明還沒出結果,就都不會走if里面的return,那么就進入到for循環里面,column從0開始,即第一列。此時第一行第一列肯定合乎要求(即check方法肯定通過),能放下皇后,因為還沒有任何其他皇后來干擾。

關鍵是check方法通過了之后,在if里面又會調用一下自己(即遞歸),row加了1,表示擺第二行的皇后了。第二行的皇后在走for循環的時候,分兩種情況,第一種情況:for循環沒走到頭時就有通過check方法的了,那么這樣就順理成章地往下走再調用一下自己(即再往下遞歸),row再加1(即擺第三行的皇后了,以此類推)。第二種情況:for循環走到頭了都沒有通過check方法的,說明第二行根本一個皇后都擺不了,也觸發不了遞歸,下面的第三行等等后面的就更不用提了,此時控制第一行皇后位置的for循環column加1,即第一行的皇后往后移一格,即擺在第一行第二列的位置上,然后再往下走,重復上述邏輯。

注意,一定要添加清零的代碼,它只有在皇后擺不下去的時候會執行清0的動作(避免臟數據干擾),如果皇后擺放很順利的話從頭到尾是不會走這個請0的動作的,因為已經提前走if里面的return方法結束了。

總之,這段核心代碼很繞,原理一定要想通,想個十幾二十遍差不多就能理解其中的原理了,遞歸回溯的思想也就不言而喻了。八皇后問題一共有92種情況,下面是用Java實現的完整代碼:

public static int[][] arry=new int[8][8];//棋盤,放皇后
public static int map=0;//存儲方案結果數量

public static void main(String[] args) {
    // TODO Auto-generated method stub

    System.out.println("八皇后問題");
    findQueen(0);
    System.out.println("八皇后問題共有:"+map+"種可能");
}

public static void findQueen(int i){//尋找皇后節點
    if(i>7){//八皇后的解  
        map++;
        print();//打印八皇后的解
        return;
    }
    
    for(int m=0;m<8;m++){//深度回溯,遞歸算法
        if(check(i,m)){//檢查皇后擺放是否合適
            arry[i][m]=1;
            findQueen(i+1);
            arry[i][m]=0;//清零,以免回溯的時候出現臟數據
            }
    }   
}

public static boolean check(int k,int j){//判斷節點是否合適
    for(int i=0;i<8;i++){//檢查行列沖突
         if(arry[i][j]==1){
                return false;
         }
    }
    for(int i=k-1,m=j-1; i>=0 && m>=0; i--,m--){//檢查左對角線
        if(arry[i][m]==1){
                return false;
        }
    }
    for(int i=k-1,m=j+1; i>=0 && m<=7; i--,m++){//檢查右對角線
        if(arry[i][m]==1){
                return false;
        }
    }
    return true;
}

public static void print(){//打印結果
    System.out.print("方案"+map+":"+"\n");
    for(int i=0;i<8;i++){
        for(int m=0;m<8;m++){
            if(arry[i][m]==1){  
                //System.out.print("皇后"+(i+1)+"在第"+i+"行,第"+m+"列\t");
                System.out.print("o ");
            }
            else{
                    System.out.print("+ ");
            }
        }
        System.out.println();
    }
    System.out.println();
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 回溯算法 回溯法:也稱為試探法,它并不考慮問題規模的大小,而是從問題的最明顯的最小規模開始逐步求解出可能的答案,并...
    fredal閱讀 13,725評論 0 89
  • 什么是八皇后問題? 八皇后問題是一個古老的問題,于1848年由一位國際象棋棋手提出:在8×8格的國際象棋上擺放八個...
    Soujiro閱讀 2,084評論 0 3
  • 八皇后問題是一個經典的遞歸回溯問題。 描述 八皇后問題是在一個 8*8 的棋盤上放置皇后,要求其放置后滿足同一行,...
    JYGod丶閱讀 2,397評論 1 3
  • 八皇后問題問題描述:八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾...
    藥藥耀耀閱讀 2,091評論 0 0
  • 【旅行雪鄉故事】雪龍峰其實就是大禿頂子山的最高峰,海拔1691米,從大雪谷門前徒步到雪龍峰頂10公里左右,純玩的游...
    勒克兒閱讀 577評論 1 3