1. 程式人生 > 其它 >八皇后問題 - 回溯法的應用

八皇后問題 - 回溯法的應用

技術標籤:劉汝佳紫書演算法遞迴法

題目

  • 大意:
    在國際象棋的棋盤上,按照國際象棋的規則,擺放8個皇后,使得不互相攻擊,要求找出所有的解。皇后的攻擊範圍為同行同列和同對角線。

知識點

  • 回溯法

思路

  • 在解決這類問題時(是否合法/存在限制條件),我們可以在暴力求解使用遞迴時同時檢查是否已經不滿足要求,如果不滿足要求則直接遞迴返回,減少搜尋樹的大小,就是我們的“回溯法”。
  • 在這道題目中,基於皇后的性質,我們必然不可能在一行中有多個皇后,因此我們每一層的遍歷可以按照“逐行”或“逐列”搜尋,即每一層遞迴確定一行或一列可以放置的皇后的位置,記錄當前的狀態(當然這個狀態是臨時的,因為該層滿足條件狀態不唯一)然後逐漸向下遞迴。若條件不滿足或到達遞迴邊界後一層一層返回到當前狀態,更新下一個可能的狀態。如果當前所有可能的狀態更新完畢,則返回上一層(去更新可能的狀態)
  • 這裡的主對角線與副對角線的判斷方法有一個巧妙之處,我們看到“列-行”與“列+行”可以代表的主對角線和副對角線是唯一的,因此我們可以用這樣的方式去判斷某一條主對角線或副對角線是否已經被放置皇后: !vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i],這裡使用的vis[3][15]vis[0]代表某列,vis[1]代表某一主對角線,vis[2]代表某一副對角線。在判斷時候我們的主對角先使用了cur - i + 8是因為主對角線的表示可能為負,如下圖所示:

圖1 - 列-行唯一標識了主對角線

程式碼

# include
<iostream>
using namespace std; int vis[3][15]; // 記錄是否已放置 int result[8][8]; // 用於輸出結果 void search(int cur) { // cur - 遞迴的層數,每一層相當於在第cur行放置一個queen if (cur >= 8) { // 到達遞迴邊界,輸出一個結果(此時到達這裡的,必然滿足條件,不滿足的在前面直接已經返回) for (int i = 0; i < 8; i++) { for (int j = 0; j <
8; j++) cout << result[i][j] << " "; cout << endl; } cout << endl; return; // 不要忘記返回 } for (int i = 0; i < 8; i++) { // 在第cur行第i列放置chess的情況 // 第i列、(i-cur) 號主對角線、(cur+i) 號副對角線都沒放置,則可以更新為該狀態 if (!vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i]) { // 更新狀態,第i列、(i-cur) 號主對角線、(cur+i) 號副對角線設定為已放置 vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 1; result[cur][i] = 1; // 記錄當前狀態 search(cur + 1); // 向下遞迴 // 遞歸回來還原之前的狀態!!切記 vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 0; result[cur][i] = 0; // 記錄當前結果 } } } int main() { memset(vis, 0, sizeof(int[3][8])); // 初始化 search(0); return 0; }

過程中遇到的問題 & 解決

  • 使用列-行標識唯一主對角線時候,注意可能出現的負數的情況
  • 回溯法一定要記得在狀態遞迴返後,還原之前的狀態,再去尋找當前遞迴層下一個可能的狀態
  • 到達遞迴邊界不要忘記return,不然程式會出現異常

測試

輸入:

/

結果:✔️(只列舉了兩個)

1 0 0 0 0 0 0 0 
0 0 0 0 1 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 1 0 
0 1 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 1 0 0 
0 0 0 0 0 0 0 1 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
      ...