回溯法與N皇后問題JAVA實現

提出問題

八皇后問題:在8*8格的國際象棋上擺放8個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

如何解決這個問題?一種常用且有效的方法是回溯法,是用樹形結構描述問題的求解過程。

什麼是回溯法

回溯法是一類非常重要的算法設計方法,有“通用解題法”之稱。

回溯法(探索與回溯法):一種選優搜索法,又稱試探法。利用試探性的方法,在包含問題所有解的解空間樹中,將可能的結果搜索一遍,從而獲得滿足條件的解。搜索過程採用深度遍歷策略,並隨時判定結點是否滿足條件要求,滿足要求就繼續向下搜索,若不滿足要求則回溯到上一層,這種解決問題的方法稱爲回溯法。

回溯法解求解問題步驟
  1. 針對給定問題,定義問題的解空間樹
  2. 確定易於搜索的解空間結構
  3. 以深度優先方式搜索解空間,並且在搜索過程中永剪枝函數避免無效搜索。

用回溯法求解問題,重點是設計問題的解空間樹,其解題過程則是深度遍歷解空間樹的過程

解空間樹:是依據待求解問題的特性,用樹結構表示問題的解結構、用葉子表示問題所有可能的解的一棵樹。

解空間樹的形成過程:我們可以把求解問題的過程當作一系列的決定來考慮,回溯法對每一個決定都系統地分析所有可能的結果。而每一次決定即爲解空間樹中的一個分支結點,各種可能的結果便形成了各棵不同的子樹,問題最終所有可能的解將展現在所有的葉子上。這便是解空間樹的形成過程。

對解空間樹的遍歷可搜索到問題所有可能的解,因此,可獲得滿足要求的全部解,也可通過對所有解的比較選擇獲得最優解。

由於空間問題,下面給出一個三皇后問題的解空間樹(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);
	}
}

運行結果


參考資料:《數據結構與算法》    王曙燕    主編    王春梅    副主編    人民郵電出版社


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章