n皇后詳解及代碼實現/C++

來源:互聯網
上載者:User

初衷

這個學期開了演算法課,要幾個關鍵演算法思想的代碼實現。當時感覺學的還可以了,也做了認真的筆記。真正寫代碼的時候發現還是

沒有完全掌握。網上關於這方面的資料也零零散散不是很全,致使走了不少彎路。今晚上實驗成功驗收了,感覺自己也收穫不小

遂決定把演算法實現的詳細思路記錄下來,一是自己坐下總結、另外也希望給當時想我一樣找資料、搞演算法的同學一些協助。

這中間我會盡最大可能的把問題描述清楚。

這篇博文主要寫的是n皇后問題、後續還會加上背包問題(動態規劃和分支界限)、旅行商問題等等。

 

寫在前面

不管什麼問題、都是可以抽象的,對於任何問題,你總是可以找到幾個point,它們對問題全域有著決定性的作用,弄清楚看他們之間的內在聯絡;

還有一個重要的方式就是找特例:對於一個無從下手的問題,可以舉幾個例子,找特例。通過幾個關鍵的point和特例,你就能容易的找出隱藏在問

題背後的實質。

在這裡我會給出這個問題的分析過程、而不會用成熟的理論來說明問題。如果你只想要代碼的話也可以直接copy。

 

問題描述:

一個n*n的棋盤,要在上面放n個皇后。規則:兩個皇后之間如果是同列、同行、同對角線它們會互相攻擊。也就是說:棋盤上的任意兩個皇后皇后

不能為同列、同行、同對角線。

 

問題分析

對於這個問題、當n不大的時候,可以用窮舉法實現。對於n皇后,每一行有n個位置可以放,一共n行。就會有n的n次方種情況。對於這些情況、再一一判斷是不是滿足情況。

其實一個關鍵的點在於:什麼時候判斷已經放了皇后的棋盤是否滿足條件,大致可以分為兩種:

1、等棋盤上放了n個皇后以後判斷

2、放一個皇后判斷一次、對於特定的某一次,如果這種情況不滿足條件,那麼以這種情況為基礎而產生的情況就不用再判斷了,他們都不會滿足條件。

比如說:頭兩行有衝突,那麼後面的不管怎麼放,都沒有意義了,總會有衝突。

如:

 

 

說明:這是四皇后的圖解,樹的每一層對應棋盤的一層,樹的邊上的數字代表放在棋盤的位置,如:邊上的數字是3,則代表下一行的皇后位置

是第三行,一次類推。可以看出,第一行有四個孩子,這是因為棋盤上還沒有放任何皇后,所以第一行的每個位置都可以放而不會發生衝突。

第二行就只有三個孩子因為這個時候第一行已經有皇后,要保證不和第一樣的發生衝突,選擇的餘地就變小了。一下情況一次類推。

如果考慮全部情況的話,應該每個節點都有四個孩子,然後再來判斷最終情況是否滿足情況,這會坐更多次數的判斷,導致低效率。

 

上面兩種判斷法可以導致完全不同的演算法效率:

方法一判斷了每一種情況,其實就是窮舉法。方法二隻判斷了一部分情況,對於那些沒有判斷的情況,是因為我們已經知道他們不可能成為解了,所以就

沒有判斷的必要,可以節省大量的時間開支。

(用稍微專業些的話就是在深度優先遍曆解空間的時候每一步都判斷目前狀態是否滿足條件,如不滿足就沒有就不再繼續往下遍曆,而是回溯)

 

所以思路可以是這樣:在第一行放一個皇后(可以是任意位置),然後在第二行找一個可行的位置放置,在這個基礎上在第三行找一個沒有衝突的位置,如果

發現某一行沒有地方可放了,那麼修改它的上一行,(找到另外一個沒有衝突的位置),然後在繼續遍曆。(回溯)

下面得考慮用什麼樣的資料結構來儲存結果:

當然,你可以用二維數組來存放,相當於類比了個棋盤,有皇后的位置存1,沒有皇后的位置存0,

多數的做法是用一個數組來存放,(一維數組),下文會有提到。

 

演算法實現

有了上面的分析後,再給出演算法應該就能比較好的理解了

可以用遞迴和非遞迴來求解 :

非遞迴演算法:

NQueens1(int n)
{ int k, n; extern int x[n]; k=0; x[k]=-1;
while(k>=0)
{ x[k]=x[k]+1;
while((x[k]<n) && (!Place(k)) x[k]=x[k]+1;  //如果當前列不滿足情況,則判斷下一列
if(x[k]<n)         //如果是上文while中的第二個條件不滿足而退出while迴圈,也就是說找到了可以放置的位置
{ if(k<n-1) { k=k+1; x[k]=-1; }    //判斷當前行是不是最後一行,如果是最後一行則表明已經找到結果,列印結果
else printf(x[0:n-1]);
}
else k=k-1;      //上文中while因為第一個條件不滿足而退出while迴圈,在這行裡沒有滿足條件的列,那麼退回上一行重新選擇滿足
                  //條件的的列(回溯)
}
}

代碼說明:K表示行數,數組元素X[K]的值表示第K行的皇后位置,比如X[3]=2,表示第3行皇后的位置是2. 

place()是判斷當前位置是否滿足條件的函數,如果滿足條件返回真,不滿足為假。從迴圈可知,如果當前位置不可行,則判斷下一列:X[k]=X[k]+1,由於遞迴和非遞迴都會用到這個函數,將在下面提到。

 有人可能注意到X[k]的初始值為-1,是因為對於每一行程式都要從第一個位置(第一列)判斷,如果不滿足再往後,對於每一列的處理

是從第一個while迴圈裡開始的,裡面的X[k]=X[k]+1,使得第一次判斷的是第一列。

遞迴演算法:

NQueens2(int k)
{ extern int x[n];
x[k]=-1;
while(1)
{ x[k]=x[k]+1;
while((x[k]<n)&&(!Place(k)) x[k]=x[k]+1;
if(x[k]<n)
{ if(k<n-1) NQueens2(k+1);      //如果還沒找到最終解,則遞迴調用演算法,判斷下一行(K+1)
else printf(x[0:n-1]);
}
else return;
}
}

 

對於兩個函數中都用到的place()函數:

int Place(int k) 
{ int i; extern int x[n];
for(i=0; i<k-1; i++)
if( (x[i]==x[k-1]) || (abs(x[i]-x[k-1])==abs(i-k+1)) )
return false=0;
return true=1;
}

傳入參數是K(行數),還引入數組X[n],這樣就能知道從第一行到第k行的皇后位置,(上文中說說x[k]這個數組是存放皇后位置的)

要判斷第k行的x[k]列是否滿足條件,只要用k前的每一行來和它相比較,只要有其中的一行不滿足條件,就返回假。

(x[i]==x[k-1])表示他們在同一列,(abs(x[i]-x[k-1])==abs(i-k+1)表示他們在同一對角線


演算法實現代碼 (遞迴實現)

 

#include<fstream>
#include<iostream>

std::ofstream fout("queenoo.txt");

/**//* 記錄當前的放置方案 */
int *x;
/**//* 皇后的個數N 和 方案數目 */
int n,sum=0;
/**//* 檢查參數所指示的這一行皇后放置方案是否滿足要求 */
int Place(int);
/**//* 遞迴方法求取皇后放置方案*/
void Queen1(void);
/**//* 使用者遞迴求取皇后放置方案的遞迴方法 */
void TraceBack(int);
/**//* 列印當前成功的放置方案 */
void PrintMethod(void);

int main()
{
using namespace std;
long start,stop;
cout<<"input n 輸入皇后個數 :";
cin>>n;

x=(int *)malloc(sizeof(int)*n);
time(&start);/**//*記錄開始時間*/
Queen1();
time(&stop);/**//*記錄結束時間*/
cout<<"一共的方案數為:"<<sum<<"\n";
cout<<"共花時間:"<<(int(stop-start))<<"\n";
fout<< "一共的方案數為:"<<sum<<"\n";
fout<< cout<<"共花時間:"<<((stop-start))<<"\n";

}

int Place(int r)
{
int i;
for(i=0;i<r;i++){
if(x[r]==x[i] || abs(r-i)==abs(x[r]-x[i]))
return 0;
}
return 1;
}

void TraceBack(int r)
{
int i;
if(r>=n){
PrintMethod();
sum++;
/**//* PrintMethod(); */
}else{
for(i=0;i<n;i++){
x[r]=i;
if(Place(r)) TraceBack(r+1);
}
}
}

void PrintMethod(void)
{
int i,j;
std::cout<<"第"<<sum<<"個方案\n";
fout<<"第"<<sum<<"個方案\n";
for(i=0;i<n;i++){
for(j=0;j<n;j++){
if(j==x[i]) std::cout<<"1",fout<<"1";
else std::cout<<"0",fout<<"0";
}
std::cout<<"\n";
fout<<"\n";
}
}

void Queen1(void)
{
TraceBack(0);
}

以上代碼cfree5編譯通過。

代碼把實驗結果輸入到檔案“queenoo.txt”中,路徑就在編譯器安裝路徑中,和執行代碼位於同一目錄。

非遞迴代碼類似,就沒寫出來


後記

解決八皇后問題用到的是一種稱為回溯的方法,在上面的分析過程中就體現了這種思想,在深度優先遍曆解空間的時候加上判斷

條件,只有合格,才繼續往下遍曆。任何一種理論的產生都是先有實踐環節的,只有在實踐正確的情況下,才能總結出理論

而很多演算法書都是現給出裡論,再給執行個體,其實這不是一種好的學習方式,這中間學習者少了許多思考、推導的過程,而這過程恰恰

就是理論的雛形,忽視了這個關鍵的作用,只是被動的的接受演算法思想而不知道演算法是怎樣產生的,不知其所以然,這樣學起來自然就

不透徹而且很難掌握。

所以在講皇后問題的時候,並沒有開篇就講什麼是回溯法,回溯法的細想,如何運用回溯法等等。而是用原始的思維去思考解決問題之道,

等接觸回溯法時才恍然,啊、、這就是回溯法。那時候對於理論的學習就能加深對演算法思想的理解,自己在加以思考分析,就能比較好的

掌握演算法。


 

以上博文如有錯誤還望各位神仙大牛指正,在下感激不盡


關於上文中提到的回溯法,會在下一篇博文中有詳細說明,歡迎關注


如有轉載請註明出處:http://www.cnblogs.com/yanlingyin/

一條魚@部落格園 2011-12-19

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.