「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」
Hope is a good thing, maybe the best of things. And no good thing ever dies.
前言
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.
五大常用算法,包含 分治算法、动态规划算法、贪心算法、回溯法、分治界限法。
其实在我们日常的开发工作中,这些算法的思想我们都会用到,可能是我们没有完整的了解过他们的概念,以至于讲起来的时候会觉得陌生,其实我自己就是这样的...
今天就来简单的学习一下什么是回溯法。
什么是回溯法
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。
当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯法的过程
-
针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
-
确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
-
以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
例如我之前学到的一个算法题:# 算法:复原 IP 地址 其实我们在递归字符串的过程中,就是为了找到符合有效IP的最优解的过程。我们会从头开始,去遍历字符串,我们要根据要求设置结束条件和退出条件,然后依次去递归我们的字符串,直到拿到最后的正确结果。
// 递归函数
const dfs = (s, segId, segStart) => {
// 退出条件
if (segId === SEG_COUNT) {
if (segStart === s.length) {
ans.push(segments.join('.'));
}
return;
}
// 结束条件,如果还没有找到 4 段 IP 地址就已经遍历完了字符串,提前回溯
if (segStart === s.length) {
return;
}
// 剪枝策略, 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if (s.charAt(segStart) === '0') {
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
}
// 枚举每一种可能性并递归
let addr = 0;
for (let segEnd = segStart; segEnd < s.length; ++segEnd) {
addr = addr * 10 + (s.charAt(segEnd) - '0');
if (addr > 0 && addr <= 0xFF) {
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else {
break;
}
}
}
回溯法的基本思想
从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。
回溯法中的「剪枝」
所谓优化剪枝策略,就是判断当前的分支树是否符合问题的条件,如果当前分支树不符合条件,那么就不再遍历这个分支里的所有路径。
回溯法的场景
经典的回溯法问题:
附:
结语
如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。
文章如有错误之处,希望在评论区指正🙏🙏
欢迎关注我的微信公众号,一起交流技术,微信搜索 🔍 :「 五十年以后 」