回溯算法总结

回溯法学习总结

回溯算法也是算法导论中常用的算法,回溯算法类似于暴力求解算法,经常用在求可能解的问题。下面我将从三个方面来介绍回溯算法。
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着色问题

你可能感兴趣的:(回溯算法总结)