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

Posted yealxxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何深度理解回溯法,让它变得简单相关的知识,希望对你有一定的参考价值。

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

一、回溯法怎么理解

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

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

  • 适用的问题:
    回溯法既然是图的深度搜索,那么相关问题都可以用回溯法解。回溯可以抽象为一棵树,我们的目标可以是找这个树有没有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策略,单步调试,看那些搜索是在做无用功。因为你会对那些肯定没有解的搜索过程调试的更快。哈哈哈。

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

以上是关于如何深度理解回溯法,让它变得简单的主要内容,如果未能解决你的问题,请参考以下文章

回溯法

回溯法

回溯法

0/1背包问题(回溯法)

使用回溯法解决编辑距离问题(C语言)

使用回溯法解决编辑距离问题(C语言)