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