提出問題
八皇后問題:在8*8格的國際象棋上擺放8個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。
如何解決這個問題?一種常用且有效的方法是回溯法,是用樹形結構描述問題的求解過程。
什麼是回溯法
回溯法是一類非常重要的算法設計方法,有“通用解題法”之稱。
回溯法(探索與回溯法):一種選優搜索法,又稱試探法。利用試探性的方法,在包含問題所有解的解空間樹中,將可能的結果搜索一遍,從而獲得滿足條件的解。搜索過程採用深度遍歷策略,並隨時判定結點是否滿足條件要求,滿足要求就繼續向下搜索,若不滿足要求則回溯到上一層,這種解決問題的方法稱爲回溯法。
回溯法解求解問題步驟
- 針對給定問題,定義問題的解空間樹
- 確定易於搜索的解空間結構
- 以深度優先方式搜索解空間,並且在搜索過程中永剪枝函數避免無效搜索。
用回溯法求解問題,重點是設計問題的解空間樹,其解題過程則是深度遍歷解空間樹的過程。
解空間樹:是依據待求解問題的特性,用樹結構表示問題的解結構、用葉子表示問題所有可能的解的一棵樹。
解空間樹的形成過程:我們可以把求解問題的過程當作一系列的決定來考慮,回溯法對每一個決定都系統地分析所有可能的結果。而每一次決定即爲解空間樹中的一個分支結點,各種可能的結果便形成了各棵不同的子樹,問題最終所有可能的解將展現在所有的葉子上。這便是解空間樹的形成過程。
對解空間樹的遍歷可搜索到問題所有可能的解,因此,可獲得滿足要求的全部解,也可通過對所有解的比較選擇獲得最優解。
由於空間問題,下面給出一個三皇后問題的解空間樹(3皇后問題無解),樹中第i層的結點決定第i行皇后的擺放位置,均有三種不同的選擇,便形成了三個孩子結點,但其中不包括不符合要求的佈局。N皇后問題解空間樹與三皇后問題解空間樹類似。
求解N皇后問題的回溯法
N皇后問題要求求解在N*N的棋盤上放置N個皇后,並使各皇后彼此不受攻擊的所有可能的棋盤佈局。皇后彼此不受攻擊的約束條件是:任何兩個皇后均不能在棋盤上同一行、同一列或者同一對角線上出現。
由於N皇后問題不允許兩個皇后在同一行,所以,可用一維數組X表示N皇后問題的解,X[i]表示第i行的皇后所在的列號。例如一個滿足要求的四皇后棋盤佈局如下圖所示,其結果X數組的值爲:[2, 4, 1, 3]。
由上述X數組求解N皇后問題,保障了任意兩個皇后不在同一行上,而判定皇后彼此不受攻擊的其他條件,可以描述如下:
(1)X[i] = X[s],則第i行與第s行皇后在同一列上。
(2)如果第i行的皇后在第j列,第s行皇后在第t列,即X[i] = j和X[s] = t,則只要i-j = s-t或者i+j = s+t,說明兩個皇后在同一對角線上。
對兩個等式進行變換後,得到結論:只要|i-s| = |j-t|(即i-s = X[i]-X[s]),則皇后在同一對角線上。
解N皇后問題需要遍歷解空間樹,遍歷中要隨時判定當前結點棋盤佈局是否符合要求,符合要求則繼續向下遍歷,直至判斷得到一個滿足約束條件的葉子結點,從而獲得一個滿足要求的棋盤佈局;不符合要求的結點將被捨棄(稱之爲剪枝),並回溯到上一層的結點繼續遍歷。當整棵樹遍歷結束時,已獲得所有滿足要求的棋盤佈局。
綜上所述,用回溯法遞歸遍歷解空間樹,求解N皇后問題的算法如下(Java)。
/*
* 求解N皇后問題的回溯算法
* 棋盤爲n*n,函數從第index行起求解皇后的佈局,本函數初始調用爲tria(1, n)
*/
public void tria(int index, int n) {
if(index > n) {
// TODO 輸出X數組(也可以改寫算法,比如記錄滿足約束的棋盤個數)
} else {
//依次生成各孩子結點
for(int j = 1; j <= n; j++) {
//第index行的皇后放入第j列
X[index] = j;
if(place(index)) {
tria(i+1, n);
//結點滿足約束條件,則遞歸進入下一層繼續遍歷,否則跳過
}
}
}
}
/*
* 皇后位置滿足約束條件的判定函數
*/
private boolean place(int s) {
//判定s行X[s]位置上的皇后,與1至s-1行上各皇后的位置是否滿足約束條件
for(int i = 1; i < s; i++) {
if((X[i] == X[s]) || (Math.abs(i-s) == Math.abs(X[i]-X[s]))) {
return false;
}
}
return true;
}
求解八皇后問題代碼實現
public class Main2 {
static int resultCount = 0;
private static boolean place(int[] arr, int s) {
for(int i = 0; i < s; i++) {
if((arr[i] == arr[s]) || (Math.abs(i-s) == Math.abs(arr[i]-arr[s]))) {
return false;
}
}
return true;
}
public static void tria(int[] arr, int i, int n) {
if(i >= n) {
++resultCount;
} else {
for(int j = 0; j < n; j++) {
arr[i] = j;
if(place(arr, i)) {
tria(arr, i+1, n);
}
}
}
}
public static void main(String[] args) {
int[] queen = new int[8];
tria(queen, 0, 8);
System.out.println(resultCount);
}
}