遞迴之八皇后問題(回溯演算法)
技術標籤:演算法與資料結構java演算法資料結構遞迴演算法面試
一、問題描述
八皇后問題, 是一個古老而著名的問題, 是回溯演算法的典型案例。 該問題是國際西洋棋棋手馬克斯· 貝瑟爾於 1848 年提出,問題的具體描述為:在 8×8 格的國際象棋的棋盤上擺放八個皇后,使其不能互相攻擊(即任意兩個皇后都不能處於同一行、同一列或同一斜線上),問共有多少種擺法 ?
八皇后問題的答案是 92 種解法,那麼這 92 種解法是怎麼得來的呢?
二、解決思路
這個問題的解決思路可以分為以下幾個步驟:
- 第一個皇后先放第一行第一列;
- 第二個皇后放在第二行第一列、然後判斷是否 OK,如果不 OK,則嘗試放在第二列、第三列…,直到找到一個合適的位置;
- 繼續放置第三個皇后,還是第一列、第二列…,直到找到一個合適的位置;
- 依次迴圈下去,直到第 8 個皇后也能放在一個不衝突的位置,就算是找到了一個正確解;
- 當得到一個正確解時,在棧回退到上一個棧時,就會開始回溯。本次回溯之後,會即將第一個皇后放到第一列前提下的所有正確解全部得到;
- 然後回頭從 1 開始,第一個皇后放到第一行第二列,後面繼續迴圈執行 2、3、4 的步驟,直至第一個皇后將第一行的所有列都放置過。至此,便得到了八皇后問題的所有放置解法。
上面的思路其實是有個回溯的思想在裡面的,這個回溯的思想體現在第 5 步。即當第 8 個皇后正確放置之後便開始回溯,回溯的步驟是:首先把第 8 個皇后消去,第 7 個皇后更改自己當前所在位置,重新找到一個合適的位置,由此第 8 個皇后便可以找到一個新的位置,這便誕生出了一種新的解法;接著把第 8 個和第 7 個皇后消去,第 6 個皇后改變到一個合適的位置,再重新擺放第 7 個皇后和第 8 個皇后的位置,由此又可以誕生幾種新的解法。如此迴圈下去,直到第 2 個皇后遍歷完所有的合適的位置,這樣求得的結果就是第 1 個皇后在第一行第一列情況下的所有解法
上面的回溯思想可以求得第 1 個皇后在 第一行第一列情況下的所有解法。事實上,第 1 個皇后也可以擺放在第一行的任意一列,在這八列情況下的所有解法的累計,就是八皇后問題最終的解。
三、程式碼實現
理論上應該建立一個二維陣列來表示棋盤,但是實際上可以通過演算法, 用一個一維陣列即可表示棋盤。如:
int[8] arr[] = {0, 4, 7, 5, 2, 6, 1, 3}
對於這個一維陣列 arr
,它的下標表示第幾行,也就相當於第幾個皇后;下標對應的值則代表這個皇后所在的列。如 a[1] = 4
,代表第 2 個皇后在第二行第五列。
八皇后問題完整的程式碼實現如下:
public class No3_Recursion_EightQueens {
private static int max = 8; // 總共 8 個皇后
private static int[] arr = new int[8]; // 這個一維陣列用於存放8個皇后在棋盤的擺放位置,索引代表行,值代表列
private static int num = 0; // 計數器,用於記錄有多少種解法
public static void main(String[] args) {
// 從第 0 行,開始擺放
check(0);
System.out.printf("最終解法有:%d 種", num);
}
/**
* @Description 開始擺放皇后
* @Param [n] 當前準備擺放的皇后的索引值(也就是所在行數)
* @return void
*/
public static void check(int n){
// 遞迴首先是退出條件,也就是第 8 個皇后擺放完畢,即開始擺放第 9 個皇后了,就說明成功了
if (n == max){
num++; // 成功的次數 +1
showArr(); // 擺放完畢,就列印一下吧
return;
}
// 如果不符合退出條件
// 因為每一個皇后在每一行的擺放位置都有 8 個可能,所以需要遍歷 8 次
for (int i=0; i<max; i++){
arr[n] = i; // 擺放
// 判斷擺放是否合理
if (isReasonable(n)){
// 擺放合理的話,就開始擺放下一個
check(n+1);
}
}
}
/**
* @Description 判斷當前擺放的皇后是否和之前擺放的皇后有衝突
* @Param [n] 當前正在擺放的皇后的索引值
*/
public static boolean isReasonable(int n){
// 需要遍歷,也就是和前面的每一個皇后進行比對當前擺放位置是否合理
for (int i=0; i<n; i++){
// arr[i] == arr[n] 代表的是擺放在同一列
// Math.abs(n -i) == Math.abs(arr[n] - arr[i]) 代表的是擺放在對角線上
if (arr[i] == arr[n] || Math.abs(n -i) == Math.abs(arr[n] - arr[i])){
return false;
}
}
return true;
}
/**
* @Description 列印陣列
*/
public static void showArr(){
for (int i=0; i<max; i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
執行結果如下:
以最後一個解為例,即 arr = {7, 3, 0, 2, 5, 1, 6, 4}
,代表著第 1 個皇后在第一行的第八列、第 2 個皇后在第二行的第四列、第3 個皇后在第三行的第一列…,以此類推。