【基礎算法】回溯法與八皇后問題

 
 

  在國際象棋中,皇后是最強大的一枚棋子,能夠吃掉與其在同一行、列和斜線的敵方棋子。比中國象棋裏的車強幾百倍,比她那沒用的老公更是強的飛起(國王只能先後左右斜線走一格)。上圖右邊高大的棋子即爲皇后。ios

   八皇后問題是這樣一個問題:將八個皇后擺在一張8*8的國際象棋棋盤上,使每一個皇后都沒法吃掉別的皇后,一共有多少種擺法?此問題在1848年由棋手馬克斯·貝瑟爾提出,豈止是有年頭,簡直就是有年頭,82年的拉菲分分鐘被秒的渣都不剩。算法

  八皇后問題是典型的回溯法解決的問題,咱們以這個問題爲例介紹回溯法。數組

  所謂回溯法,名字高大上,思想很樸素。設想把你放在一個迷宮裏,想要走出迷宮,最直接的辦法是什麼呢?沒錯,試。先選一條路走起,走不通就往回退嘗試別的路,走不通繼續往回退,直到找到出口或全部路都試過走不出去爲止。框架

是的你沒說錯,這就是暴力破解。對於BigMoyan這種簡單粗暴不喜歡動腦子的人來講實在是太合適了,盲僧李青說得好:若是暴力不是爲了解題,那就毫無心義了。函數

 

  儘管回溯法也算是暴力方法,但也不是特別暴力,特別暴力的相關部門都不讓播,能播的都是能夠接受的暴力。怎麼說?考慮八皇后問題,解決這個問題最暴力的辦法是這樣的:測試

  有關部門不讓播的方法:spa

  從8*8=64個格子裏選8個格子,放皇后,測試是否知足條件,若知足則計數加1,不然換8個格子繼續試。code

  很顯然, 64中選8,並非個小數字,十億級別的嘗試次數,夠暴力。blog

  這仍是8*8的格子,要是換圍棋棋盤……這畫面太美我都不敢算。遞歸

  稍加分析,咱們能夠獲得一個不那麼暴力的辦法,顯然,每行每列最多隻能有一個皇后,若是基於這個事實進行暴力破解,那結果會好得多。安排皇后時,第一行有8種選法,一旦第一行選定,假設選爲(1,i),第二行只能選(2,j),其中j!=i,因此有7種選法。以此類推,須要窮舉的狀況有8!=40320種。

  看起來這個結果已經不錯了,但嘗試的次數是隨問題規模按階乘水平提升的,BigMoyan仍然不滿意——咋可能滿意嘛!回溯法還沒出,我要是滿意了剩下的篇幅講啥。

  8皇后太多,後宮太過豐富BigMoyan可hold不住,不如咱們先裁一半分析試試,因而BigMoyan與4位皇后辦了離婚手續,同時把家縮小了一半,那麼如今問題變成了4皇后問題,4個皇后在4*4的格子裏各自安排不打架,一共有多少種安排方法?

 

  試着來窮舉一下,真的須要4!=24次嘗試嗎?

  如今咱們把第一個皇后放在第一個格子,被塗黑的地方是不能放皇后的。

 

  第二行的皇后只能放在第三格或第四格,比方咱們放第三格,則:

  糟啦擼,前兩位皇后狼狽爲奸,已經把第三行所有鎖死了,第三位皇后不管放哪裏都難逃被吃掉的厄運。因而在第一個皇后位於1號,第二個皇后位於3號的狀況下問題無解。咱們只能返回上一步來,給2號皇后換個位置。

  顯然,第三個皇后只有一個位置可選。當第三個皇后佔據第三行藍色空位時,第四行皇后無路可走,因而發生錯誤,返回上層調用(3號皇后),而3號也別無可去,繼續返回上層調用(2號),2號已然無路可去,繼續返回上層(1號),因而1號皇后改變位置以下,繼續搜索。

  話說道這裏,想必讀者對「回溯法」已經有了基本概念。然而所謂知易行難,理解算法和將算法寫出來徹底是兩回事。按照BigMoyan的風格,下面該進行算法分析了,下面的代碼改寫自劉汝佳《算法競賽入門經典》,幾乎是BigMoyan看到的實現8皇后問題最簡潔的代碼,改寫後整個函數只有10行。

void queen(int row){
    if(row==n)
        total++;
    else
        for(int col=0;col!=n;col++){
            c[row]=col;
            if(is_ok(row))
                queen(row+1);
        }        
}

 

  算法是逐行安排皇后的,其參數row爲如今正執行到第幾行。n是皇后數,在八皇后問題裏固然就是8啦。

  第2行好理解,若是程序當前能正常執行到第8行,那天然是找到了一種解法,因而八皇后問題解法數加1。

  若是當前還沒排到第八行,則進入else語句。遍歷全部列col,將當前col存儲在數組c裏,而後使用is_ok()檢查row行col列能不能擺皇后,若能擺皇后,則遞歸調用queen去安排下一列擺皇后的問題。

  還不太清楚?再慢點來,剛開始的時候row=0,意思是要對第0行擺皇后了。

  If判斷失敗,進入else,進入for循環,col初始化爲0

  顯然,0行0列的位置必定能夠擺皇后的,由於這是第一個皇后啊,後宮空蕩她想怎麼折騰就怎麼折騰,因而is_ok(0)測試成功,遞歸調用queen(1)安排第1行的皇后問題。

  第1行時row=1,進來if依然測試失敗,進入for循環,col初始化爲0。1行0列顯然是不能擺皇后的,由於0行0列已經有一個聖母皇太后在那擱着了,因而is_ok()測試失敗,循環什麼也不作空轉一圈,col變爲1。1行1列依然is_ok()測試失敗,一直到1行2列,發現能夠擺皇后,因而繼續遞歸queen(2)去安排第二個皇后位置。

  若是在某種狀況下問題無解呢?例如前面在4皇后問題中,0行0列擺皇后是無解的。假設前面遞歸到queen(2)時候,發現第2行沒有地方能夠擺皇后,那怎麼辦呢?要注意queen(2)的調用是在queen(1)的for循環框架內的,queen(2)若無解,則天然而然queen(1)的for循環col自加1,即將第1行的皇后從1行2列改成1行3列的位置,檢查能否放皇后後繼續安排下一行的皇后。如此遞歸,當queen(0)的col自加到7,說明第一列的皇后已經遍歷了從0行1列到0行7列,此時for循環結束,程序退出。

  在主函數中調用queen(0),獲得正確結果,8皇后問題一共有92種解法。

  所有程序以下:

 

 1 #include<iostream>
 2 #include<math.h>
 3 using namespace std;
 4 
 5 int n=8;
 6 int total=0;
 7 int *c=new int(n);
 8 
 9 bool is_ok(int row){
10     for(int j=0;j!=row;j++){
11         if(c[row]==c[j] || row-c[row]==j-c[j] || row+c[row]==j+c[j])
12             return false;
13     }
14     return true;
15 }
16 
17 void queen(int row){
18     if(row==n)
19         total++;
20     else
21         for(int col=0;col!=n;col++){
22             c[row]=col;
23             if(is_ok(row))
24                 queen(row+1);
25         }       
26 }
27 
28 int main(){
29     queen(0);
30     cout<<total;
31     return 1;
32 }
33  
相關文章
相關標籤/搜索