基础算法总结(一):回溯法 | 豆包MarsCode AI刷题

144 阅读3分钟

回溯法的本质

回溯法是一种深度优先搜索算法,该方法试图穷举问题所有的解。所谓递归回溯,回溯一般出现在递归函数中。通过递归来实现深度优先遍历。常用来解决组合问题、排列问题、分割问题等问题。

深度优先搜索(DFS)

在回溯算法中,“深度”指的是递归调用的层数,或者说是在解决问题时探索路径的长度。每深入一层,就选择一个可能的选项继续向下探索。检查当前路径是否满足问题的要求。如果满足,则记录这个解;如果不满足,则回溯到上一层,尝试其他可能的选择。

如何搜索?

  1. 做出选择: 在当前状态下,选择一个可行的选项。

  2. 递归深入: 基于当前选择,进入下一个状态,继续做出新的选择。

  3. 撤销选择: 如果发现当前路径不能到达目标或者已经找到一个解,回溯到上一步,撤销之前的决定,尝试其他选择。

  4. 终止条件: 当所有选择都被尝试过或者找到了足够的解后,结束搜索过程。

  5. 剪枝策略: 可以设置不同的剪枝策略,在枚举搜索节点(即路径节点)时使用剪枝避免不必要的搜索,提高算法性能。

通过在递归过程中提前排除不可能产生有效解的路径,剪枝可以帮助我们更快地找到所有可能的解决方案!

  • 问题约束规则(约束函数):视具体情况而定。
  • 限界选优(剪枝函数),常出现在求解最优问题时。

回溯算法的最简洁模版

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

e.g:组合总和问题

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& result) {
    if (target == 0) { // 终止条件:如果目标值为0,说明找到了一个有效组合
        result.push_back(path);
        return;
    }

    for (int i = start; i < candidates.size(); ++i) {
        if (candidates[i] > target) break; // 剪枝:如果当前候选数大于目标值,后面的更大数也不需要考虑

        // 选择:将当前候选数加入路径
        path.push_back(candidates[i]);

        // 递归调用,深入下一层
        backtrack(candidates, target - candidates[i], i, path, result); // 注意这里的start保持不变,允许重复使用当前候选数

        // 撤销选择:移除最后一个元素
        path.pop_back();
    }
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> result;
    vector<int> path;

    // 首先对候选数组进行排序,方便后续剪枝操作
    sort(candidates.begin(), candidates.end());

    backtrack(candidates, target, 0, path, result);
    return result;
}

int main() {
    vector<int> candidates = {2, 3, 6, 7};
    int target = 7;
    vector<vector<int>> combinations = combinationSum(candidates, target);
    
    return 0;
}

回溯算法中只有剪枝策略一种优化方式

优化方案

  1. 在for循环(单层遍历节点)上做剪枝操作,映射到解空间树的结构上就是层方面的剪枝

  2. 在递归函数中做剪枝或者更优解的更新,映射到解空间树的结构就是递归树垂直上的剪枝

本文非常简陋,是在ai刷题过程中慢慢总结的,目前还有很多地方不足,个人建议初学者(虽然我也是)最好一开始刷题先问ai题目的分类和推荐方法,然后具体的方法应该参考课本,搭配专门的一些算法总结和例题,然后自己抽象总结,常备纸笔自己手写程序,感觉是很有效的。