8皇后問題是一道非常經典的題目。題目是說,一個N*N的國際象棋棋盤上主放置N個皇后,使其不能相互攻擊,即任何兩個皇后都不能處在棋盤的同一行,同一列,同一條斜線上,試問共有多少種擺法。
其實,題目就是要找出所有的可能情況,然後排除其中不合格情況,剩下的情況即為所要求的。怎麼找出所有的情況呢。對於8皇后,我們可以使用窮舉法,窮舉出每一种放置方法,然後判斷是否符合題意。如果每次放一行,那就需要8重迴圈才可以解出來。雖然空間複雜度可以小到為0,但是時間複雜度太高。
書中一般使用回溯法來解此題。仔細分析此題,可以發現:每一行上只能放置一個皇后,然後後面每行放置的皇后,不能與前面的行上放置的皇后在同一列上或者同一對角線上。所以用一個一維數組就可以存放在棋盤上放置的皇后的行列資訊:一維數組的第i個位置存放的數值j就表示,在棋盤的第i行、第j列上放著一個皇后。棋盤的一行就用一個元素來表示,所以不能在同一行就不用判斷了。知道了皇后在棋盤的行列位置後,判斷是否符合後面的兩個條件也比較容易了(對角線只要仔細分析下,兩個二維座標點如果在對角線上,他們的行列座標將會滿足何種情況即可)。
搞定了資料結構,接著就要考慮如何進行回溯搜尋了。回溯一般借用遞迴來實現。用我的一個ACM非常牛的一個同學的話來說:回溯就是讓電腦自動的去搜尋,碰到符合的情況就結束或者儲存起來,在一條路徑上走到盡頭也不能找出解,就回到原來的岔路口,選擇一條以前沒有走過的路繼續探測,直到找到解或者走完所有路徑為止。就這一點,回溯和所謂的DFS(深度優先搜尋)是一樣的。那現在的關鍵是,怎麼實現搜尋呢。回溯既然一般使用遞迴來實現,那個這個遞迴調用該如何來寫呢。我現在的理解就是,進行回溯搜尋都會有一系列的步驟,每一步都會進行一些尋找。而每一步的情況除了輸入會不一樣之外,其他的情況都是一致的。這就剛好滿足了遞迴調用的需求。通過把遞迴結束的條件設定到搜尋的最後一步,就可以借用遞迴的特性來回溯了。因為合法的遞迴調用總是要回到它的上一層調用的,那麼在回溯搜尋中,回到上一層調用就是回到了前一個步驟。當在當前步驟找不到一種符合條件情況時,那麼後續情況也就不用考慮了,所以就讓遞迴調用返回上一層(也就是回到前一個步驟)找一個以前沒有嘗試過的情況繼續進行。當然有時候為了能夠正常的進行繼續搜尋,需要恢複以前的調用環境。
下面貼出8皇后問題的代碼: #include<iostream>
#include <cmath>
usingnamespace std;
void PrintResult(int*arr,int n)
{
for (int i=1; i!= n+ 1;++ i)
cout <<"("<< i << ","<< arr[i]<< ")"<<"" ;
cout << endl;
}
bool Verify(int*arr,int i)
{
/* 和前面的i - 1行比較,看當前置放位置是否合法。*/
for (int k = 1; k != i;++ k)
if (arr[k]== arr[i]|| abs(i - k)== abs(arr[i]- arr[k]))
returnfalse ;
return true ;
}
/* 雖然只用了一個一維數組,但是其中已經儲存了足夠的資訊。
因為每一行只能放一個皇后,所以一維數組的第i個位置存放的
是在第i行的哪一列(第j列)上放置了皇后。這個遞迴函式
每次處理一行,直到第n行(下標從1開始)。*/
void NQueens(int*arr,int i, int n)
{
/* 嘗試著在第i行的第j列放置一個皇后。*/
for (int j = 1; j != n+1;++ j)
{
arr[i] = j;
if (Verify(arr, i))
{
/* 這個遞迴程式的結束條件是第n行放置完畢,
所以,當遞迴函式從調用NQueens(arr, i + 1, n)返回時,
就是回到了第i行,繼續搜尋合適的位置。當第i + 1行的
所有位置都不能滿足的時候,上面的調用就會返回,也就
是進行了所謂的回溯。這個回溯不需要顯示的恢複以前的
調用環境,因為所需要的資訊沒有被破壞。*/
if (i== n)
PrintResult(arr, n);
else
NQueens(arr, i +1 , n);//下一行放置皇后
}
}
}
int main()
{
int n;
cin >> n;
int *arr =newint[n +1 ];
NQueens(arr, 1 , n);
return 0 ;
}
最後還有一個回溯的模般
回溯法對解空間作深度優先搜尋,因此,在一般情況下用遞迴方法實現回溯法。
:
void backtrack (int t)
{
if (t>n) output(x);
else
for (int i=f(n,t);i<=g(n,t);i++) {
x[t]=h(i);
if (constraint(t)&&bound(t)) backtrack(t+1);
}
}
供大家學習。
註:回溯法是一個既帶有系統性又帶有跳躍性的的搜尋演算法。它在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點出發搜尋解空間樹。演算法搜尋至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解。如果肯定不包含,則跳過對以該結點為根的子樹的系統搜尋,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜尋。回溯法在用來求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜尋遍才結束。而回溯法在用來求問題的任一解時,只要搜尋到問題的一個解就可以結束。這種以深度優先的方式系統地搜尋問題的解的演算法稱為回溯法,它適用於解一些組合數較大的問題.
詳細介紹參照:百度百科 http://baike.baidu.com/view/699271.htm