回溯算法
回溯算法
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
来源
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。
用回溯算法解决问题的一般步骤:
1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
2 、确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
3 、以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
基本思想
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。八皇后问题就是回溯算法的典型,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。回溯在迷宫搜索中使用很常见,就是这条路走不通,然后返回前一个路口,继续下一条路。回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
迷宫回溯问题
public class MiGong { public static void main(String[] args) { /*创建二维数组模拟迷宫 * 地图*/ int[][] map =new int[8][7]; /*1:墙2: * 上下置为1*/ for (int i = 0; i < 7; i++) { map[0][i] = 1; map[7][i] = 1; } /*左右置为1*/ for (int i = 0; i <8 ; i++) { map[i][0]=1; map[i][6]=1; } map[3][1]=1; map[3][2]=1; //输出地图 System.out.println("地图情况====="); for (int i = 0; i <8 ; i++) { for (int j = 0; j <7 ; j++) { System.out.print(map[i][j]+" "); } System.out.println(); } //使用递归回溯给小球找路 setWay(map,1,1); //输出新地图,小球走过并标识的路 System.out.println("小球走过并标识的路====="); for (int i = 0; i <8 ; i++) { for (int j = 0; j <7 ; j++) { System.out.print(map[i][j]+" "); } System.out.println(); } } /*使用递归回溯来给小球找路 * 1.起始位置:(1,1) * 如果找到map[6][5],说明通路找到 * 2.约定:0:该点没有找过1:表示墙,2:通路可以走3:该点已经走过但是不能走通 断路不能走 * 3.策略:行进方向()下--》右--》上--》左,走不通进行回溯*/ /** * * @description:使用递归回溯来给小球找路 * @params:1:地图 起始位置 2:行3:列 * @return: 找到通路为真,否则为假 * @author: 苏兴旺 * @time: 2020/3/12 11:54 */ public static boolean setWay(int[][]map,int i ,int j){ if (map[6][5]==2){//通路找到 return true; }else { if (map[i][j]==0){//如果当前点没有走过 /*按照策略下--》右--》上--》左走*/ map[i][j] = 2;//假定该点可以走 if (setWay(map, i+1, j)){//向下走 return true; }else if (setWay(map, i, j+1)){//向右走 return true; }else if (setWay(map, i-1, j)){//向上 return true; }else if (setWay(map, i, j-1)){//向左 return true; }else { map[i][j] = 3;//死路走不通 return false; } }else {//map[i][j]!=0 /*1: 2: 3:*/ return false; } } } }
八皇后问题
八皇后问题,一个古老而著名的问题,是回溯算法的典型案例。该问题由国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有 76 种方案。1854 年在柏林的象棋杂志上不同的作者发表了 40 种不同的解,后来有人用图论的方法解出 92 种结果。计算机发明后,有多种计算机语言可以编程解决此问题。八皇后问题如果用穷举法需要尝试 88 =16,777,216 种情况。每一列放一个皇后,可以放在第 1 行,第 2 行,……,直到第 8 行。穷举的时候从所有皇后都放在第 1 行的方案开始,检验皇后之间是否会相互攻击。如果会,把列 H 的皇后挪一格,验证下一个方案。移到底了就 “进位” 到列 G 的皇后挪一格,列 H 的皇后重新试过全部的 8 行。如图 1 所示,这种方法无疑是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。
回溯算法优于穷举法。将列 A 的皇后放在第一行以后,列 B 的皇后放在第一行已经发生冲突。这时候不必继续放列 C 的皇后,而是调整列 B 的皇后到第二行,继续冲突放第三行,不冲突了才开始进入列 C。如此可依次放下列 A 至 E 的皇后,如图 2 所示。将每个皇后往右边横向、斜向攻击的点位用叉标记,发现列 F 的皇后无处安身。这时回溯到列 E 的皇后,将其位置由第 4 行调整为第 8 行,进入列 F,发现皇后依然无处安身,再次回溯列 E。此时列 E 已经枚举完所有情况,回溯至列 D,将其由第 2 行移至第 7 行,再进入列 E 继续。按此算法流程最终找到如图 3 所示的解,成功在棋盘里放下了 8 个 “和平共处” 的皇后。继续找完全部的解共 92 个。
回溯算法求解八皇后问题的原则是:有冲突解决冲突,没有冲突往前走,无路可走往回退,走到最后是答案。为了加快有无冲突的判断速度,可以给每行和两个方向的每条对角线是否有皇后占据建立标志数组。放下一个新皇后做标志,回溯时挪动一个旧皇后清除标志。以下给出回溯算法解八皇后问题的编程代码实现。
public class Queue8 { public static void main(String[] args) { Queue8 queue8 = new Queue8(); queue8.check(0); System.out.printf("一共有%d中解法",count); } /*定义一个max:多少个皇后*/ int max = 8; static int count=0; /*定义一个数组放置位置的结果*/ int[] array = new int[max]; /*将皇后摆放的位置输出*/ private void print(){ count++; for (int i = 0; i < array.length; i++) { System.out.print(array[i]+" "); } System.out.println(); } /*放置第n个皇后*/ private void check(int n){ if (n ==max){//n=8 其实8个皇后已经放好 print(); return; } //依次放入皇后判断是否冲突 for (int i = 0; i <max ; i++) { //先把当前皇后n放到该行的第1列 array[n]=i; /*判断放置第n个皇后到i列时是否冲突*/ if (judge(n)){//不冲突接着放下一个皇后开始递归 check(n+1); } /*如果冲突,就继续arry[n]=i即将第n个皇后放置在本行的后一个位置*/ } } /*查看当我们放置第n个皇后时:判断检查该皇后是否和之前摆放的皇后冲突*/ private boolean judge(int n){ for (int i = 0; i < n; i++) { if (array[i]==array[n] ||Math.abs(n-i)==Math.abs(array[n]-array[i])){//1.同一列2.通一斜线 return false; } } return true; } }