回溯算法中关于剪枝的一些应用

衔接上篇( ^ _ ^ )
剪枝优化 是回溯算法中一种重要的优化手段,其核心思想是 提前终止无效的递归分支,避免无意义的搜索,从而大幅减少计算量。通过合理剪枝,可以将指数级的时间复杂度降低到更优的水平。


一、剪枝的核心思想

在递归遍历决策树时,提前判断当前路径是否可能得到有效解

  • 不可能得到解 → 直接终止当前分支的递归("剪掉"这个分支)
  • 可能得到解 → 继续递归探索

二、剪枝的常见类型及示例

1. 可行性剪枝

在每一步判断当前路径是否满足问题的约束条件,若不满足则提前返回。
示例:组合总和问题

cpp 复制代码
void backtrack(vector<int>& nums, int remain, int start, vector<int>& path, vector<vector<int>>& res) {
    if (remain < 0) return;  // ✂️剪枝:剩余值为负数时直接返回
    if (remain == 0) {
        res.push_back(path);
        return;
    }
    
    for (int i = start; i < nums.size(); ++i) {
        if (nums[i] > remain) break;  // ✂️剪枝:已排序,后续元素更大,无需尝试
        path.push_back(nums[i]);
        backtrack(nums, remain - nums[i], i, path, res);
        path.pop_back();
    }
}

关键点

  • 先对数组排序(sort),使后续元素递增
  • nums[i] > remain 时,后续元素必然更大,不可能满足条件 → 直接break

2. 重复性剪枝

避免生成重复的解(常见于元素有重复值的问题)。
示例:全排列II(含重复元素)

cpp 复制代码
void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& res) {
    if (path.size() == nums.size()) {
        res.push_back(path);
        return;
    }
    
    for (int i = 0; i < nums.size(); ++i) {
        // ✂️剪枝条件1:跳过已使用的元素
        if (used[i]) continue;  
        // ✂️剪枝条件2:跳过重复元素(需先排序)
        if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
        
        used[i] = true;
        path.push_back(nums[i]);
        backtrack(nums, used, path, res);
        path.pop_back();
        used[i] = false;
    }
}

关键点

  • 先对数组排序(sort),使相同元素相邻
  • nums[i] == nums[i-1]!used[i-1] 时,说明前一个相同元素未被使用 → 当前分支会产生重复解 → 跳过

3. 对称性剪枝

利用问题本身的对称性,避免重复搜索对称解。
示例:生成回文数

在生成回文数时,只需构造前半部分,后半部分对称生成即可。


三、剪枝的实现技巧

技巧 适用场景 示例
排序 + 提前终止循环 组合问题、子集问题 if (nums[i] > remain) break
索引标记(start 避免重复选择元素 组合问题中的 for (i = start)
哈希表记录已访问状态 棋盘类问题(如数独) 记录某行/列/宫是否包含某数字
数学性质推导 利用数学公式提前排除不可能的情况 N皇后问题的斜线检查

四、剪枝的意义

  1. 时间复杂度优化:将指数级复杂度降低到更优水平(例如从 O(n!) 优化到 O(n×n!))
  2. 空间优化:减少递归深度和内存占用
  3. 实际效率提升:对于大规模输入,剪枝可能将不可行的问题变为可解

五、经典剪枝练习

  1. 组合总和II (不可重复选相同元素):if (i > start && nums[i] == nums[i-1]) continue
  2. 子集II (含重复元素):if (i > start && nums[i] == nums[i-1]) continue
  3. 数独求解:预先记录每行/列/宫已使用的数字,快速判断是否可填

六、总结

剪枝是回溯算法的灵魂,核心在于通过问题特性提前终止无效递归
关键步骤

  1. 明确问题的约束条件
  2. 分析决策树中哪些分支必然无效
  3. 设计剪枝条件,用代码实现提前终止
  4. 通过排序、哈希表等工具辅助剪枝判断
相关推荐
全域智图1 小时前
元胞自动机(Cellular Automata, CA)
人工智能·算法·机器学习
珂朵莉MM1 小时前
2022 RoboCom 世界机器人开发者大赛-本科组(省赛)解题报告 | 珂学家
人工智能·算法·职场和发展·深度优先·图论
独家回忆3641 小时前
每日算法-250601
数据结构·算法
YONYON-R&D1 小时前
DEEPSEEK帮写的STM32消息流函数,直接可用.已经测试
算法·消息流
Steve lu3 小时前
回归任务损失函数对比曲线
人工智能·pytorch·深度学习·神经网络·算法·回归·原力计划
蒙奇D索大3 小时前
【数据结构】图论核心算法解析:深度优先搜索(DFS)的纵深遍历与生成树实战指南
数据结构·算法·深度优先·图论·图搜索算法
让我们一起加油好吗3 小时前
【基础算法】高精度(加、减、乘、除)
c++·算法·高精度·洛谷
不会敲代码的灵长类3 小时前
机器学习算法-k-means
算法·机器学习·kmeans
Studying 开龙wu3 小时前
机器学习有监督学习sklearn实战二:六种算法对鸢尾花(Iris)数据集进行分类和特征可视化
学习·算法·机器学习
鑫鑫向栄4 小时前
[蓝桥杯]缩位求和
数据结构·c++·算法·职场和发展·蓝桥杯