回溯法解決n後問題
阿新 • • 發佈:2019-01-30
參考教材:演算法設計與分析(第3版) 王曉東 編著 清華大學出版社
問題的解空間
用回溯法解問題時,明確定義問題的解空間。問題的解空間至少應包含問題的一個(最優)解。
定義了問題的解空間後,還應將解空間很好地組織起來,使得能用回溯法方便地搜尋整個解空間。通常將解空間組織成樹或圖的形式。
回溯法的基本思想
確定瞭解空間的組織結構後,回溯法從開始節點(根節點)出發,以深度優先方式搜尋整個解空間。這個開始節點成為活節點,同時也成為當前的擴充套件節點。在當前擴充套件節點處,搜尋向縱深方向移至一個新節點。這個新節點成為新的活節點,併成為當前擴充套件節點。如果在當前擴充套件節處不能再向縱深方向移動,則當前擴充套件節點就成為死節點。此時,應往回移動(回溯)至最近的活節點處,並使這個活節點成為當前擴充套件節點。回溯法以這種工作方式遞迴地在解空間中搜索,直到找到所要求的解或解空間中已無活節點時為止。
剪枝函式
回溯法搜尋解空間樹時,通常採用兩種策略避免無效搜尋,提高搜尋效率。其一是用語數函式在擴充套件節點處剪去不滿足約束的子樹;其二是用限界函式剪去得不到最優解的子樹。這兩類函式統稱為剪枝函式。
回溯法解題的3個步驟
- 針對所給問題,定義問題的解空間。
- 確定易於搜尋的解空間結構。
- 以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋。
n後問題
在n×n格的棋盤上放置n個皇后,任何2個皇后不放在同一行或同一列或同一斜線上。
演算法
- 遞歸回溯
程式碼:
public class NQueen1 {
static int n; // 皇后個數
static int[] x; // 當前解
static long sum; // 當前找到的可行方案數
public static long nQueen(int nn) {
n = nn;
sum = 0;
x = new int[n + 1];
for (int i = 0; i <= n; i++)
x[i] = 0;
backtrack(1);
return sum;
}
private static boolean place(int k) {// 判斷皇后是否能放入k列
for (int j = 1; j < k; j++) { // 與前k-1個皇后的位置比較
if ((Math.abs(k - j) == Math.abs(x[j] - x[k])) || (x[j] == x[k])) // 同對角線或同列
return false;
}
return true;
}
private static void backtrack(int t) {
if (t > n) {
sum++;
for (int i = 1; i <= n; i++)
// 輸出當前方案
System.out.printf("%5d", x[i]);
System.out.println();
} else
for (int i = 1; i <= n; i++) {
x[t] = i; // 把第t個皇后依次放入n個格子,看是否可行
if (place(t)) // 可行就繼續放第t+1個皇后
backtrack(t + 1);
}
}
// 測試
public static void main(String[] args) {
System.out.println("5皇后問題方案可行數為:" + nQueen(5));
}
}
- 非遞迴迭代回溯
程式碼:
public class NQueen2 {// n後問題的遞歸回溯演算法
static int n; // 皇后個數
static int[] x; // 當前解
static long sum; // 當前找到的可行方案數
public static long nQueen(int nn) {
n = nn;
sum = 0;
x = new int[n + 1];
for (int i = 0; i <= n; i++)
x[i] = 0;
backtrack();
return sum;
}
private static boolean place(int k) {
for (int j = 1; j < k; j++) {
if ((Math.abs(k - j) == Math.abs(x[j] - x[k])) || (x[j] == x[k]))
return false;
}
return true;
}
private static void backtrack() {
x[1] = 0;
int k = 1;
while (k > 0) {
x[k] += 1;
while ((x[k] <= n) && !(place(k)))
x[k] += 1;
if (x[k] <= n) {
if (k == n) {
sum++;
for (int i = 1; i <= n; i++)
// 輸出當前方案
System.out.printf("%5d", x[i]);
System.out.println();
} else {
k++;
x[k] = 0;
}
} else
k--;
}
}
// 測試
public static void main(String[] args) {
System.out.println("5皇后問題方案可行數為:" + nQueen(5));
}
}
輸出結果(兩種演算法肯定是一樣的):
1 3 5 2 4
1 4 2 5 3
2 4 1 3 5
2 5 3 1 4
3 1 4 2 5
3 5 2 4 1
4 1 3 5 2
4 2 5 3 1
5 2 4 1 3
5 3 1 4 2
5皇后問題方案可行數為:10