龙空技术网

算法设计技巧之回溯法(Java实现N皇后问题)

小林软件工作室 128

前言:

当前你们对“回溯法算法复杂度”大致比较关怀,同学们都需要知道一些“回溯法算法复杂度”的相关知识。那么小编同时在网摘上网罗了一些有关“回溯法算法复杂度””的相关文章,希望各位老铁们能喜欢,姐妹们快快来学习一下吧!

一、基本概念

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

二、基本思想

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

三、解题步骤(1)针对给定问题,定义问题的解空间树:首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解;(2)确定结点的扩展搜索规则;(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。四、算法框架

(1)问题框架

设问题的解是一个n维向量(a1,a2,………,an),约束条件是ai(i=1,2,3,…..,n)之间满足某种条件,记为f(ai)。

(2)非递归回溯框架

 int a[n],i; 初始化数组a[]; i = 1; while(i>0(有路可走)&&(未达到目标)){  //还未回溯到头    if(i > n){//搜索到叶结点        搜索到一个解,输出;    }else { //处理第i个元素        a[i]第一个可能的值;        while(a[i]在不满足约束条件且在搜索空间内) {            a[i]下一个可能的值;        }        if(a[i]在搜索空间内){            标识占用的资源;            i = i+1; //扩展下一个结点         }else {            清理所占的状态空间 //回溯            i = i–1;         }    }}

(3)递归的算法框架

回溯法是对解空间的深度优先搜索,在一般情况下使用递归函数来实现回溯法比较简单,其中i为搜索的深度,框架如下:

int a[n];try(int i){   if(i>n){}           输出结果;    }else {        for(j =下界; j <= 上界; j=j+1)   { //枚举i所有可能的路径             if(fun(j)){                //满足限界函数和约束条件                a[i] = j;                 ...                         //其他操作                try(i+1);                  回溯前的清理工作(如a[i]置空值等);            }          }    }  }
四、N皇后问题

八皇后问题:在8*8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

用回溯法求解问题,重点是设计问题的解空间树,其解题过程则是深度遍历解空间树的过程。

解空间树:是依据待求解问题的特性,用树结构表示问题的解结构、用叶子表示问题所有可能的解的一棵树。

解空间树的形成过程:我们可以把求解问题的过程当作一系列的决定来考虑,回溯法对每一个决定都系统地分析所有可能的结果。而每一次决定即为解空间树中的一个分支结点,各种可能的结果便形成了各棵不同的子树,问题最终所有可能的解将展现在所有的叶子上。这便是解空间树的形成过程。

对解空间树的遍历可搜索到问题所有可能的解,因此,可获得满足要求的全部解,也可通过对所有解的比较选择获得最优解。

由于空间问题,下面给出一个三皇后问题的解空间树(3皇后问题无解),树中第i层的结点决定第i行皇后的摆放位置,均有三种不同的选择,便形成了三个孩子结点,但其中不包括不符合要求的布局。N皇后问题解空间树与三皇后问题解空间树类似。

求解N皇后问题的回溯法

N皇后问题要求求解在N*N的棋盘上放置N个皇后,并使各皇后彼此不受攻击的所有可能的棋盘布局。皇后彼此不受攻击的约束条件是:任何两个皇后均不能在棋盘上同一行、同一列或者同一对角线上出现。

由于N皇后问题不允许两个皇后在同一行,所以,可用一维数组X表示N皇后问题的解,X[i]表示第i行的皇后所在的列号。例如一个满足要求的四皇后棋盘布局如下图所示,其结果X数组的值为:[2, 4, 1, 3]。

由上述X数组求解N皇后问题,保障了任意两个皇后不在同一行上,而判定皇后彼此不受攻击的其他条件,可以描述如下:

(1)X[i] = X[s],则第i行与第s行皇后在同一列上。

(2)如果第i行的皇后在第j列,第s行皇后在第t列,即X[i] = j和X[s] = t,则只要i-j = s-t或者i+j = s+t,说明两个皇后在同一对角线上。

对两个等式进行变换后,得到结论:只要|i-s| = |j-t|(即i-s = X[i]-X[s]),则皇后在同一对角线上。

解N皇后问题需要遍历解空间树,遍历中要随时判定当前结点棋盘布局是否符合要求,符合要求则继续向下遍历,直至判断得到一个满足约束条件的叶子结点,从而获得一个满足要求的棋盘布局;不符合要求的结点将被舍弃(称之为剪枝),并回溯到上一层的结点继续遍历。当整棵树遍历结束时,已获得所有满足要求的棋盘布局。

综上所述,用回溯法递归遍历解空间树,求解N皇后问题的算法如下(Java)。

public class NQueens {    public static List<List<String>> solveNQueens(int n){        //判断边界条件        if(n<=0)return null;        //新建一个特全局变量保存结果        List<List<String>> res = new ArrayList<>();        //保每一个皇后的位置的结果变量        int[] queen = new int[n];        //进行递归        backTrack(res,queen,0);        return res;    }    public static void backTrack(List<List<String>> res,int[] queen ,int pos) {        //这里就表示全部都找完了,第一个已经找到        if(pos==queen.length){            List<String> list = new ArrayList<>();            for(int i=0;i<queen.length;i++){                //sb表示每一行的符合条件的结果                StringBuilder sb = drawPoint(queen.length);                sb.setCharAt(queen[i],'Q');                list.add(sb.toString());            }            res.add(list);            return ;        }        for (int i = 0; i < queen.length; i++) {            //循环调用从第一行开始寻找符合条件的皇后的位置            queen[pos]=i;            //不满足条件,这一行的皇后向右移            if(isValid(queen, pos)) {                //1、第pos行的皇后已经确定了,现在递归的去找下一行的皇后                //2、若是找的到下一行则会继续递归的向下找,直到pos==queen.length则表明这个是满足条件的                //递归结束(成功或者失败)后则会返回【回溯】到这个for循环,这一行的皇后向右移                backTrack(res, queen, pos+1);            }        }    }    /**Math.abs(queen[pos]-queen[i])==Math.abs(i-pos)表示对角线      *  判定条件     * @param queen     * @param pos 当前行     * @return     */    public static boolean isValid(int[]queen,int pos){        for(int i = 0;i<pos;i++){            if(queen[pos]==queen[i]) return false;            if(Math.abs(queen[pos]-queen[i])==Math.abs(i-pos)) return false;        }        return true;    }    public static StringBuilder drawPoint(int n){        StringBuilder sb = new StringBuilder();        for(int i = 0 ;i<n ; i++) sb.append("□");        return sb;    }    public static void main(String[] args) {        List<List<String>> result = solveNQueens(8);        for (List<String> list : result) {            for (String str : list) {                System.out.println(str);            }            System.out.println();        }        System.out.println(result.size());    }}

将n定义为8,运行结果如下:

Q□□□□□□□□□□□Q□□□□□□□□□□Q□□□□□Q□□□□Q□□□□□□□□□□□Q□□Q□□□□□□□□□Q□□□□...□□□□□□□Q□□Q□□□□□Q□□□□□□□□□□□□Q□□□Q□□□□□□□□□□Q□□□□□□□□□Q□□□□Q□□□□□□□□□□□Q□□□Q□□□□Q□□□□□□□□□Q□□□□□□□□□□Q□□□Q□□□□□□□□□□□□Q□□□□□Q□□□92
时间复杂度

n皇后问题如果不进行任何剪枝,以最朴素的观点来看,其时间复杂度就是O(N^N) 。因为 N 行N 列,皇后的排列方式共有O(N^N)种。下面是一篇总结N皇后问题的复杂度的图,听说还有牛逼的构造算法是O(1).

标签: #回溯法算法复杂度 #回溯算法设计模式是什么 #n皇后高效算法详解 #java回溯法 #n皇后回溯算法的优化策略