12 贪心算法(Greedy)

GESP 5 级CSP-J / NOIP 高频

级别标注:GESP 5 级(考级要求)/ CSP-J、NOIP 高频(竞赛进阶:正确性证明、区间问题、哈夫曼、活动安排)。

一、什么是贪心

贪心策略:每一步都做"当前看起来局部最优"的选择,期望得到全局最优。使用前提:局部最优能推导出全局最优(需证明或由题目性质保证)。

重要提醒:不是所有问题都能贪心。无法证明时,需谨慎,可改用 DP 或搜索验证。

形象理解贪心像吃自助餐只拿最贵的菜:每轮都挑当前看起来最赚的选择。问题是“每次都拿最贵”不一定总金额最高——所以贪心必须证明局部最优能推出全局最优。
易错点不是所有问题都能贪心!经典反例:硬币 1、3、4 凑 6,贪心(先拿 4)得 4+1+1 共 3 枚,最优是 3+3 共 2 枚。做题先想“贪心会不会被反例打死”,拿不准就用 DP 或暴力对拍。

二、经典贪心题目

2.1 活动安排(会议室/区间调度)

给定若干区间(开始、结束时间),选最多不重叠的区间。策略:按结束时间升序,优先选最早结束的。

C++ 实例
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct Seg { int l, r; };      // 区间:左端点 l,右端点 r
// 区间选点/选活动:按结束时间最早贪心
int main() {
    int n; cin >> n;
    vector<Seg> v(n);
    for (int i = 0; i < n; i++) cin >> v[i].l >> v[i].r;
    sort(v.begin(), v.end(), [](const Seg &a, const Seg &b) {
        return a.r < b.r;            // 按结束时间升序(先结束的先选)
    });
    int ans = 0, last = -1;          // last 记录上一个选中区间的结束点
    for (auto &s : v) {
        if (s.l >= last) {           // 与上一个不重叠才能选
            ans++;
            last = s.r;              // 更新结束点
        }
    }
    cout << ans << endl;             // 最多可选区间数
    return 0;
}

2.2 硬币找零(特殊币值可贪心)

当币值是"倍数关系"(如 1、5、10、20、50、100 人民币)时,优先用大面额贪心成立。

C++ 实例
#include <iostream>
using namespace std;
int main() {
    int money; cin >> money;
    int coins[] = {100, 50, 20, 10, 5, 1};   // 面额从大到小
    int cnt = 0;
    for (int c : coins) {        // 贪心:能多取大面额就多取
        cnt += money / c;            // 当前面额可用张数
        money %= c;                  // 剩余金额
    }
    cout << cnt << endl;             // 最少硬币数
    return 0;
}

⚠️ 一般币值(如 1、3、4)不能贪心,如 6 用贪心得出 4+1+1(3 枚)但最优是 3+3(2 枚)。此类要用 DP(15 章)。

2.3 区间选点 / 覆盖问题

例:给定若干区间,选尽可能少的点使每个区间都至少有一个点被选中。策略:按右端点升序,每次点在当前最靠右的位置。与活动安排同族。

2.4 哈夫曼编码 / 合并果子

每次取两个最小的合并(第 7 章 priority_queue 示例),保证总的合并代价(带权路径长度)最小。

2.5 多机调度 / 平均等待时间

加工时间短的先处理,总等待时间最短(短作业优先)。

形象理解活动安排像抢会议室:想让一天能开最多的会,就每次都选最早结束的那个会——结束得早,留给后面的时间就多。
考级视角区间类贪心统一套路:按右端点升序排序,再扫一遍能选就选。活动安排(最多不重叠)、区间选点(最少点覆盖所有区间)都是这个模板,GESP 5 级 / CSP-J 常考。

三、贪心的正确性证明思路(进阶)

比赛需能"说服自己": 1. 交换论证法:证明任意最优解中,若相邻两项不满足贪心顺序,交换后不会变差 → 贪心解不差于最优解。 2. 反证法:假设贪心不是最优,构造矛盾。 3. 归纳法:证明贪心每一步都保留最优解的前缀。

对初学者:先用小数据"对拍"(暴力枚举 vs 贪心)验证,再到题解里学习证明方法。

形象理解交换论证像检查排队是否合理:假设队伍里两个人顺序反了,交换他们后发现总等待时间不会变差,那就说明“按贪心顺序排”不劣于任何方案。
考级视角对拍是赛前练贪心的最实用武器:写一个暴力枚举(小数据全排列/全组合)和贪心程序,随机数据跑一遍对比。被 HACK 就说明这题不能贪心,立刻换 DP。

四、贪心 vs 动态规划

贪心 动态规划
每步只做一次选择,不回退 记录所有子问题状态,全局推导
快、简单 慢一些但通用
需证明局部最优=全局最优 保证遍历所有方案取最优

常见误区:题目看起来像贪心但实际需要 DP。若贪心解法被样例 HACK,优先想 DP。

形象理解贪心像走一步算一步、绝不回头;DP 像把每条路都走一遍并记下每个路口的最优值。贪心快但可能错,DP 慢但保证最优。
易错点01 背包是“看似贪心实则 DP”的经典:按单位价值从高到低拿会得到错误结果。看到“选择组合使价值最大”类题目,默认先怀疑贪心、优先考虑 DP。

五、易错点 / 常见坑点

  • ⚠️ 不要盲目贪心:先证明或测反例。经典反例:01 背包(不能按"单位价值"贪心拿,见 15 章)。
  • ⚠️ 区间题排序维度要选对:活动安排按右端点,区间选点本质也按右端点。
  • ⚠️ 贪心+贪心的叠加可能互相冲突,注意题目条件。
  • ⚠️ 结构体排序没写比较函数导致编译错误或默认错误序。
  • ⚠️ 结果可能超 int(如合并果子用 long long)。

六、考级要求 vs 竞赛进阶

  • 考级要求(GESP 5 级):贪心入门题(硬币、活动选择、简单调度)能理解并实现;常以"每次取最小/最大"的直接策略命题。
  • 竞赛进阶(CSP-J/NOIP):区间覆盖、多机调度、哈夫曼、贪心+堆结合、贪心与 DP 的辨析;要求会写证明思路并在笔试中出现"贪心反例判断"。

七、课内练习建议

  1. 活动安排 / 排队打水(短作业优先)手推最优解并编程序。
  2. 写一个"反例找茬程序":用小数据暴力枚举,对比贪心结果,找到不可贪心的反例。
  3. 阅读 2.2 的币值反例,思考为什么 1、3、4 币值不能贪心。
  4. 用优先队列实现哈夫曼(合并果子)。
  5. 火车调度/线段覆盖类题目各来一道,练"读题→抽象成贪心模型"。
本章目录
一、什么是贪心 二、经典贪心题目 三、贪心的正确性证明思路(进阶) 四、贪心 vs 动态规划 五、易错点 / 常见坑点 六、考级要求 vs 竞赛进阶 七、课内练习建议