1. 程式人生 > >N皇后問題(回溯遞迴)

N皇后問題(回溯遞迴)

Problem

八皇后問題是一個以國際象棋為背景的問題:如何能夠在 8×8 的國際象棋棋盤上放置八個皇后,使得任何一個皇后都無法直接吃掉其他的皇后?為了達到此目的,任兩個皇后都不能處於同一條橫行、縱行或斜線上。

Solution

八個皇后中任意兩個不能處在同一行,所以每個皇后必須佔據一行,及一列。我們採用回溯法的思想去解。首先擺放好第1行皇后的位置,然後在不衝突的情況下襬放第2行皇后的位置。到第i行時,如果沒有一個位置可以無衝突的擺放皇后,則回溯到第i-1行,尋找第i-1行皇后的下一個可以擺放的位置。

回溯法採用試錯的思想,它嘗試分步的去解決一個問題。在分步解決問題的過程中,當它通過嘗試發現現有的分步答案不能得到有效的正確的解答的時候,它將取消上一步甚至是上幾步的計算,再通過其它的可能的分步解答再次嘗試尋找問題的答案。回溯法通常用最簡單的遞迴方法來實現,在反覆重複上述的步驟後可能出現兩種情況:
* 找到一個可能存在的正確的答案
* 在嘗試了所有可能的分步方法後宣告該問題沒有答案

總結一下,用回溯的方法解決8皇后問題的步驟為:
1)從第一列開始,為皇后找到安全位置,然後跳到下一列
2)如果在第n列出現死衚衕,如果該列為第一列,棋局失敗,否則後退到上一列,再進行回溯
3)如果在第8列上找到了安全位置,則棋局成功。


在最壞的情況下,回溯法會導致一次複雜度為指數時間的計算。

Source code

#include <iostream>
using namespace std;
int num = 1;

bool IsOk(int i, int j, int n, bool **map)
{
    for (int k = 0; k < i; k++)
    for
(int h = 0; h < n; h++) { if (map[k][h]) { if (h == j)//同一列 return false; if (abs(k - i) == abs(h - j))//對角線 return false; } } return true; } void print(bool **map, int n) { cout << "solution :" << num++ << " :\n"
; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (map[i][j]) cout << "Q" << " "; else cout << "." << " "; } cout << endl; } } void Search(int i, int n, bool **map)//尋找第i行queen可以擺放的位置 { if (i >= n )//遞迴出口 print(map, n); else { for (int j = 0; j < n; j++) { map[i][j] = true;//依次嘗試i行queen的所有可能位置 if (IsOk(i, j, n, map)) Search(i + 1, n, map); //else map[i][j] = false; //查詢第i行元素的下一列位置:兩種情況。A:IsOk(i, j, n, map)= false.B.遞歸回溯時,下一行找不到合適的位置or已經將map[i][j]=1情況下的所有可能位置都遍歷完。 //此處不加else,遞歸回溯時,如果加了else此時的判斷條件並不是IsOk(i, j, n, map)= false,而是IsOk(i-1, j, n, map)= true.是IsOk(i+1, j, n, map)= false. //當遞迴返回時並不會執行map[i][j]=false語句,因此達不到回溯效果 } } //前面i-1行的queen已經無衝突的擺好 } int main(void) { int n; cout << "Input the number of queens!" << endl; cin >> n; //initial map bool **map = new bool *[n]; for (int i = 0; i < n; i++) map[i] = new bool[n]; for (int i = 0; i < n;i++) for (int j = 0; j < n; j++) map[i][j] = false; Search(0, n, map); return 0; }