相关笔记

概览

本节系统总结贪心算法的设计方法论,提炼出两个关键性质:贪心选择性质(greedy-choice property)和最优子结构(optimal substructure)。通过 0-1 背包问题 vs 分数背包问题的对比,揭示贪心与动态规划的本质区别

  • 贪心选择性质:局部最优选择能导致全局最优解
  • 最优子结构:最优解包含子问题的最优解(与 DP 共享)
  • 核心区别:贪心先选择后求解(自顶向下),DP先求解后选择(自底向上)
  • 设计方法:两种 6 步流程(详细版 vs 简洁版)

知识结构总览

flowchart TD
    A["贪心策略要素"] --> B["两大关键性质"]
    B --> B1["贪心选择性质<br/>Greedy-Choice Property"]
    B --> B2["最优子结构<br/>Optimal Substructure"]

    B1 --> B1a["局部最优 → 全局最优"]
    B1a --> B1b["不依赖子问题解"]
    B1b --> B1c["证明方法:替换论证"]

    B2 --> B2a["最优解包含子问题最优解"]
    B2a --> B2b["与 DP 共享此性质"]
    B2b --> B2c["贪心中只需论证<br/>贪心选择 + 子问题最优解<br/>= 原问题最优解"]

    A --> C["贪心 vs 动态规划"]
    C --> C1["贪心:先选择,后求解<br/>自顶向下"]
    C --> C2["DP:先求解,后选择<br/>自底向上"]
    C --> C3["0-1背包 → 需DP"]
    C --> C4["分数背包 → 贪心即可"]

    A --> D["贪心设计方法"]
    D --> D1["详细版6步<br/>(从DP出发)"]
    D --> D2["简洁版6步<br/>(直接贪心)"]

核心思想

核心思路

贪心算法通过一系列局部最优选择来构造全局最优解。判断一个问题能否用贪心算法求解,关键在于验证两个性质:贪心选择性质(能否安全地做出局部最优选择)和最优子结构(做出选择后的子问题是否保持最优性)。贪心算法与动态规划共享最优子结构,但本质区别在于求解顺序:贪心先做选择再解子问题(自顶向下),动态规划先解子问题再做选择(自底向上)。

贪心算法定义

贪心算法(Greedy Algorithm)

贪心算法通过做出一系列选择来获得问题的最优解。在每个决策点,算法做出在当前看来最好的选择。这种启发式策略并不总是产生最优解,但在某些问题(如15.1 活动选择问题)中确实如此。

贪心选择性质(Greedy-Choice Property)

定义:可以通过做出局部最优(贪心)选择来组装全局最优解。换言之,在考虑做哪个选择时,只需做出在当前问题中看起来最好的选择,无需考虑子问题的解

贪心与 DP 的关键区别

在动态规划中,每步也做选择,但选择通常依赖于子问题的解。因此,DP 通常以自底向上的方式求解,从小子问题推进到大子问题。

在贪心算法中,做出在当前看来最好的选择,然后求解剩下的子问题。贪心选择可能依赖于之前的选择,但不能依赖于未来的选择或子问题的解。因此,贪心算法在求解任何子问题之前就做出第一个选择,通常以自顶向下的方式推进。

【替换论证(修改最优解将贪心选择替换进去,证明存在包含贪心选择的最优解)】 证明贪心选择性质的一般方法

通常,如 Theorem 15.1 中的证明方式,考察某个子问题的全局最优解,然后说明如何修改该解,将贪心选择替换为其他选择,从而得到一个类似的但更小的子问题。这种”替换论证”(exchange argument)是证明贪心选择安全性的标准技术。

贪心选择的效率优势

通常,做出贪心选择比考虑更广泛的选择集更高效。例如,在活动选择问题中,假设活动已按结束时间排序,每个活动只需被检查一次。通过预处理输入或使用适当的数据结构(通常是6.5 优先队列),可以快速做出贪心选择,从而得到高效算法。

最优子结构(Optimal Substructure)

正如在第14章中看到的,如果一个问题的最优解包含了子问题的最优解,则该问题具有最优子结构。这个性质是评估动态规划是否适用的关键要素,对贪心算法也同样至关重要。

回顾 15.1 节的例子:如果 的最优解包含活动 ,则它也必须包含 的最优解。给定这个最优子结构,如果我们知道用哪个活动作为 ,就可以通过选择 加上 的最优解中的所有活动来构造 的最优解。

贪心算法中对最优子结构的使用方式

【归纳法(贪心选择+子问题最优解=原问题最优解)】 在贪心算法中,对最优子结构的使用通常更直接。由于可以假设已经通过贪心选择到达了子问题,只需论证:子问题的最优解,结合已做出的贪心选择,能产生原问题的最优解。这个方案隐式地使用了对子问题的归纳法来证明每一步都做出贪心选择能产生最优解。

贪心 vs 动态规划:0-1 背包 vs 分数背包

0-1 背包问题:一个小偷抢劫商店,想带走背包能容纳的最多价值的物品。背包最多能装 磅。商店有 件物品,第 件物品价值 美元、重量 磅。小偷必须对每件物品做出二元选择:要么全拿,要么不拿(不能拿部分)。

分数背包问题:设置相同,但小偷可以拿物品的分数,不必做出二元(0-1)选择。可以把 0-1 背包中的物品想象成金锭,分数背包中的物品想象成金粉。

数值实例

物品重量 (磅)价值 (美元)单位价值 (美元/磅)
110606
2201005
3301204

背包容量: 磅。

【反例论证(按单位价值贪心选物品1+2得160美元,最优为物品2+3得220美元)】 0-1 背包的贪心策略失败

贪心策略按单位价值排序,先选物品1(单位价值6美元/磅最高)。

  • 选物品1(10磅,60美元),剩余容量40磅
  • 再选物品2(20磅,100美元),剩余容量20磅
  • 再选物品3(30磅 > 20磅,放不下)
  • 总价值: 美元

但最优解是选物品2和物品3:

  • 选物品2(20磅,100美元)+ 物品3(30磅,120美元)= 50磅
  • 总价值: 美元

贪心解(160美元)远低于最优解(220美元)。失败原因:选择物品1后,背包有20磅的空闲空间无法利用,降低了装载的有效单位价值。

【贪心成功论证(可取分数恰好装满背包,无空间浪费)】 分数背包的贪心策略成功

贪心策略同样按单位价值排序:

  • 先拿物品1的全部(10磅,60美元),剩余容量40磅
  • 再拿物品2的全部(20磅,100美元),剩余容量20磅
  • 再拿物品3的 (20磅, 美元)
  • 总价值: 美元

由于可以取分数,贪心策略能将背包恰好装满,得到最优解。

【贪心选择性质不成立论证(0-1背包需比较选与不选的子问题解)】 为什么贪心对 0-1 背包失败?

在 0-1 背包中,当考虑是否将某物品放入背包时,必须比较包含该物品的子问题解与排除该物品的子问题解,然后才能做出选择。这种问题表述产生了许多重叠子问题——这是动态规划的标志。因此,0-1 背包问题需要动态规划来求解。

贪心算法设计方法

方法一:详细版 6 步(从 DP 出发,如 15.1 节所用)

  1. 确定问题的最优子结构
  2. 开发递归解法(对于活动选择问题,我们建立了递推式(15.2),但绕过了仅基于该递推式开发递归算法)
  3. 证明如果做出贪心选择,则只剩下一个子问题
  4. 证明做出贪心选择总是安全的(步骤3和4可以互换顺序)
  5. 开发实现贪心策略的递归算法
  6. 将递归算法转换为迭代算法

方法二:简洁版 6 步(直接贪心设计,后续章节使用)

  1. 将优化问题表述为:做出一个选择后,只剩下一个子问题需要求解
  2. 证明原问题总是存在一个做出贪心选择的最优解,因此贪心选择总是安全的
  3. 通过证明以下事实来展示最优子结构:做出贪心选择后,剩下的是一个子问题,且将该子问题的最优解与已做出的贪心选择组合,能得到原问题的最优解

两种方法的关系

详细版方法更侧重于展示贪心算法的动态规划根基。例如,活动选择问题的第一版子问题定义为 (两个下标都变化),然后发现如果总是做出贪心选择,可以将子问题限制为 的形式。简洁版方法则直接从贪心角度出发设计子问题。然而,每个贪心算法的背后,几乎总是存在一个更繁琐的动态规划解法


补充理解与拓展

贪心算法与拟阵理论——贪心最优性的数学基础

一个自然的问题是:什么样的优化问题可以用贪心算法求得最优解? 这个问题有一个精确的数学回答:拟阵(Matroid)。

拟阵是由 Whitney(1935)和 Edmonds(1971)等人发展起来的组合结构。形式化地说,拟阵 由有限集合 的子集族 (称为独立集族)组成,满足:

  1. 遗传性:若 ,则
  2. 交换性:若 ,则存在 使得

Rado-Edmonds 定理:对于任何拟阵 和任何权重函数 ,按权重从大到小贪心地选择元素,总能得到最大权独立集。反之,如果一个组合优化问题上的贪心算法对所有权重函数都能得到最优解,则该问题的独立集族构成一个拟阵。

这意味着拟阵精确刻画了”贪心算法总能成功”的问题类。经典例子:

  • 最小生成树:图的森林构成一个拟阵(图拟阵),Kruskal 算法正是贪心算法在图拟阵上的应用(第21章 最小生成树)
  • 活动选择问题:虽然不直接是拟阵问题,但其贪心选择性质可以用类似的交换论证证明

值得注意的是,0-1 背包问题的独立集族不构成拟阵,因此贪心算法不能保证最优——这与教材中的分析完全一致。

来源:Edmonds, J. (1971). “Matroids and the Greedy Algorithm”, Mathematical Programming; Oxley, J. (2007). “What is a Matroid?”, Notices of the AMS; 南京大学 Advanced Algorithms (2025) 课程讲义

贪心 vs 动态规划——决策流程图

在实际面对一个优化问题时,如何判断应该用贪心还是动态规划?以下决策流程提供了一个系统化的判断方法:

第一步:检查最优子结构

  • 问题能否分解为子问题?子问题的最优解能否组合为原问题的最优解?
  • 如果 → 贪心和DP都不适用,考虑其他方法(如分治、网络流等)
  • 如果 → 进入第二步

第二步:检查贪心选择性质

  • 是否存在一个局部最优选择,使得做出该选择后只需解决一个子问题?
  • 尝试构造反例:找到一个贪心选择导致非最优解的实例
  • 如果反例存在 → 贪心不适用,使用动态规划
  • 如果无法找到反例 → 尝试证明贪心选择性质(通常用替换论证)

第三步:检查重叠子问题

  • 如果使用DP,子问题之间是否有重叠?(同一子问题被多次求解)
  • 如果有重叠 → DP + 备忘录/自底向上表格,效率显著优于朴素递归
  • 如果无重叠 → DP退化为分治,但仍能保证最优性
判断维度贪心算法动态规划
前提条件贪心选择性质 + 最优子结构最优子结构(+ 重叠子问题)
求解方向先选择,后求解(自顶向下)先求解,后选择(自底向上)
选择数量每步只考虑一个选择考虑所有可能的选择
时间复杂度通常 通常 或更高
适用范围较窄(需要贪心选择性质)较广(只需最优子结构)
典型代表活动选择、Huffman、MSTLCS、矩阵链乘法、0-1背包

来源:GeeksforGeeks “Greedy approach vs Dynamic programming”; LeetCopilot “Greedy vs Dynamic Programming: When to Use Each”; Kleinberg & Tardos, “Algorithm Design”, Chapter 4


易混淆点与辨析

误区辨析

误区一:贪心选择性质和最优子结构是同一个东西

不是。它们是两个独立的性质:

  • 贪心选择性质:保证可以在不考虑子问题解的情况下做出正确的选择
  • 最优子结构:保证做出选择后,子问题的最优解能组合成原问题的最优解

0-1 背包问题有最优子结构但没有贪心选择性质,因此贪心算法不适用。分数背包问题同时具有两个性质,因此贪心算法适用。

误区二:贪心算法总是比动态规划快

虽然贪心算法通常更高效(如活动选择中 vs ),但这不是绝对的。贪心算法的效率取决于贪心选择能否高效实现。如果每次贪心选择需要大量计算,贪心算法可能并不比 DP 快。此外,贪心算法只适用于具有贪心选择性质的问题,适用范围比 DP 窄。

误区三:自顶向下 vs 自底向上是贪心和 DP 的唯一区别

虽然求解方向是重要区别,但更本质的区别在于是否需要考察多个选择。DP 需要比较所有可能选择的结果(如 0-1 背包中”选”vs”不选”),而贪心只考虑一个选择(如活动选择中”选最早结束的”)。这个区别决定了贪心算法能否保证最优性。

误区四:贪心算法不需要最优子结构

错误。最优子结构是贪心算法和动态规划的共同前提。贪心算法做出选择后,需要保证剩余子问题的最优解与已做出的选择组合后能得到原问题的最优解。如果没有最优子结构,即使做出了看似合理的贪心选择,也无法保证最终结果的最优性。


习题精选

题号题目描述难度核心考点
15.2-1证明分数背包问题具有贪心选择性质★★★贪心选择性质证明
15.2-2给出 0-1 背包问题的 动态规划解法★★★DP 求解 0-1 背包
15.2-3按重量递增与按价值递减顺序一致时的 0-1 背包★★★★特殊条件下的贪心
15.2-4Gekko 教授滑冰穿越北达科他州的加水站问题★★★贪心策略设计与证明
15.2-5用最少的单位长度闭区间覆盖实轴上的点集★★★区间覆盖贪心
15.2-6 时间求解分数背包问题★★★★线性时间选择算法
15.2-7重排两个集合最大化 ★★★贪心排序策略

视频学习指南

资源链接说明
MIT 6.006 Lecture 12YouTube贪心算法导论 + 设计方法
Abdul Bari - Greedy AlgorithmsYouTube贪心思想直观讲解
Tushar Roy - KnapsackYouTube0-1 背包 vs 分数背包详解
GeeksforGeeks Greedy TutorialsGFG贪心算法全面教程

教材原文

CLRS 第4版 15.2节原文

贪心算法通过做出一系列选择来获得问题的最优解。在每个决策点,算法做出在当前看来最好的选择。这种启发式策略并不总是产生最优解,但在活动选择问题中,有时确实如此。本节讨论贪心方法的一般性质。

我们在 15.1 节中开发贪心算法的过程比通常的情况稍微复杂一些。它包括以下步骤:

  1. 确定问题的最优子结构。
  2. 开发递归解法。
  3. 证明如果做出贪心选择,则只剩下一个子问题。
  4. 证明做出贪心选择总是安全的。(步骤3和4可以互换顺序。)
  5. 开发实现贪心策略的递归算法。
  6. 将递归算法转换为迭代算法。

这些步骤非常详细地展示了贪心算法的动态规划根基。另一种方法是以贪心选择为出发点来构造最优子结构,使得选择后只剩下一个子问题需要求解。更一般地,可以按照以下步骤序列设计贪心算法:

  1. 将优化问题表述为:做出一个选择后,只剩下一个子问题需要求解。
  2. 证明原问题总是存在一个做出贪心选择的最优解,因此贪心选择总是安全的。
  3. 通过证明以下事实来展示最优子结构:做出贪心选择后,剩下的是一个子问题,且将该子问题的最优解与已做出的贪心选择组合,能得到原问题的最优解。

如何判断贪心算法能否解决某个特定的优化问题?没有万能的方法,但贪心选择性质和最优子结构是两个关键要素。如果你能证明问题具有这些性质,那么你就朝着为它开发贪心算法迈出了重要的一步。

贪心选择性质:第一个关键要素是贪心选择性质:你可以通过做出局部最优(贪心)选择来组装全局最优解。换言之,在考虑做哪个选择时,你做出在当前问题中看起来最好的选择,而不考虑子问题的结果。

最优子结构:正如我们在第14章中看到的,如果一个问题的最优解包含了子问题的最优解,则该问题具有最优子结构。这个性质是评估动态规划是否适用的关键要素,对贪心算法也同样至关重要。

贪心与动态规划:由于贪心和动态规划策略都利用最优子结构,你可能会在贪心解法足够时生成动态规划解法,或者反过来,你可能会错误地认为贪心解法有效而实际上需要动态规划解法。为了说明两种技术之间的细微差别,让我们研究一个经典优化问题的两个变体。


参见Wiki


第15章-贪心算法 贪心策略要素