回溯法之N皇后问题

回溯法

有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。

回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。

思想方法

回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

剪枝函数:

用约束函数在扩展结点处剪去不满足约束的子树;
用限界函数剪去得不到最优解的子树。

特点

用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))或O(h(n)!)内存空间。

N皇后问题

问题描述

N皇后问题要求在一个NXN的棋盘上放上N个皇后,使得每一个皇后既攻击不到另外N-1个皇后,也不被另外N-1个皇后所攻击。

问题分析

按照国际象棋的规则,一个皇后可以攻击与之处在同一行或同一列或同一斜线上的其他任何棋子。因此,N皇后问题等于要求N个皇后中的任意两个不能被放在同一行或同一列或同一斜线上。

我们给N×N棋盘的行和列分别从左到右和从上到下编号为1,2,3…N,同时也给N个皇后也分别编号为1,2,3…N。由于要求不同的皇后不能放在同一行,不失广般性,可设皇后i只放在第i行。这样,N后问题的解可用N元组(x1,x2,x3…xN)来表示。其中xi表示皇后i所处的列的列号。

那么约束条件或者说是限界函数便为:
① xi≠xj(皇后i和j不在同一列上);
② xi−i≠xj−j(皇后i和j不在斜率为-l的同一条斜线上);
③ xi+i≠xj+j(皇后i和j不在斜率为+l的同一条斜线上);
④ 其中i、j=1,2,…,k,且i<>j。

代码及详细注释


/** * @ClassName_NQueen * @author_Stone6762 * @CreationTime_2016年11月23日 下午5:15:43 * @Description_ */
public class NQueen {

    private int QueenSize;//N皇后问题的规模
    private int[] QueenInfo;//一次方案中,存储每一个皇后的列号(假设皇后i只能放置在i行,因此只记录列号即可)

    @SuppressWarnings("unused")
    private NQueen() {}

    public NQueen(int size) throws Exception {
        if (size > 0) {
            this.QueenSize = size;
            this.QueenInfo = new int[size + 1];
        } else {
            throw new Exception("参数不能为负值!");
        }
    }

    /** * @Description:验证pos的位置是否能否放置: 不能同行,同列,同斜线 * @param pos放置的位置 * @return */
    private Boolean PlaceSafety(int pos) {
        //因为一行只放一个,因此只需要判断是否同列和同斜线即可
        for (int i = 1; i < pos; i++) {
            //同斜线即斜率相同或者相反
            if (Math.abs(pos - i) == Math.abs(QueenInfo[i] - QueenInfo[pos])
                    || (QueenInfo[i] == QueenInfo[pos])) {//同列,即某个数与当前数相同
                return false;
            }
        }
        return true;
    }

    /** * @Description: 利用迭代回溯解决n皇后问题 */
    public void ShowResult() {
        QueenInfo[1] = 0;
        int pos = 1;//记录当前判断到第几行了
        while (pos > 0) {//说明还有可以深度遍历的节点
            QueenInfo[pos] += 1;
            //找到该Pos行能够放置的地方:在没有超过边界的情况下,该位置不能放置,就尝试下一列看看能不能放置
            while (QueenInfo[pos] <= QueenSize && (!this.PlaceSafety(pos))) {
                QueenInfo[pos] += 1;
            }
            if (QueenInfo[pos] <= QueenSize) {//找到了可以放置的位置的情况下
                if (pos == QueenSize) {//如果此时已经深度编列到末尾了,即该此判断已经到末尾了,都满足条件
                    //则记录该方案,并打印
                    QueenInfo[0]++;
                    this.Print();
                    //在此方案可行的情况下,再判断改行的其他列是否可以放置,如果不可以,那么才会回溯
                    //因此,在方案可行的情况下,打印完毕,不用做任何操作
                } else {//如果还没有判断到最后一行,那么继续判断下一行
                    pos++;
                    QueenInfo[pos] = 0;
                }           
            } else {//找不到可以放置的地方,那么只能回溯
                pos--;
            }
        }
    }

    /** * @Description: 输出中间结果 */
    private void Print() {
        System.out.println("方案" + QueenInfo[0]);
        for (int i = 1; i <= QueenSize; i++) {
            for (int j = 1; j <= QueenSize; j++) {
                if (j == QueenInfo[i]) {
                    System.out.print("■");
                } else {
                    System.out.print("□");
                }
            }
            System.out.println();
        }
        System.out.println();
    }
}
    原文作者:回溯法
    原文地址: https://blog.csdn.net/u011446177/article/details/54561831
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞