1. 程式人生 > >回溯法解決n後問題

回溯法解決n後問題

參考教材:演算法設計與分析(第3版) 王曉東 編著 清華大學出版社

問題的解空間

用回溯法解問題時,明確定義問題的解空間。問題的解空間至少應包含問題的一個(最優)解。
定義了問題的解空間後,還應將解空間很好地組織起來,使得能用回溯法方便地搜尋整個解空間。通常將解空間組織成樹或圖的形式。

回溯法的基本思想

確定瞭解空間的組織結構後,回溯法從開始節點(根節點)出發,以深度優先方式搜尋整個解空間。這個開始節點成為活節點,同時也成為當前的擴充套件節點。在當前擴充套件節點處,搜尋向縱深方向移至一個新節點。這個新節點成為新的活節點,併成為當前擴充套件節點。如果在當前擴充套件節處不能再向縱深方向移動,則當前擴充套件節點就成為死節點。此時,應往回移動(回溯)至最近的活節點處,並使這個活節點成為當前擴充套件節點。回溯法以這種工作方式遞迴地在解空間中搜索,直到找到所要求的解或解空間中已無活節點時為止。

剪枝函式

回溯法搜尋解空間樹時,通常採用兩種策略避免無效搜尋,提高搜尋效率。其一是用語數函式在擴充套件節點處剪去不滿足約束的子樹;其二是用限界函式剪去得不到最優解的子樹。這兩類函式統稱為剪枝函式。

回溯法解題的3個步驟

  1. 針對所給問題,定義問題的解空間。
  2. 確定易於搜尋的解空間結構。
  3. 以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋。

n後問題

在n×n格的棋盤上放置n個皇后,任何2個皇后不放在同一行或同一列或同一斜線上。

演算法

  1. 遞歸回溯

程式碼:

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)); } }
  1. 非遞迴迭代回溯

程式碼:

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