回溯法学习总结
回溯算法也是算法导论中常用的算法,回溯算法类似于暴力求解算法,经常用在求可能解的问题。下面我将从三个方面来介绍回溯算法。
1.回溯法定义
2.回溯算法的解题思路
3.回溯算法例题分析
回溯法定义
1.定义
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
来源于百度百科
下面讲一下我的理解:回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
2.详细解释
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
解题步骤
用回溯算法解决问题的一般步骤:
1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
2 、确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
3 、以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
例题分析
八皇后问题就是:将八位皇后放在一张8x8的棋盘上,使得每位皇后都无法吃掉别的皇后,(即任意两个皇后都不在同一条横线,竖线和斜线上。
public static void findQueen(int i){//寻找皇后节点
if(i>7){//八皇后的解
map++;
print();//打印八皇后的解
return;
}
for(int m=0;m<8;m++){//深度回溯,递归算法
if(check(i,m)){//检查皇后摆放是否合适
arry[i][m]=1;
findQueen(i+1);
arry[i][m]=0;//清零,以免回溯的时候出现脏数据
}
}
}
我们来重点看一下这段代码:
回溯算法中最重要的就是使用剪枝函数:
1.限界函数
if(i>7){//八皇后的解
map++;
print();//打印八皇后的解
return;
}
当 i=8时,说明我们已经找到一种可能解,这是我们就可以退出函数,这就是回溯的边界问题
2.约束函数
public static boolean check(int k,int j){//判断节点是否合适
for(int i=0;i<8;i++){//检查行列冲突
if(arry[i][j]==1){
return false;
}
}
for(int i=k-1,m=j-1; i>=0 && m>=0; i--,m--){//检查左对角线
if(arry[i][m]==1){
return false;
}
}
for(int i=k-1,m=j+1; i>=0 && m<=7; i--,m++){//检查右对角线
if(arry[i][m]==1){
return false;
}
}
return true;
}
我们使用此函数来约束是否可以存放其他皇后,不满足条件就回溯。
第一次进来,row=0,意思是要在第一行摆皇后,只要传进来的row参数不是8,表明还没出结果,就都不会走if里面的return,那么就进入到for循环里面,column从0开始,即第一列。此时第一行第一列肯定合乎要求(即check方法肯定通过),能放下皇后,因为还没有任何其他皇后来干扰。
关键是check方法通过了之后,在if里面又会调用一下自己(即递归),row加了1,表示摆第二行的皇后了。
第二行的皇后在走for循环的时候,分两种情况,第一种情况:for循环没走到头时就有通过check方法的了,那么这样就顺理成章地往下走再调用一下自己(即再往下递归),row再加1(即摆第三行的皇后了,以此类推)。第二种情况:for循环走到头了都没有通过check方法的,说明第二行根本一个皇后都摆不了,也触发不了递归,下面的第三行等等后面的就更不用提了,此时控制第一行皇后位置的for循环column加1,即第一行的皇后往后移一格,即摆在第一行第二列的位置上,然后再往下走,重复上述逻辑。
注意,一定要添加清零的代码,它只有在皇后摆不下去的时候会执行清0的动作(避免脏数据干扰),如果皇后摆放很顺利的话从头到尾是不会走这个请0的动作的,因为已经提前走if里面的return方法结束了。
最后放上完整代码
public static int[][] arry=new int[8][8];//棋盘,放皇后
public static int map=0;//存储方案结果数量
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("八皇后问题");
findQueen(0);
System.out.println("八皇后问题共有:"+map+"种可能");
}
public static void findQueen(int i){//寻找皇后节点
if(i>7){//八皇后的解
map++;
print();//打印八皇后的解
return;
}
for(int m=0;m<8;m++){//深度回溯,递归算法
if(check(i,m)){//检查皇后摆放是否合适
arry[i][m]=1;
findQueen(i+1);
arry[i][m]=0;//清零,以免回溯的时候出现脏数据
}
}
}
public static boolean check(int k,int j){//判断节点是否合适
for(int i=0;i<8;i++){//检查行列冲突
if(arry[i][j]==1){
return false;
}
}
for(int i=k-1,m=j-1; i>=0 && m>=0; i--,m--){//检查左对角线
if(arry[i][m]==1){
return false;
}
}
for(int i=k-1,m=j+1; i>=0 && m<=7; i--,m++){//检查右对角线
if(arry[i][m]==1){
return false;
}
}
return true;
}
public static void print(){//打印结果
System.out.print("方案"+map+":"+"\n");
for(int i=0;i<8;i++){
for(int m=0;m<8;m++){
if(arry[i][m]==1){
//System.out.print("皇后"+(i+1)+"在第"+i+"行,第"+m+"列\t");
System.out.print("o ");
}
else{
System.out.print("+ ");
}
}
System.out.println();
}
System.out.println();
}
回溯算法主要还是递归算法,想明白这个过程,我们就可以写一下简单回溯算法。
回溯算法主要有下面几个经典问题,大家平时可以花点时间多研究一下。
(1)装载问题
(2)0-1背包问题
(3)旅行售货员问题
(4)八皇后问题
(5)迷宫问题
(6)图的m着色问题