想象你是一位登山者,背着容量有限的背包沿山路向山顶进发,路边散落着闪闪发光的宝石。你只能经过时弯腰捡起某块宝石,然后继续向前,绝不回头。如果每次都捡起眼前最大的一块,最后能得到最大的总价值吗?有时可以,有时却会捡了芝麻丢西瓜。这种“每步都做当前最优选择”的决策方式,就是贪心算法。 贪心算法的魅力在于简单快速,但用错地方就会翻车。它背后需要两个关键支撑:贪心选择性质和最优子结构。从登山拾宝到安排休息站、压缩行李,再到 0-1 背包的反例,本文将用一个登山故事串起这些概念,让你真正理解贪心的力量与边界。

岗位关联

掌握贪心算法能让你在面对优化问题时快速判断“能否贪心、怎么贪心、如何证明”,这是算法设计、技术面试与工程性能优化的核心技能之一。

目录
  1. 可视化演示
  2. 什么是贪心
  3. 贪心选择性质
  4. 最优子结构
  5. 正确性证明
  6. 活动选择问题
  7. 霍夫曼编码
  8. 贪心与动态规划
  9. 贪心失败反例
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示贪心算法策略与正确性证明的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

贪心算法策略与正确性证明:贪心算法 贪心算法核心思想与贪心选择性质 贪心与动态规划的本质区别 核心机制可视化 贪心算法策略与正确性证明整体架构与关键组件

什么是贪心

小林第一次登上这座宝石山,面前出现两条岔路,左边的小宝石离脚边只有一步,右边的大宝石需要多走几步才能捡到。他只够时间捡一块,于是毫不犹豫走向大宝石。这种“此刻看着最划算就选它”的直觉,正是贪心思想的雏形。

把这份直觉变成算法语言:贪心算法在每一步都从当前候选集合中,根据某个评价函数挑出最优决策,然后继续前进,不再修改已经做出的选择。它不像暴力搜索那样把未来所有路线都看一遍,也不像动态规划那样回头比较方案。贪心看重的是“此刻的局部最优”能否自然累积成“全局最优”。

一个完整的贪心算法由三件事定义:候选集合是什么、选择准则是什么、何时终止。候选集合可以是要选的活动、要合并的字符频率、要装入背包的物品;选择准则则决定了“最优”二字的具体含义——最大价值、最早结束时间、最小频率等等。终止条件通常是候选集合为空或达到限制。

小林一路专挑“当下最大”,最终收获是否最大,取决于山路的整体格局。如果每一步的局部最优都被证明不会连累未来,那这条路就能放心走到山顶。

a∗=arg⁡max⁡a∈S w(a)a^* = \arg\max_{a \in S} \ w(a)
这个式子表示:从候选集合 S 中,挑出权重 w 最高的决策 a\*,作为当前这一步的选择。
  1. 候选集合 S表示当前全部可选局面的集合,比如登山者面前所有岔路口的宝石。
    S={a1,a2,…,an}S = \{a_1, a_2, \dots, a_n\}
  2. 权重函数 w(a)给每个决策一个数字,衡量它当下的好处,如宝石的价值、活动的结束时间。
    w:a↦Rw: a \mapsto \mathbb{R}
  3. arg max 运算返回使括号内式子取得最大值的那个自变量取值。
    a∗=arg⁡max⁡a∈Sw(a)a^* = \arg\max_{a\in S} w(a)

贪心选择性质

小林捡起一块大宝石前,心里突然冒出一个问题:这个决定会不会堵死后面更值钱的路线?如果不会,那它就是“安全”的。贪心算法中,这种安全属性被称作贪心选择性质。

贪心选择性质是指:存在至少一个全局最优解,包含了当前这一步的贪心选择。换句话说,贪心做出的局部最优决策,并没有被所有最优方案排斥。有了这个性质,你才敢在岔路口不回头地走下去,因为你选的这条路一定能延伸到某个最优出口。

验证贪心选择性质通常用反证法或交换论证。假设某个最优解不包含贪心选择 a\*,那么可以把它替换进最优解,并调整后续内容,使总价值不下降,从而证明贪心选择一定属于某个最优解。这个过程有点像小林在山路上遇到一条所谓“最佳路线”,他把自己的选择塞进去,发现路线依然不输给原来的方案。

小林每捡起一块宝石都要确认它能出现在“理想收获清单”上。这正是“无悔选择”的底气:不是盲目乐观,而是数学上证明过它安全。

a∗∈OPT⁡(S)a^* \in \operatorname{OPT}(S)
这个式子表达:贪心选出的 a\* 属于集合 S 的某个最优解 OPT(S),这是贪心算法正确的核心前提。
  1. OPT(S)表示候选集合 S 的所有合法解中,目标函数值最大的那一个解。
    OPT⁡(S)=arg⁡max⁡X⊆Sval⁡(X)\operatorname{OPT}(S) = \arg\max_{X \subseteq S} \operatorname{val}(X)
  2. ∈ 符号读作“属于”,表示 a\* 是 OPT(S) 这个解中包含的一个决策。
    a∗∈OPT⁡(S)a^* \in \operatorname{OPT}(S)
  3. 安全选择的含义如果每一步都能找到这样的 a\*,贪心算法就可以放心地选定它,无需回溯。
    贪心安全  ⟺  ∃O∈OPT⁡(S),  a∗∈O\text{贪心安全} \iff \exists O \in \operatorname{OPT}(S),\; a^* \in O

最优子结构

小林捡起一块大宝石后,剩下的山路依然蜿蜒向前,面前依旧有岔路和宝石,只是起点变了。如果整段山路的“最优收获”可以由“第一步决策 + 剩余山路的最优收获”组成,就称这个问题具有最优子结构。

最优子结构是贪心和动态规划共同的地基:一个问题的最优解,包含其子问题的最优解。设当前候选集合为 S,小林选择 a\ 后,剩余可再选的集合记为 S′,那么最终总收益等于 a\ 的收益加上 S′ 上的最优收益。

这个递推关系和动态规划的结构完全一致,但贪心不需要比较所有第一步选择——贪心选择性质已经锁定了 a\*。因此贪心算法不会像动态规划那样维护一张状态表,而是沿着唯一一条路径不断递归“小一号的自己”。

山路再长,每一步之后都是一个更新版本的“小登山”。最优子结构保证了这种分而治之的想法不会因为局部拆分而丢失全局最优。

OPT⁡(S)=w(a∗)+OPT⁡(S′)\operatorname{OPT}(S) = w(a^*) + \operatorname{OPT}(S')
这个式子说明:S 的最优解等于第一步贪心选择的收益 w(a\*),加上剩余候选 S′ 的最优解收益。
  1. 剩余候选 S′做出选择 a\ 后,从 S 中剔除与 a\ 冲突的所有元素,剩下的集合。
    S′=S∖{a∈S∣a 与 a∗ 冲突}S' = S \setminus \{a \in S \mid a \text{ 与 } a^* \text{ 冲突}\}
  2. 收益累加总收益由当前选择收益和子问题最优收益两部分相加。
    OPT⁡(S)=w(a∗)+OPT⁡(S′)\operatorname{OPT}(S) = w(a^*) + \operatorname{OPT}(S')
  3. 递归结构不断代入 OPT(S′),会得到一条从山顶到山脚的选择链。
    OPT⁡(S)=w(a1∗)+w(a2∗)+⋯+OPT⁡(∅)\operatorname{OPT}(S) = w(a_1^*) + w(a_2^*) + \cdots + \operatorname{OPT}(\emptyset)

正确性证明

小林向同伴炫耀自己捡到的宝石,同伴不服,拿出一个所谓的“最优路线”。小林发现两条路线第一个不同点后,悄悄把同伴的方案改成自己的选择,结果总价值没有减少——同伴承认小林这条路也是最优的。这种改换思路,就是交换论证。

证明贪心算法正确,常用的工具有交换论证、数学归纳法和反证法。交换论证的核心是:任取一个最优解,找到第一个与贪心解不同的决策,把它替换成贪心选择,再验证改变后的解仍然合法且目标值不劣于原最优解。反复替换,最终最优解可以被改造成贪心解,于是贪心解必然也是最优解。

数学归纳法则按问题规模 n 进行归纳:基础情形显然正确;若贪心策略对 n-1 规模正确,且贪心选择性质保证第一步决策安全,那么对 n 规模也正确。反证法常用于证明某些选择“不可能不在最优解中”,一旦假设它在会导致矛盾,贪心选择性质就得到验证。

出发前先证明每一段路都处在某条最佳路线上,之后只要沿着选好的路走就不会输。交换论证正是这样一份“路线合格证”。

val⁡(O′)≥val⁡(O)\operatorname{val}(O') \ge \operatorname{val}(O)
这个式子表示:把最优解 O 交换成包含贪心选择的 O′ 后,总价值不下降,因此 O′ 也是最优解。
  1. O 与 O′O 是某个最优解,O′ 是将 O 中第一个选择替换成贪心选择后得到的新解。
    O′=(O∖{oj})∪{aj}O' = (O \setminus \{o_j\}) \cup \{a_j\}
  2. val 函数计算一个解的目标函数值,例如宝石总价值或覆盖区间数量。
    val⁡(X)=∑x∈Xw(x)\operatorname{val}(X) = \sum_{x \in X} w(x)
  3. 不等式成立的原因因为贪心选择的结束时间或权重更优,不会减少后续可选空间,所以替换后价值不降。
    val⁡(O′)≥val⁡(O)  ⟹  O′∈OPT⁡\operatorname{val}(O') \ge \operatorname{val}(O) \implies O' \in \operatorname{OPT}

活动选择问题

林登山路上遇到 11 个补给站,每个站有对外开放的时间区间,他希望能拜访尽量多的补给站。如果选了一个很早开始却拖到很晚才结束的站,后面的站点可能全被错过;反过来,先选最早关门的站,就能给后面留下最多时间。

这就是经典的活动选择问题:给定 n 个时间区间,选出最多互不重叠的区间。贪心策略是:把所有活动按结束时间从小到大排序,逐个扫描,只要当前活动开始时间不早于上一次选定活动的结束时间,就选择它。这种“先选最早结束”的准则听起来有些反直觉,却恰恰是安全且最优的。

为什么成立?设 a1 是所有活动中结束时间最早的。任取一个最优解,其中最早结束的活动是 ak。由于 a1 的结束时间不晚于 ak 的结束时间,用 a1 替换 ak 后,其他被选活动依然不会与 a1 重叠,于是仍然得到一个最优解。接着对剩余活动重复同样的推理,贪心选择性质就通过归纳建立起来。

小林果断放弃拖到很晚的“大馅饼”,先打卡最早关门的休息站,结果拜访的站点数量最多。这正是贪心在区间调度问题上的经典胜利。

f(a1)≤f(ak)f(a_1) \le f(a_k)
这个式子说明:最早结束的活动 a1 的结束时间,不晚于某个最优解中最先结束的活动 ak,因此可以用 a1 安全替换 ak。
  1. f(a)表示活动 a 的结束时间,a1 是全体活动中结束时间最早的那个。
    a1=arg⁡min⁡if(ai)a_1 = \arg\min_{i} f(a_i)
  2. 替换可行性由于最优解中其他活动开始时间都不早于 f(ak),而 f(a1) ≤ f(ak),所以替换后依然互不重叠。
    ∀a∈O∖{ak}:sa≥f(ak)≥f(a1)\forall a \in O \setminus \{a_k\}: s_a \ge f(a_k) \ge f(a_1)
  3. 归纳结论从 a1 开始,剩余问题规模缩小,重复同样策略即可得到全局最优解。
    OPT⁡({1,…,n})={a1}∪OPT⁡({i∣si≥f(a1)})\operatorname{OPT}(\{1,\dots,n\}) = \{a_1\} \cup \operatorname{OPT}(\{i \mid s_i \ge f(a_1)\})

霍夫曼编码

下山后小林要把一大袋宝石分装进行李箱,每个小袋重量不同。他决定每次把两个最轻的袋子合并成一个大袋,重复到只剩一袋,这样总搬运工作量最小。这种“优先合并最轻”的直觉,正是霍夫曼编码的贪心思路。

霍夫曼编码解决的是数据传输中的压缩问题:给 n 个字符设计互不为前缀的二进制编码,使出现频率高的字符编码短、频率低的字符编码长,最终让总编码长度最短。所有编码可以组织成一棵二叉树,每个叶子代表一个字符,叶子到根的距离就是编码长度,左右分支对应 0 和 1。

贪心算法从所有字符的“频率节点”出发,每次挑选频率最小的两个节点合并成一个新节点,频率等于两者之和,直到只剩一个根节点。合并过程中,频率最低的字符会沉到最深的叶子位置,频率最高的字符留在浅层,恰好满足最优前缀码的要求。这个策略同样可以用交换论证证明全局最优。

合并最轻的两袋,就像把低频字符放在树的深处。每次只做最省力的合并,最终整棵树的搬运总成本最低,这正是贪心在数据压缩领域的经典名片。

WPL⁡(T)=∑i=1nwi⋅di\operatorname{WPL}(T) = \sum_{i=1}^{n} w_i \cdot d_i
这个式子定义带权路径长度:每个字符频率 w_i 乘以它到根节点的距离 d_i,再全部求和,霍夫曼编码要最小化这个值。
  1. 频率 w_i字符 i 在数据中出现的次数,作为树叶的权值。
    wi=字符 i 的出现次数w_i = \text{字符 } i \text{ 的出现次数}
  2. 深度 d_i叶子节点到根节点的边数,也就是该字符编码的二进制位数。
    di=depth⁡(leafi)d_i = \operatorname{depth}(\text{leaf}_i)
  3. 贪心合并操作每次取权值最小的两个节点 x、y,合并为权值 w(x)+w(y) 的新节点 z。
    w(z)=w(x)+w(y)w(z) = w(x) + w(y)

贪心与动态规划

假设小林的朋友带着全息地图登山,每到岔路都会展开地图,把每条支路的宝石收获都记录下来,走错还能返回。不带地图的小林则凭直觉选一条路就继续走。两人最终都可能登顶,但带地图的人更“万无一失”,不带地图的人更轻快。

这对应的就是动态规划与贪心算法的关系。动态规划求解最优化问题时,会枚举当前所有可能选择,计算每个选择带来的子问题最优解,并缓存重叠子问题的结果;贪心算法一旦选出 a\*,就立刻沿着这条分支递归,不比较其他分支,也不需要状态表。

什么时候可以省掉那副重地图?答案是问题同时满足贪心选择性质和最优子结构。动态规划只需要最优子结构和重叠子问题,而贪心额外要求“局部最优一定通往全局最优”。从这个角度看,贪心是动态规划在更强条件下退化成的一路追击策略。

山路千万条,动态规划要探索全部,贪心若证明了一条路永远不差,就能轻装快步直达山顶。两者不是对立,而是不同假设下的两种打法。

OPT⁡(S)=max⁡a∈S{w(a)+OPT⁡(Sa)}\operatorname{OPT}(S) = \max_{a \in S} \big\{ w(a) + \operatorname{OPT}(S_a) \big\}
这是动态规划的通用递推式:遍历所有第一步决策 a,取收益最大者;贪心则直接把 max 替换成由贪心选择性质锁定的 a\*。
  1. S_a选择 a 后剩余的可选集合,不同决定会得到不同的 S_a。
    Sa={b∈S∣b 与 a 相容}S_a = \{b \in S \mid b \text{ 与 } a \text{ 相容}\}
  2. max 的作用动态规划需要比较所有候选 a,因为无法提前知道哪个最优。
    OPT⁡(S)=max⁡{w(a1)+OPT⁡(Sa1), w(a2)+OPT⁡(Sa2), …}\operatorname{OPT}(S) = \max\big\{ w(a_1)+\operatorname{OPT}(S_{a_1}),\, w(a_2)+\operatorname{OPT}(S_{a_2}),\, \dots \big\}
  3. 贪心的简化若贪心选择性质保证存在固定的 a\* 使上式 max 一定落在它身上,就不用枚举了。
    OPT⁡(S)=w(a∗)+OPT⁡(Sa∗)\operatorname{OPT}(S) = w(a^*) + \operatorname{OPT}(S_{a^*})

贪心失败反例

小林在山路上发现一颗巨大的宝石,漂亮但也很重,背包只剩下最后一点空间。他毫不犹豫地捡起它,结果后面出现一大片小宝石,虽然单颗不如大宝石,但合起来价值更高,却都塞不进去了。这就是贪心失败的真实模样。

0-1 背包问题是最经典的反例:每个物品要么整体拿走要么不拿,不能切割。若按单位价值从大到小贪心,很可能在最后因为剩余空间不足而放弃一个“小组合”,让总价值低于动态规划选出的一组物品。分数背包允许取一部分,所以按单位价值贪心反而正确。

两者唯一的差别在于决策变量是连续还是整数:0-1 背包要求 x_i ∈ {0,1},选择一件物品可能改变剩余的可用空间结构;贪心只看到当前单位价值最高,却看不到它对后续组合的破坏。这说明贪心策略的适用边界取决于选择是否会强烈限制未来。

登山路上不能只盯着最大宝石,更要看它占用的空间是否值得。贪心的边界,恰恰藏在“一个选择对未来的结构影响”里。

max⁡∑i=1nvixi,s.t. ∑i=1nwixi≤C,xi∈{0,1}\max \sum_{i=1}^{n} v_i x_i, \quad \text{s.t.} \ \sum_{i=1}^{n} w_i x_i \le C, \quad x_i \in \{0, 1\}
这个公式是 0-1 背包的数学形式:目标是最大化总价值 v_i x_i,但每个 x_i 只能是 0 或 1,这种整数约束正是贪心失效的原因。
  1. v_i 与 x_iv_i 是物品价值,x_i 是 0/1 决策变量,0 表示不放,1 表示放入。
    xi∈{0,1}x_i \in \{0, 1\}
  2. 容量约束所有放入物品的重量总和不能超过背包容量 C。
    ∑i=1nwixi≤C\sum_{i=1}^{n} w_i x_i \le C
  3. 贪心为何失败按单位价值 v_i/w_i 降序选择时,可能出现剩余容量放不下最优组合的情况,导致局部最优掩盖了全局更优。
    max⁡x∈{0,1}n∑vixi≠贪心按单位价值的解\max_{x\in\{0,1\}^n} \sum v_i x_i \ne \text{贪心按单位价值的解}

AI 应用场景

会议室智能预订系统

阶段:任务调度

解决的问题:传统会议室预订通常“先到先得”,用户随手占用长时段,导致一天内能接待的会议数量大幅下降。AI 算法可将所有预订请求视作时间区间,按结束时间最早策略挑选互不冲突的会议,最大化会议室使用次数。

具体案例:某公司内部会议室系统每天收到数百条预订请求。系统将所有请求按结束时间排序,贪心扫描并推荐一组互不冲突的预订,使会议室日均使用场次从 9 场提升到 14 场,类似登山者优先打卡最早关门的补给站。

日志压缩服务

阶段:数据预处理与压缩

解决的问题:服务端每天产生海量日志,直接传输和存储成本高昂。霍夫曼编码按字符出现频率动态构造最优前缀码,用最短编码表示最频繁的字符,能显著减少日志体积。

具体案例:一个日志分析平台对相同格式的重复日志做字符频率统计,再调用霍夫曼编码生成私有压缩字典,日志传输量下降约 45%,解压后的数据仍可原样分析。就像下山后把最轻的行李袋优先合并,搬运总工作量最低。

算法选型中的贪心预判

阶段:模型推理与优化

解决的问题:许多组合优化问题(如特征选择、资源分配)规模庞大,直接动态规划不可行。工程师先用贪心选择性质快速判断能否用贪心取得最优,或用贪心快速生成高质量初始解,再配合局部搜索逼近最优。

具体案例:在广告投放预算分配中,系统先用单位收益贪心给每个渠道分配初步预算,再微调边缘渠道,比全量枚举快数百倍,且收益损失在 2% 以内。这就像登山者用“每步当前最优”先快速走通路线,再回头微调。

Python 演示

python21 行
def greedy_activity_selection(activities):    # 按结束时间升序排列    activities.sort(key=lambda x: x[1])    selected = []    last_end = 0    for start, end in activities:        if start >= last_end:            selected.append((start, end))            last_end = end    return selected  if __name__ == '__main__':    # (开始时间, 结束时间)    meetings = [        (1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9),        (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)    ]    ans = greedy_activity_selection(meetings)    print("最多可安排会议数:", len(ans))    print("选择的会议:", ans)
目的:实现活动选择问题的贪心算法,用“结束时间最早优先”的策略选出最多互不重叠的会议。
  1. activities.sort(key=lambda x: x[1])按结束时间升序排列,保证每一步优先考察最早结束的会议。
  2. for start, end in activities:按排序后的顺序逐个扫描候选会议。
  3. if start >= last_end:当前会议开始时间不早于上一选中会议的结束时间,则兼容。
  4. selected.append((start, end))把兼容的会议放入结果集合。
  5. last_end = end更新“最近一次选中会议的结束时间”,作为后续判断基准。

常见误区

误区贪心算法不就是每次挑价值最大的东西吗?比如背包问题按价值从大到小拿准没错。

正解贪心的“优”取决于选择准则,不一定是数值最大。活动选择找的是“结束时间最早”,霍夫曼编码找的是“频率最小”。更重要的是,贪心策略必须证明安全,否则即便每一步都在取最大值,也可能得不到全局最优。

为何常见:“贪心”这个词本身暗示了攫取最大值,加上许多入门题目恰好可以用最大/最小策略解决,容易让直觉代替证明。

误区既然贪心快又简单,那只要题目难,直接贪心就对了,反正能得到最优答案。

正解贪心只对满足贪心选择性质和最优子结构的问题保证最优。0-1 背包、旅行商等问题用贪心只能得到近似解。判断能否贪心,靠的是性质证明,而不是问题规模或难度。

为何常见:教材和题库里大量问题恰好适用贪心,练多了会产生“贪心万能”的错觉,忽略了反例的存在。

误区贪心和动态规划都做递推,那贪心是不是动态规划剪枝后加了缓存?

正解两者决策方式本质不同。动态规划会枚举当前所有选择并保存重叠子问题结果,贪心通过贪心选择性质直接锁定一个选择,不枚举、不缓存。贪心是动态规划在更强前提下的特例,而不是简单的实现优化。

为何常见:二者都依赖最优子结构,递推式长得像,初学者容易以为只是优化层面不同。

误区活动选择要先排序,霍夫曼编码要用堆排序,所以排序就是贪心的关键。

正解排序只是实现贪心选择的工具,真正的关键是证明“按这个顺序取第一个”是安全的。排序本身不产生正确性,很多排序后直接取的问题依然会失败,比如按单位价值排序取 0-1 背包。

为何常见:两个经典贪心问题恰好都涉及排序/堆操作,让人把“排序”误当成贪心的充分条件。

面试题

Q1. 贪心算法的核心特征是什么?

单选题 · easy

  • A.穷举所有可能解
  • B.每一步选择当前看起来最优的解← 正确答案
  • C.随机选择以降低复杂度
  • D.先求解所有子问题再合并

答案:B。贪心算法的关键在于每步只看当前候选集,做出局部最优选择后不再回溯。A 是暴力搜索,C 是随机化算法,D 更像动态规划,均不符合贪心的定义。

Q2. 活动选择问题中,贪心策略选择的排序依据是什么?

单选题 · mid

  • A.开始时间最早
  • B.持续时间最短
  • C.结束时间最早← 正确答案
  • D.价值密度最高

答案:C。按结束时间最早排序,能尽量为后续活动留下更多时间窗口。开始时间早或持续时间短的策略都可能选中一个晚结束的活动,从而错过后续大量兼容区间。

Q3. 一个问题已经具备最优子结构,还要满足什么条件才能用贪心算法?

单选题 · mid

  • A.存在重叠子问题
  • B.满足贪心选择性质← 正确答案
  • C.数据规模足够大
  • D.输入已经排好序

答案:B。贪心算法需要贪心选择性质,即存在某个全局最优解包含当前的局部最优选择。重叠子问题是动态规划的需求,排序只是实现手段,均不能替代这一性质。

Q4. 证明贪心算法正确性时,最常用的方法是什么?

单选题 · mid

  • A.模拟退火
  • B.交换论证← 正确答案
  • C.蒙特卡洛模拟
  • D.随机化分析

答案:B。交换论证通过把最优解中的某个决策替换成贪心选择,证明替换后解不劣于原最优解,从而说明贪心解也是最优的。其余选项都不是严格的正确性证明方法。

Q5. 霍夫曼编码每次合并的两个节点是哪些?

单选题 · mid

  • A.频率最大的两个节点
  • B.频率最小的两个节点← 正确答案
  • C.深度最深的两个节点
  • D.任意两个节点

答案:B。合并频率最小的两个节点,才能让低频字符处于较深位置、高频字符处于较浅位置,从而最小化带权路径长度。合并最大频率会让高频字符编码变长,远离最优。

Q6. 为什么 0-1 背包问题按单位价值贪心可能失败?

单选题 · hard

  • A.因为物品价值太小
  • B.因为决策变量是 0 或 1,剩余容量无法被任意小物品填补← 正确答案
  • C.因为物品数量太多
  • D.因为物品重量完全相同

答案:B。0-1 背包不允许取物品的一部分,贪心按单位价值选完一个物品后,剩余容量可能放不下任何更优组合。分数背包可以切割,因此贪心有效,这正说明整数约束是贪心失效的关键。

Q7. 以下哪些方法可以用于证明贪心算法的正确性?

多选题 · hard

  • A.交换论证← 正确答案
  • B.数学归纳法← 正确答案
  • C.反证法← 正确答案
  • D.主定理

答案:ABC。交换论证证明贪心解不劣于最优解,数学归纳法按问题规模归纳贪心选择的递推性,反证法通过矛盾证明贪心选择安全。主定理用于分析分治算法时间复杂度,不用于证明贪心正确。

Q8. 下列哪些问题适合用贪心算法得到全局最优解?

多选题 · extreme

  • A.活动选择问题← 正确答案
  • B.霍夫曼编码← 正确答案
  • C.0-1 背包问题
  • D.分数背包问题← 正确答案

答案:ABD。活动选择满足贪心选择性质,霍夫曼编码通过合并最小频率构造最优前缀码,分数背包允许取部分物品所以按单位价值贪心有效。0-1 背包因决策变量为整数,贪心只能得到近似解,需要动态规划。