算法-什么是回溯法?

313 阅读3分钟

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

Hope is a good thing, maybe the best of things. And no good thing ever dies.

前言

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.

五大常用算法,包含 分治算法动态规划算法贪心算法回溯法分治界限法

其实在我们日常的开发工作中,这些算法的思想我们都会用到,可能是我们没有完整的了解过他们的概念,以至于讲起来的时候会觉得陌生,其实我自己就是这样的...

今天就来简单的学习一下什么是回溯法。

什么是回溯法

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。

当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯法的过程

  1. 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。

  2. 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。

  3. 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

例如我之前学到的一个算法题:# 算法:复原 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;
            }
        }
    }

回溯法的基本思想

从一条路往前走,能进则进,不能进则退回来,换一条路再试。

回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。

回溯法中的「剪枝」

所谓优化剪枝策略,就是判断当前的分支树是否符合问题的条件,如果当前分支树不符合条件,那么就不再遍历这个分支里的所有路径。

回溯法的场景

经典的回溯法问题:

附:

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏

欢迎关注我的微信公众号,一起交流技术,微信搜索 🔍 :「 五十年以后