八皇后問題是一個古老而著名的問題,它是回溯演算法的典型例題。該問題是十九世紀德國著名數學家高斯於1850年提出的:在8行8列的國際象棋棋盤上擺放著八個皇后。若兩個皇后位於同一行、同一列或同一對角線上,則稱為它們為互相攻擊。在國際象棋中皇后是最強大的棋子,因為它的攻擊範圍最大,圖6-15顯示了一個皇后的攻擊範圍。
圖6-15 皇后的攻擊範圍
現在要求使這八個皇后不能相互攻擊,即任意兩個皇后都不能處於同一行、同一列或同一對角線上,問有多少種擺法。高斯認為有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。現代教學中,把八皇后問題當成一個經典遞迴演算法例題。圖6-16顯示了兩種八個皇后不相互攻擊的情況。
圖6-16八個皇后不相互攻擊的情況
現在來看如何使用回溯法解決八皇后問題。這個演算法將在棋盤上一列一列地擺放皇后直到八個皇后在不相互攻擊的情況下都被擺放在棋盤上,演算法便終止。當一個新加入的皇后因為與已經存在的皇后之間相互攻擊而不能被擺在棋盤上時,演算法便發生回溯。一旦發生這種情況,就試圖把最後放在棋盤上的皇后移動到其他地方。這樣做是為了讓新加入的皇后能夠在不與其它皇后相互攻擊的情況下被擺放在棋盤的適當位置上。例6-17所示的情況,儘管第7個皇后不會與已經放在棋盤上的任何一皇后放生攻擊,但仍然需要將它移除並發生回溯,因為無法為第8個皇后在棋盤上找到合適的位置。
圖6-17 需要發生回溯的情況
演算法的回溯部分將嘗試移動第7個皇后到第7列的另外一點來為第8個皇后在第8列尋找一個合適的位置。如果第7個皇后由於在第7列找不到合適的位置而無法被移動,那麼演算法就必須去掉它然後回溯到第6列的皇后。最終演算法不斷重複著擺放皇后和回溯的過程直到找到問題的解為止。
下面給出了求解八皇后問題的樣本程式。
#include <conio.h>
#include <iostream>
using namespace std;
// 首先 要求皇后不衝突,那麼每行只應該有一個皇后
// 用queens[]數組在儲存每個皇后的位置
// 例如: queens[m] = n 表示 第m行的皇后放在第n列上
#define MAX 8
int sum = 0;
class QueenPuzzle
{
int queens[MAX]; // 儲存每行皇后的列標
public:
void printOut(); // 列印結果
int IsValid(int n); //判斷第n個皇后放上去之後,是否合法
void placeQueen(int i); // 遞迴演算法 放置皇后
};
void QueenPuzzle::printOut()
{
for(int i=0; i<MAX; i++)
{
for(int j=0; j<MAX; j++)
{
if(j == queens[i])
cout << "Q ";
else
cout << "0 ";
}
cout << endl;
}
cout << endl << "按q鍵盤退出,按其他鍵繼續" << endl << endl;
if(getch() == 'q')
exit(0);
}
// 在第i行放置皇后
void QueenPuzzle::placeQueen(int i)
{
for(int j=0; j<MAX; j++)
{
// 如果全部放完了 輸出結果
if(i == MAX)
{
sum ++;
cout << "第" << sum << "組解:" << endl;
printOut();
return;
}
// 放置皇后
queens[i] = j;
// 此位置不能放皇后 繼續實驗下一位置
if(IsValid(i))
placeQueen(i+1);
}
}
//判斷第n個皇后放上去之後,是否合法,即是否無衝突
int QueenPuzzle::IsValid(int n)
{
//將第n個皇后的位置依次於前面n-1個皇后的位置比較。
for(int i = 0 ; i < n ; i++)
{
//兩個皇后在同一列上,返回0
if(queens[i] == queens[n])
return 0;
//兩個皇后在同一對角線上,返回0
if(abs(queens[i] - queens[n]) == (n - i))
return 0;
}
//沒有衝突,返回1。
return 1;
}
void main()
{
QueenPuzzle queen;
queen.placeQueen(0);
cout << "共" << sum << "組解" << endl;
}
由於回溯法也是在試圖搜尋整個解空間中的所有可能的選擇,於是有人會誤認為回溯法與窮舉法差不多,但事實上回溯法要較窮舉法在效率上高出很多。這裡就以一種簡單的估算方法來對八皇后問題進行一下分析。首先採用窮舉法,那麼可以容易得出該問題的解空間的結點總數為109601。然後在使用回溯法的前提下隨機選取20條路徑,分別估計它們的結點個數並求得總數的平均值。因為已知八皇后問題共有92種解,所以選擇20種隨機路徑進行估計所得出的結果已經可以較為接近實際數值。經過計算得出回溯法產生的結點數的平均值約為1740,這相對於109601,不足2%,可見回溯法作為一種跳躍性和系統性相結合的搜尋方法是具有較高效率的。
以上內容節選自《C++資料結構原理與經典問題求解》第6.3.3小節,電子工業出版社出版。
第6章 遞迴
6.1 遞迴的概念
6.1.1 定義
6.1.2 應用遞迴的原則
6.1.3 遞迴和非遞迴的轉化
6.2 分治法
6.2.1 分治法簡述
6.2.2 漢諾塔問題
6.2.3 傳染病問題
6.3 回溯法
6.3.1 回溯法簡述
6.3.2 迷宮問題
6.3.3 八皇后問題
6.3.4 騎士周遊問題
6.4 本章小結
=======================================
如果你想同我交流,歡迎點選連結http://student.csdn.net/invite.php?u=113322&c=a139a65a1494291d和我成為好友!