如何深度理解回溯法,让它变得简单

本文深入浅出讲解回溯法,先介绍其原理,它是深度优先搜索法,在解空间树中按深度优先策略探索,适用于图的深度搜索相关问题。接着说明解题步骤,还给出代码模板。最后以LeetCode 40题为例,介绍解题思路及剪枝技巧。

这篇文章主要是想深入浅出的讲解回溯法,会从回溯法的原理上分析,也会从应用的角度的分析回溯法的使用。

一、回溯法怎么理解

  • 回溯法的解释:
    深度优先搜索法,又称为试探法,实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径,满足回溯条件的某个状态的点称为“回溯点”。(多读几遍,你会发现这涵盖了回溯法的全部类容,如果不能理解看完文章再读一遍)

  • 回溯法基本思想:
    在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法,这个解释又更加的精简而正确。用白话讲回溯法就是深度搜索问题隐含的图,找到正确的路径)。

  • 适用的问题:
    回溯法既然是图的深度搜索,那么相关问题都可以用回溯法解。回溯可以抽象为一棵树,我们的目标可以是找这个树有没有good leaf(找一个路径),也可以是问有多少个good leaf(找所有路径),也可以是找这些good leaf都在哪,也可以问哪个good leaf最好,分别对应上面所说回溯的问题分类。good leaf都在leaf上。good leaf是我们的goal state,leaf node是final state,是解空间的边界。
    — 求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
    — 求任一个解时,只要搜索到问题的一个解就可以结束。
    — 判断有没有解、解的个数。

二、回溯法的解题步骤

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

前面两步是核心,第三步只是一个加速的过程,后面会单独介绍第三步,现在先不管。

三、回溯法的代码模板

看到这里如果你还是有点迷,没有关系,看看回溯法的代码,一键开启的回溯法世界。后面会一个实际的例子来讲解这个模板式怎么回事。

1. 定义一个全局的解(如List<List> result)
2. 定义一个辅助的方法(函数)def backtracking(int n,int k, Listlist):
在回溯时通常需要一个线性结构来保存前面的状态,以便回溯时使用,

backtrack(result, i){
    if(i>n) //终止条件(第一步)
        输出结果;
    for(j=下界;j<上届;j=j+1){//枚举所有路径(第二步)
        if(fun(j)){//满足界限条件或约束条件(第三步)
            backtrack(result, i+1) //递归调用,其他 操作;(第二步)
            //回到上一步;
        }
}

四、实例

leetcode40. 从候选集找到目标为target的集合。(候选集有重复值)

  • 第一步:解空间和目标。路径上和 == target,即sum(path) == target。这里我们做一个转换,每次target减去路径上的值,然后目标就是target==0.
  • 第二步:如何搜索。其一,搜索过程是每次加入候选集当前值后面的数。其二,为了使得结果不重复,跳过一些重复的路径。
  • 第三步:剪枝。代码里会把剪枝贴出来,后面会告诉大家找剪枝的技巧。
def combinationSum2(candidates, target):
  
    def backtrack(path,candiates,target,start):
        if target == 0: 目标
            res.append(path)
        for i in range(start,len(candidates)):
            if i != start and candidates[i-1] == candidates[i]:搜索的第二个条件
                continue
            if candidates[i] > target:剪枝
                break
            backtrack(path+[candidates[i]],candidates, target - candidates[i],i+1)搜索的过程,用start来控制搜索的路径
    res = []
    candidates.sort()
    backtrack([],candidates,target,0)
    return res
补充:找剪枝的技巧
  • 方法一:在纸上画出你的路径搜索过程,然后看那些搜索是不必要的,遂剪掉
  • 方法二:debug策略,单步调试,看那些搜索是在做无用功。因为你会对那些肯定没有解的搜索过程调试的更快。哈哈哈。

希望大家看完可以觉得回溯法很简单,也就达到了我的目的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值