1. 程式人生 > >[工作準備--演算法] 八皇后問題--遞迴求解

[工作準備--演算法] 八皇后問題--遞迴求解

目標

在8*8的的國際象棋中擺上八個皇后 使其不能相互攻擊
即皇后不能在同列同行或者同直線

問題分析

  • 問題的解向量:(x0,x2,x3,….,x7)

    • 採用陣列的下標i 表示皇后的所在行
    • 採用陣列元素x[i]表示皇后的所在列向量
  • 約束條件

    • 顯示約束 (對解向量的直接約束) xi=1,2,3…n
    • 隱示約束1 :任意兩個皇后不同列 : x
      i =≠ x j
    • 隱示約束2 :兩個皇后不處於同一對角線 :
      | i j | |
      x i x j |

/**
     *遞迴呼叫函式  放置第t 行的皇后 
     * @param t
     */
    public void Backtrack(int t) {
        //遞迴終止條件  即第九行的皇后即結束(八皇后問題 注意陣列的下標從0開始)
        if (t>7) {
            //輸出放置皇后情況並且count++
            output(x);
        }
        else {
            //迴圈是遍歷在第一行上放置皇后
            for(int i=0;i<8;i++) {
                x[t] = i;
                //如果符合約束條件 就放置下一行的皇后
                if(Bound(t)) {
                    Backtrack(t+1);
                }
            } 
        }
    }
/**
 * 判斷 k行第 i列放置皇后 是否在約束條件中 
 * @param k 第幾行要放置皇后 
 * @return
 */
public  boolean Bound(int k ) {
        for(int i =0;i<k ;i++) {
            if((Math.abs(k-i)==Math.abs(x[k]-x[i]))||x[i]==x[k]) {
                return false;
            }
        }
        return true;
    }

最後的整體呼叫和類的實現程式碼如下(完整程式碼)

public class Queen {
    private int count =0;
    private int[] x = new int [8];
    public static void main(String [] args) {
        Queen q = new Queen();

        q.Backtrack(0);
        System.out.println(q.count);
    }
    /**
     * 判斷當前放置的 k = i 是否在約束條件中 
     * @param k 第幾行要放置皇后 
     * @return
     */
    public  boolean Bound(int k ) {
        for(int i =0;i<k ;i++) {
            if((Math.abs(k-i)==Math.abs(x[k]-x[i]))||x[i]==x[k]) {
                return false;
            }
        }
        return true;
    }

    /**
     *遞迴呼叫函式  放置第t 行的皇后 
     * @param t
     */
    public void Backtrack(int t) {
        //遞迴終止條件  即第九行的皇后即結束(八皇后問題 注意陣列的下標從0開始)
        if (t>7) {
            //輸出放置皇后情況並且count++
            output(x);
        }
        else {
            //迴圈是遍歷在第一行上放置皇后
            for(int i=0;i<8;i++) {
                x[t] = i;
                //如果符合約束條件 就放置下一行的皇后
                if(Bound(t)) {
                    Backtrack(t+1);
                }
            } 
        }
    }
    private void output(int []x) {
        count++;
        for(int x1 :x) {
            System.out.print(x1+" ");
        }
        System.out.println();
    }

}

思路總結

  1. 這裡使用了遞迴呼叫使得設計思想的時候更加簡便和易讀易懂了.但是遞迴呼叫在效率上因為要壓棧(jvm棧–>執行緒安全的)等一系列的操作,使得效率不高效,同時所有的遞迴呼叫都可以用迴圈完成,只是不容易思考的設計而已.
  2. 在使用遞迴呼叫時候一定要注意到遞迴結束標誌,如果沒有遞迴結束標誌就會一直遞迴下去
  3. 當遇到大量的資料時候最好不要使用遞迴呼叫,因為遞迴呼叫佔用jvm棧的空間,而棧的空間是有限的.需要合理的使用.即迴圈遞迴巢狀層數不宜太多.