8皇后問題是:在8×8的棋盤上擺放8個皇后,使其不能互相攻擊,即任意的兩個皇后不能處在同一行,同一列,或同一斜線上。可以把八皇后問題拓展為n皇后問題,即在n×n的棋盤上擺放n個皇后,使其任意兩個皇后都不能處於同一行、同一列或同一斜線上。首先需要對棋盤進行描述。直觀地,棋盤可以用二維陣列表示,有皇后的棋格對應陣列元素值為1,無皇后的棋格對應陣列元素值為0。但這種儲存結構並不是最簡單有效的選擇。
圖8.21中左邊部分給棋盤的行、列編了號,提供的擺放方法,就是問題的乙個解。右邊的部分,將各行上皇后所在的列數記錄下來,用這8個數字(4, 6, 8, 2, 7, 1, 3, 5),也構成了對問題解的一種描述。
圖8.21 8皇后問題的乙個解
由此可以看出,可以定義乙個一維陣列int x[n];,用x[i]的值表示第i行上皇后所在的列數,n皇后問題的解可以用(x[1], x[2], ….. x[n])的形式描述。
解決了資料表示的問題,設計資料處理的方法。這裡要用回溯的策略,設計計算機對n皇后問題的求解方法。以4皇后為例,如圖8.22所示,在圖8.22(a)中,第1行第1列上放置乙個皇后,圖8.22(b)中確定第2行的可能放法,在嘗試第1列、第2列由於相互攻擊而放棄之後,確定在第3列放置可以繼續,在圖8.22(c)中繼續對第3行進行考察,發現將所有4列都嘗試過了,也沒有辦法將皇后安排乙個合適的位置,對第4行做任何的嘗試都沒有意義,這時產生回溯,結果是在圖8.22(d)中將第2行的皇后安排到第4列,然後第3行的暫時可以放在第2列,在圖8.22(e)中試著確定第4行的皇后,卻發現無解再次回溯,只能夠如圖8.22(f)所示將第1行的皇后放到第2列,再經圖8.22(g)、(f)之後找到4皇后問題的乙個解,那就是圖8.22(g)的(2, 4, 1, 3)。
圖8.22 用回溯找出4皇后問題乙個解的過程
在圖8.23中,給出了求出4皇后問題所有解的完整過程的描述。圖中(1 * * *)對應圖8.22(a)中第1行皇后安排在第1列,其他行待定的狀態,接下來的(1 3 * *)對應了圖8.22(b)中第2行皇后安排在第3列的狀態。可以判斷出在這個狀態下,繼續嘗試並不能夠完成求解,於是發生回溯(其下方的b代表回溯),於是下乙個嘗試的狀態將是(1 4 * *),……。將這樣的過程繼續下去,能夠找出4皇后問題的所有解(2 4 1 3)和(3 1 4 2),如圖8.23中兩個加網格背景的結點。
圖8.23 求出4皇后問題所有解的完整過程
搞清楚用回溯法求解的過程後,將關注如何基於(x[1], x[2], ….. x[n])形式的解結構,寫出讓計算機完成求解過程的**。4皇后問題尚且可以在紙上畫出解,8皇后問題的可能解有8!=40320種,最終解有92種,必須要依靠計算機求解了。
什麼樣的解才是可行的?需要描述出任何兩個皇后可以「互相攻擊」這樣的條件:
(1)有兩個皇后處在同一行:解的結構(x[1], x[2], ….. x[n])已經保證同一行不會出現兩個皇后。
(2)有兩個皇后處在同一列:表示為x[i]=x[k],假如在圖8.23**現表示為(1 1 * *)、(4 2 3 2)之類的結點,則說明有兩個皇后在同一列了。
(3)有兩個皇后處在同一斜線:若兩個皇后的擺放位置分別是第i行第x[i]列、第k行第x[k]列,若他們在棋盤上斜率為-1的斜線上,滿足條件i-x[i]=k-x[k],例如(1 4 3 *)、(4 1 2 *);若他們在棋盤上斜率為1的斜線上,滿足條件i+x[i]=k+x[k]。將這兩個式子分別變換成i-k=x[i]-x[k]和i-k=x[k]-x[i],例如(3 4 1 *)。綜合兩種情況,兩個皇后位於同一斜線上表示為|i-k|=|x[i]-x[k]|。
在下面的程式實現中,place(x, k)函式用於判斷在第k行第x[k]列放置皇后,是否會與前面擺放好的皇后產生相互攻擊。只要有某行(第i行)的皇后與這個第k行的皇后處在同一列(x[i]=x[k])或者處在同一斜線(|i-k|=|x[i]-x[k]|),則立即返回假(0),表示不可能構成解。
再接下來,就是在實現問題求解的nqueens(x, n)函式中,從第1行開始,逐行逐列地考察皇后的擺放,當遇到某一行所有可能情況試過不必再深入到下一行考察時,及時回溯到上一行,接著考察。
程式實現中,將儲存解的陣列定義成了動態陣列。多分配乙個單元,因為陣列的首元素x[0]一直空閒未用,有用的單元是x[1]到x[n]。
【例8.12】 求解8皇后問題的程式
#include
#include
#include
void nqueens(int *x, int n); /*求解n皇后問題*/
int place(int *x, int k); /*判斷是否可以在第k行第x[k]列擺放皇后*/
void printsolution(int *x, int n); /*輸出求解結果*/
int main()
/*如果乙個皇后能放在第k行第x[k]列,則返回真(1),否則返回假(0)*/
int place(int *x, int k)
/*能執行下一句,說明在第k行第x[k]列擺放皇后,不會互相攻擊*/
return1;}
/*求解在n×n的棋盤上,放置n個皇后,使其不能互相攻擊*/
void nqueens(int *x, int n)
}else
/*對應x[k]>n的情形,這一行已經沒有再試的必要,回溯到上一行*/
k--; /*上一行在原第x[k]列的下1列開始考察*/
}}/*輸出求解結果*/
void printsolution(int *x, int n)
printf("\n");
}printf("\n");
}
【思考題】請從解題策略和程式中,找出何處使用了棧,是如何將棧應用於回溯過程的? 資料結構 棧應用
一 算術表示式的中綴表示 把運算子放在參與運算的兩個運算元中間的算術表示式稱為中綴表示式。例如 2 3 4 6 9 算術表示式中包含了算術運算子和算術量 常量 變數 函式 而運算子之間又存在著優先順序,不能簡單地進行從左到右運算,編譯程式在求值時,不能簡單從左到右運算,必須先算運算級別高的,再算運算...
資料結構 棧的應用
要求 首先將運算元棧opnd設為空棧,而將 作為運算子棧opter的棧底元素,這樣的目的是判斷表示式是否求值完畢 2 依次讀入表示式的每個字元,表示式須以 結尾,若是運算元則入棧opnd,若是運算子,則將此運算子c與opter的棧頂元素top比較優先順序後執行相應的操作,具體操作如下 i 若top的...
資料結構 棧的應用
棧是限制插入和刪除只能在乙個位置上進行的表,該位置是表的末端,也叫做棧頂。對棧的操作有進棧和出棧,進棧也叫做插入,出棧也就是刪除最後插入的元素。因此棧也被稱作lifo 後進先出 表。棧通常有兩種實現方式,陣列實現和鍊錶實現。下面是棧的兩個小應用demo 字串逆序 由於棧後進先出的特性,所以棧可以用於...