回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)

回溯算法是一种很重要的算法,有着通用算法的美称,不管是leetcode也好还是考研、笔试也罢都会有大量回溯算法的题目出现。该文章首先会解决什么叫做回溯算法,然后以leetcode题目《46. 全排列》、 leetcode题目《131. 分割回文串》作为例题,来讲解如何思考回溯算法、怎么样进行回溯,最后总结回溯模板。题目讲解用伪代码,是为了让JAVA、Python、C++等语言的靓仔、靓女搞明白,文末有具体实现的链接

文章目录

  • 1. 用leetcode题目《46.全排列》做例题,方便讲解
  • 2. 回溯算法——穷举法的2.0版本
    • 2.1 等一下,这个东西好像在哪里见过
    • 2.2 为什么要回溯,头铁撞南墙不香吗?
    • 2.3 同样是探索可能出现的所有情况,为什么搜索的时候不用广度优先搜索
  • 3. 以全排列为例题,进行回溯算法的构建
    • 3.1 首先用示例画出可能发生的情况,以及最终解,方便观察
    • 3.2 然后通过观察上面图形,用四个问题整理思路,来构造递归树
    • 3.3 完整的伪代码,各种语言的实现在后面参考资料那里
    • 3.4 可能还是哪里有点懵逼,我用图解一个小路径
  • 4. 整个好活,做道题开心一下
    • 4.1 画出示例的各种情况
    • 4.2 按照四个问题,整理思路,构造递归树
    • 4.3 完整的伪代码
  • 5 说了这么多,来总结一下回溯算法的模板
  • 6. 回溯类型算法比较经典的例题
    • 6.1 括号生成问题,比较简单容易上手
    • 6.2 N皇后问题经典中的经典,难度较高
  • 7. 参考资料

致敬一下大佬的文章:leetcode用户liweiwei1419对题目《46. 全排列》的题解《 从全排列问题开始理解“回溯搜索”算法(深度优先遍历 + 状态重置 + 剪枝》,网址:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/,给了许多启发,也是通过这篇文章,我弄懂了这个回溯鬼东西是怎么玩的。

1. 用leetcode题目《46.全排列》做例题,方便讲解

所有的算法的提出都是为了解决比较实际的问题,当然用工程方面的东西太复杂了,这里用leetcode的题目。
leetcode题目《46. 全排列》,网址:https://leetcode-cn.com/problems/permutations/
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第1张图片

2. 回溯算法——穷举法的2.0版本

回溯算法又叫回溯搜索算法,核心思路是将一个问题中所有可能出现的情况(穷举法)转化为解集树,然后逐一剔除剪枝找到符合条件的解又或者是解集(穷举的优化)。 “回溯”指的是“状态重置”,也就是一条路走不通回到原点在走一次。这种“剔除”技巧叫做剪枝技巧。常见的应用是求解子情况。

回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。

那么为了方便理解和构造回溯算法的模型,在这里我将全排列问题中所有可能出现的情况以树状图的形式列举出来,(一条路径一个解)
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第2张图片
回溯,且剪去不符合条件的解之后。
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第3张图片

2.1 等一下,这个东西好像在哪里见过

其实深度优先搜索就用到了回溯算法的思想,都是在探索过程中搜索可能符合条件的解,一种大的情况探索完了,在换一种情况,此路不通换一条路。不太熟悉深度优先搜索的,可以看一下我自己的博客《深度优先搜索DFS(动画解算法,内附C++/C、JAVA、Python的实现)》

不同点在于深度优先搜索是在探索一个比较明显的图(当然也有用在树的遍历),而回溯算法探索的是一个比较隐晦的解集树。
深度优先搜索的往回走的过程:

回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第4张图片

2.2 为什么要回溯,头铁撞南墙不香吗?

为什么要回溯?回溯是为了不用走那么多路,从而减少时间复杂度。
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第5张图片
还走1 1 * 这条路,怕是直接把我头按在电饭煲里面去哟。又不是我方打野在野区刷微信步数。
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第6张图片

2.3 同样是探索可能出现的所有情况,为什么搜索的时候不用广度优先搜索

常见的探索方法有广度(BFS)和深度(DFS),为什么回溯算法只用深度优先搜索呢?
最主要是深度没有这么麻烦,不用设置队列等乱七八糟的。

  1. 只有遍历所有可能出现的情况,才能得到所有符合条件的解
  2. 在深度优先遍历的时候,不同子情况之间(比如说 1 2 3 切换到 1 3 2)切换很容易,每两个子情况之间的差别只有1处,因此往后走,直接对整个路径的(比如说:1 1 * 这条路在1的时候就可以放弃了)放弃比较容易,全局用一份状态变量就可以完成搜索。
  3. 广度优先搜索要用到队列还要编写结点类别,比较麻烦。使用深度优先搜索,直接用了系统的栈,系统栈帮助我们保存了每一个结点的状态信息。于是我们不用编写结点类,不必手动编写栈完成深度优先遍历。简单粗暴有效
  4. 如果使用广度优先遍历,从浅层转到深层,状态的变化就很大,此时我们不得不在每一个状态都新建变量去保存它,从性能来说是不划算的;

参考了:leetcode用户liweiwei1419对题目《46. 全排列》的题解《从全排列问题开始理解“回溯搜索”算法(深度优先遍历 + 状态重置 + 剪枝)》,网址:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第7张图片
回溯算法以及剪枝技巧(内附通用构建模板,文末有C++、JAVA、Python的实现)_第8张图片
我自己的博客:《

你可能感兴趣的:(常用的算法以及数据结构,算法,剪枝,面试,机器学习,神经网络)