贪心算法(Greedy Algorithm)

贪心算法是一种在每一步选择中都采取当前状态下最优(最有利)的选择,从而希望导致全局最优解的算法范式。

定义

贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前状态下局部最优的选择,期望通过这种方式达到全局最优的算法思想。

核心本质:贪心算法 = 遍历思维 + 不穷举所有可行解,直接推导最优选择。

核心特性

特性 说明
局部最优 每一步都选择当前看起来最好的选项
无回溯 做出选择后不撤销,不回头检查
高效性 通常时间复杂度为 O(n) 或 O(1)
适用条件 需要满足贪心选择性质和最优子结构

贪心选择性质

贪心算法的正确性需要证明贪心选择性质

通过局部最优选择,能够产生全局最优解。

关键区别

  • 动态规划:穷举所有可行解(去重后),再找最优
  • 贪心算法不穷举,直接选择当前最优,跳过其他可能性

与类似算法对比

维度 贪心算法 动态规划 回溯算法
穷举范围 不穷举所有可行解 穷举所有可行解(去重后) 穷举所有可行解(剪枝后)
优化手段 推导最优,直接选择 备忘录优化,避免重复计算 剪枝优化,提前排除
时间复杂度 通常 O(n) 或 O(1) 多项式级 O(n²)、O(n³) 指数级 O(2ⁿ)、O(n!)
正确性保证 需要证明贪心选择性质 只要满足最优子结构就正确 只要正确穷举就保证找到
状态管理 通常不需要回溯 需要存储子问题解 需要维护路径,做选择/撤销选择

演进关系:回溯(剪枝优化)→ 动态规划(备忘录优化)→ 贪心(推导最优,不穷举)

经典问题

问题 难度 贪心策略
跳跃游戏 中等 维护最远可达位置
跳跃游戏 II 中等 在不得不跳时再跳
分发饼干 简单 小饼干喂小胃口
无重叠区间 中等 按结束时间排序,选结束最早的
用最少数量的箭引爆气球 中等 按起始位置排序,合并重叠区间
最优加油策略 困难 每次没油时,选择经过的加油站中油量最大的

解题框架

// 贪心算法通用思路
void greedy(vector<int>& nums) {
    // 1. 排序或预处理(根据贪心策略)
    sort(nums.begin(), nums.end());

    // 2. 遍历,每一步做当前最优选择
    for (int i = 0; i < nums.size(); i++) {
        // 根据贪心策略做选择
        // 不需要回溯,不需要备忘录
    }
}

适用条件

贪心算法能正确求解的问题必须满足:

  1. 贪心选择性质:局部最优选择能导致全局最优解
  2. 最优子结构:问题的最优解包含子问题的最优解

⚠️ 不是所有问题都适合贪心:如果问题不满足贪心选择性质,贪心算法可能得到错误结果。

经典反例

0-1 背包问题

  • 贪心策略:优先选价值密度(价值/重量)最高的物品
  • 错误:可能因为选了高密度物品,导致背包剩余空间无法利用
  • 正确解法:动态规划

分数背包问题

  • 贪心策略:优先选价值密度最高的物品
  • 正确:因为可以按比例装入,贪心策略正确

时间复杂度

问题类型 时间复杂度 说明
简单贪心 O(n) 一次遍历
需要排序 O(n log n) 排序通常是最耗时操作
复杂贪心 O(n²) 或更高 可能需要多次扫描

注意事项

  • 必须证明正确性:不能凭直觉使用贪心,需要证明贪心选择性质成立
  • 对比动态规划:如果贪心正确性难以证明,考虑使用动态规划
  • 从简单例子入手:通过小例子验证贪心策略是否真的能得到最优解
  • 关注边界条件:有些问题大部分情况贪心正确,但特定边界会出错

应用场景

  • 调度问题:任务调度、区间调度(选择最多不重叠区间)
  • 最优路径:Dijkstra 算法(带权重图的最短路径)
  • 最小生成树:Prim 算法、Kruskal 算法
  • 霍夫曼编码:每次选频率最小的两个节点合并
  • 部分背包问题:可以按比例装入物品

相关概念

  • 动态规划:比贪心多一步(穷举所有可行解 + 备忘录优化)
  • 回溯算法:比动态规划多一步(纯暴力穷举 + 剪枝)
  • 递归:贪心算法通常用迭代实现,不需要递归
  • 分治算法:也做选择,但子问题独立
  • Dijkstra算法:贪心思想的应用(每次选最近的节点)
  • 最小生成树算法:Prim 和 Kruskal 都是贪心算法

Interactive Graph