八皇後問題之回溯法
阿新 • • 發佈:2019-04-07
求解 ron 問題分析 二維數組 int str 模型 是否 ostream
八皇後問題
將n個皇後放置在n*n的國際象棋棋盤上,其中沒有任何兩個皇後處於同一行,同一列或者同一對角線上,以使得的它們不能相互攻擊。
問題分析
- 最簡答的思路是把問題轉化為“從64個格子中選一個子集”,使得“子集中恰好有8個格子,且任意選出兩個格子都不在同一行,同一列或者同意對角線上”。這恰好是子集枚舉問題。然而,64個格子的子集有2^64個,太大了,則並不是一個很好的模型。
- 我們把思路轉化為“從64個格子中選出8個格子”,這是一個明顯的組合問題,根據排列組合,有C8 64=4.426*10^9種結果,雖然比第一種要好,但是性能依然不夠好。
- 最後我們通過思考,能不能將它裝換為一維問題:因為每行每列各放置一個皇後,如果用C[X]表示第X行皇後的列編號,則問題會變成全排列生成問題,而0-7的排列一共只有8!=40320個,枚舉的次數不會超過這個值。
如果這樣表示的話,那麽判斷條件應該如何表示呢?
- 我們可以先用二維數組表示一個八宮格,假設每個格子的坐標(x,y)是二維數組的下標,那麽格子(x,y)的y-x的值就能夠標識出租對角線,所有的主對角線的y-x的值是相等的,同理格子(x,y)的x+y值就能表示出副對角線,那麽所有的問題就都解決了。
回溯法
當把問題分成了若幹步驟並遞歸求解時,如果當前步驟沒有合理的選擇時,則函數將返回上一級遞歸調用,這種現象叫回溯。正是因為這個原因,遞歸枚舉算法常常被稱為回溯法
源碼
1 #include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 5 int n = 8; 6 int c[8]={0}; 7 int count = 0; 8 void search(int cur); 9 10 int main(){ 11 search(0); 12 } 13 14 void print(){ 15 printf("\n-----------------\n"); 16 for(int i = 0; i < 8; i++){ 17 for(int j = 0; j < 8; j++){ 18 if(c[i] == j){ 19 printf("|*"); 20 }else{ 21 printf("| "); 22 } 23 } 24 printf("|\n-----------------\n"); 25 } 26 } 27 28 void search(int cur){ 29 if(cur == n){ 30 print(); 31 printf("\n"); 32 }else{ 33 for(int i = 0; i < n; i++){ 34 c[cur] = i; 35 int ok = 1; 36 for(int j = 0; j < cur; j++){ 37 if(c[cur] == c[j] || j - c[j] == cur - c[cur] || j + c[j] == cur + c[cur]){ 38 ok=0; 39 break; 40 } 41 } 42 if(ok){ 43 search(cur+1); 44 } 45 } 46 } 47 }
算法改進
上述算法的枚舉的結點數適合很難減少了,但是程序的效率可以繼續提高,利用二維數組直接判斷當前嘗試的皇後所在的列和兩個對角線是否已有其他的皇後,註意的問題是,主對角線的表示y-x可能為負值,存取時要加上n。
1 void searchs(int cur){ 2 if(cur == n){ 3 count++; 4 }else{ 5 for(int i = 0; i < n; i++){ 6 if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){ 7 c[cur]=i; 8 vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 1; 9 searchs(cur + 1); 10 vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 0; 11 } 12 } 13 } 14 }
上述程序有個極其關鍵的地方:vis數組的使用。它表示的含義是已經放置的換後占據了哪些列,主對角線和副對角線。一般的,如果在回溯法中修改了輔助全局變量,則一般要把它們及時恢復原狀,有多個地方修改,每個地方都要恢復原有的值。
有興趣的話,可以研究一下n皇後問題,看一看有沒有什麽規律或者快速解法呢?
八皇後問題之回溯法