回溯法基本思想_回溯法

e4e11aaa4737db33f5daf556677c6404.png

回溯法网上的资料也挺多的,但是看别人的代码和资料总是感觉很难真正理解其中的思想,进而内化为自己解决问题的一个思路。本文想将回溯法解决问题的方法套路化,将回溯法的实现分解成几个具体步骤,以后遇到具体的回溯法问题,只需要根据这几个步骤去思考没个步骤应该做什么,思路会相当清楚。

讲回溯法(Backtracking)之前我想先提出一个问题。


给定一个数组,可以从该数组中选择两个数求和,找出和最大的情况。相信会写程序的都知道使用两层for循环遍历所有的可能。如果问题改为选择三个数呢,程序相应的改为三层for循环。现在我将选择的数的个数也参数化,具体问题如下,给定一个数组a和一个参数n,可以从a中选择n个数求和,找出和最大的情况。此时你需要根据参数n去控制程序for循环的层数,这是一个动态for循环的问题。学完回溯法,解决这种问题会变成你的基本操作。

从经典的八皇后问题谈起(关于什么是八皇后问题,请自行搜索)。1848年提出,高斯都搞不定的问题,今天一个普通人用计算机都能轻松搞定哦。

假如我们没有回溯法的思想,我们的程序该如何计算呢?先考虑下到底有几种摆法,每一列都可以尝试放一个,总共8列。第一列我们有8个位置可选,第二列也有8个,第三列8个,第四列8个...。可以算出总共有8的8次方种摆法。

在没有任何算法知识的情况下,我们也可以设计一种暴力的算法解决。首先我们定义一个8*8的二维数组存储我们的摆放数据,将放置皇后的位置索引值设定为1。这个二维数组的可能性对应8的8次方种摆法,我们需要解决的问题就是根据这个二维数组的数据判断是否是符合条件的解。根据索引操作,我们可以很容易判断每一种摆法是不是符合要求。具体代码参考solution1。

上面的解法必须要在列出完整的摆法之后再做判断。其实我们可以在每次放一列之后都做一次判断,如果不符合则提前退出后面的循环,这样程序的效率会大幅提高。例如第二列放置的皇后,与第一列已经同一行,则后面的步骤没有必要进行判断了。又或者,第四列与前面任何一列同行,则后面的列数不用做判断。这就是回溯法相对暴力解法的优化思想,每走一步都进行一次检查,如果不符合条件则回退一步继续做检查,直到找到问题的解。具体代码参考solution2

接下来我想说一下回溯法解决问题的思路和代码模板,然后就可以将上面的代码改成回溯法实现。

先提出一个简单的问题,给定一个数字类型的数组nums和数字k,从nums中选出k个数字组合,列出所有的选法,也就是一个C(n, k)的组合问题。我先给出这个问题的完整代码。然后再说一下代码的含义。

solution 是存放单个解的集合,本例中我们存放每个组合的数字。solutionList 是存放所有solution 的集合。

dfs 方法是递归调用的深度优先搜索。

isASolution 判断是否是完整解,因为solution中的数字是一个位置一个位置的填充,本例中当solution的size等于k时,则返回true即可。每次进入 dfs 方法都会调用 isASolution ,对于符合的solution,添加进solutionList集合。

processSolution 对 solution 进行处理,本例中我们添加进solutionList即可,有的情况下需要进行数据转换操作。

isValid 判断我们的每一步是否合法,solution中的每个数字都是从nums中挑选出来的,组合问题的条件是数字不能重复选择,且需要排除重复的组合项。

makeMove 代表我们的回溯步骤向前一步。

unMakeMove 回溯步骤后退一步,找到解或者for循环都没有找到一个解的时候执行。

将回溯法的问题按照上面的模板拆解去思考每一个步骤的代码实现,你会发现每一步都很清楚,代码也容易调试和维护。上面这个模板是我解决任何回溯问题都会去直接套用的模板代码,大家可以打下断点调试,跟踪solution和solutionList的数据变化和程序的执行流程,一定要透彻理解,理解透彻之后,可以独立思考下八皇后问题怎么按照回溯法的模板去套用解决,然后还有LeetCode上面的n皇后问题,n皇后问题也需要一个动态for循环才能解决,回溯法正好可以解决。

另外回溯法的问题可以分为四类问题。

第一类,找出问题的所有解,例如上面的n皇后,需要列出问题的所有解。模板代码。

第二类,找到问题解的数量。模板代码。

第三类,找到一个解,关键在于找到解之后要退出循环。例如下面的数独问题。模板代码。

第四类,判断是否存在解,也需要提前退出循环。模板代码。

在你感觉完全理解了回溯法的套路之后,可以尝试独立解决下LeetCode上面的几个经典的回溯问题。

解数独,套用模板三。这是一道定义为hard的题目,但是如果真正理解了回溯法的套路,其实没有任何思路上的障碍,就是每个格子从1-9尝试走一波,难点只在于我们判断isValid中有一些索引计算上面的判断。

Restore IP Addresses,套用模板一。思考下你的solution应该记录什么,每一步都尝试做什么。

WordSearch,套用模板四。考虑成方格的棋盘,每一步都可以走上下左右四种。

总结,回溯法是一种很强大的搜索型算法,它可以帮助我们搜索出符合问题条件的所有解空间,相比于真正的暴力算法,它又可以提前过滤掉不符合问题的分支,提高搜索效率。当你在碰到动态规划问题,又想不出状态转移方程,可以尝试使用回溯法写出代码实现,有时可以帮助我们想出动态规划的实现方式。

另外回溯算法,还有很多的优化的剪枝函数。例如舞蹈链,还有待研究。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值