1. 程式人生 > >N皇后問題-回溯與遞迴-C++實現

N皇后問題-回溯與遞迴-C++實現

問題描述:N皇后問題是一個古老而著名的問題,是回溯演算法的典型案例。該問題由西洋棋棋手馬克斯·貝瑟爾於1848年提出。在國際象棋上,N皇后問題變成了8皇后問題,著名的數學家高斯認為有76種方案,後來有人用圖論的知識解出92種結果,計算機發明後,可以通過演算法實現問題的求解。顯然,大數學家有時候也會敗在計算機面前。8皇后問題是指在8*8的棋盤上擺放8個皇后,使得任意兩個皇后都不在同一行、同一列或者同一斜線上,求滿足這種擺放的解為多少個。(答案是92種)

現在需要分析以下問題:

1.如何擺放才能不重複也不遺漏?

很顯然,我們可以模擬手工擺放:我們逐列放皇后(從小到大逐列擺放),現在先給第一列放皇后,很顯然我們會把它放在第一行,接下來,給第二列放皇后,那麼第二列的皇后能放在哪些位置?這時候需要一個判斷函式來判斷第二列的皇后能放在那裡。如果第二列找到放皇后的某一行,那麼就進行第三列的擺放,這裡就是遞迴

如果還沒有進行到最後一列的擺放時就已經不能再放皇后,那麼此時需要怎麼做?就返回遞迴的前面一次,把當前列的前面一列的皇后放在後面的行數上(從小到大逐行檢驗)。如果此時逐行檢驗已經遍歷完所有的行數還是出現上面不能擺放的情況,那就在再返回上一次的遞迴,在進行逐行檢驗。這裡就是回溯

為方便描述,我們定義一個一維陣列pos[i]表示皇后所在位置:設pos[i]=j,則表示第i列的皇后放在第j行上。

2.遞迴的終止條件是什麼?

由初始條件知道,第一行第一列放皇后,那麼會不會有第一行第一列不放皇后的情況呢?我們能不能把這個位置放空,然後把第一列的皇后放在第二行上,或者更後面的一行。答案是肯定能的。那麼問題來了,程式在執行到什麼時候會進行這樣的處理?當第一行第一列放皇后的所有情況

都已經遍歷完之後,就會把第一列的皇后放在第二行上,再把這個情況都遍歷完,然後又把第一列的皇后放在第三行上,依次類推,直到把第一列的皇后放在最後一行上,當遍歷完成時,遞迴終止。

以下為C++實現的程式碼:(已經除錯執行成功)

#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;

#define Max 20    //表示最大的棋盤邊長,可以自定義為其它資料
int pos[Max+1];   //為什麼只需要定義一個一維陣列就能描述二維的棋盤?
                  //pos[i]是這樣定義的:即第i列的皇后放在第pos[i]行上,
                  // 也就是說,pos[i]的索引i代表皇后所在的列,它的值pos[i]代表皇后所在的行
int n;          //棋盤的邊長和皇后的數量
int sum;         //可以成功擺放的數量,每次遍歷成功就加1

bool checkNQ(int col){      //對第col之前的列進行逐列檢查,pos[i]中i的值為列,pos[i]的值為行
	for(int i=1;i<col;i++)
		if(pos[i]==pos[col]||abs(i-col)==abs(pos[i]-pos[col]))
                 //如果行數相同,或者行數相減的絕對值等於列數相減的絕對值
                //此時都不能放皇后,因為對第col列之前的列進行逐列檢查,
		//所以不需要再進行列是否相同的判斷
			return false;
	return true;
}

void dfsNQ(int col,int n){
      if(col==n+1)   //成功遍歷一次,sum加1,然後繼續探索其他情況
      	sum++;
      for(int i=1;i<=n;i++){
      	pos[col]=i;//假設第col列的皇后放在第i行上,然後利用checkNQ()函式檢查是否能放入
      	            //第一種情況,如果能放入,則繼續假設下一列也放在第i行(實際上第i行此時已經不能放了,
      	             //所以cheakNQ()函式就會直接返回false,
      	            //然後上面的for迴圈中的i自動加1,即假設第col+1列放在第i+1行,然後又繼續檢查能否放入。
      	            //第二種情況,如果不能放入,for迴圈中的i就自動加1,即假設第col列的皇后放在第i+1行上,
      	            //又繼續檢查能否放入
      	            //如果當col<=n時(即列還沒有遍歷完)再也不能在任何一行放皇后,那麼此時dfsNQ中的for迴圈的i已經
      	             //遍歷完,dfsNQ就會返回到上一級的dfsNQ(col+1,n),此時col就會自動減1(因為每次遞迴都是加1),
      	             //然後,嘗試第col列的皇后能否放在第i+1行上,如此進行回溯。
      	if(checkNQ(col))
      		dfsNQ(col+1,n);  //進行遞迴
      }
}
 int main()
 {
 	cout<<"請輸入皇后的數量:";
 	cin>>n;
 	dfsNQ(1,n);  //傳入第一列和n,從第一列開始放皇后。
 	cout<<endl<<"滿足條件的所有擺放次數為:";
 	cout<<sum;
    return 0;    //說明:dfsNQ函式完全退出的條件是所有滿足條件的情況都已經遍歷過,再也沒有滿足條件的遍歷
                 //根據遞迴的初始化分析得知,前面的遍歷都會預設第一行第一列的位置會放皇后,
                 //然而實際情況是這個位置不放皇后也能滿足條件的次數甚至更多,那麼程式在執行到什麼時候會把
                 //第一行第一列的位置放空呢?答案是當第一行第一列放皇后的滿足條件的所有遍歷都結束時,
                //就會把第一列的皇后放在第二行,而把第一行第一列的位置放空。
                 //如此進行到最後,最後面是把第一列的皇后放在最後一行,
                 //然後再全部遍歷,結束時整個dfsNQ函式遞迴執行結束。主函式return 0.
 	}

筆者水平有限,不足之處希望各位讀者不吝賜教。