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 的辨析;要求会写证明思路并在笔试中出现"贪心反例判断"。
七、课内练习建议
- 活动安排 / 排队打水(短作业优先)手推最优解并编程序。
- 写一个"反例找茬程序":用小数据暴力枚举,对比贪心结果,找到不可贪心的反例。
- 阅读 2.2 的币值反例,思考为什么 1、3、4 币值不能贪心。
- 用优先队列实现哈夫曼(合并果子)。
- 火车调度/线段覆盖类题目各来一道,练"读题→抽象成贪心模型"。