<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>算法 on 扎塔-Zata</title><link>https://www.zata.cc/categories/%E7%AE%97%E6%B3%95/</link><description>Recent content in 算法 on 扎塔-Zata</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>扎塔-Zata</copyright><lastBuildDate>Wed, 11 Nov 2026 00:15:45 +0800</lastBuildDate><atom:link href="https://www.zata.cc/categories/%E7%AE%97%E6%B3%95/index.xml" rel="self" type="application/rss+xml"/><item><title>贪心算法：原理、证明与复杂度</title><link>https://www.zata.cc/p/greedy-algorithms/</link><pubDate>Mon, 26 Oct 2026 02:22:00 +0800</pubDate><guid>https://www.zata.cc/p/greedy-algorithms/</guid><description>&lt;img src="https://www.zata.cc/p/greedy-algorithms/images/index/index.svg" alt="Featured image of post 贪心算法：原理、证明与复杂度" />&lt;p>关于贪心算法的中文材料，绝大多数长成同一个样子：先给一句直觉——每步选当前最好的；再列一串经典例子（活动安排、Huffman、最小生成树）；最后用一句「可以证明这是正确的」收尾。读完的人记住了案例，但没有获得判断新问题的能力。下次遇到一个没见过的贪心策略，仍然只能靠试。&lt;/p>
&lt;p>这份教程换一条路。它把「贪心是否正确」当成一个&lt;strong>可判定的命题&lt;/strong>来处理，而不是一种需要灵感的技巧。下面是我在写这份 50 页教程时形成的几个核心结论，以及它们为什么值得单独成篇。&lt;/p>
&lt;hr>
&lt;h2 id="一贪心不是算法是算法模板">一、贪心不是算法，是算法模板
&lt;/h2>&lt;p>大多数人把贪心当成一类算法，但它其实是&lt;strong>一族共享同一骨架的算法&lt;/strong>。把骨架写出来之后会发现，所谓不同的贪心策略，差别只落在五元组里的两三个位置上：&lt;/p>
\[
\mathcal{G} = (E,\ \sigma,\ \kappa,\ \mathcal{F},\ S_0)
\]&lt;ul>
&lt;li>\(E\)：候选元素的全集；&lt;/li>
&lt;li>\(\sigma: E \to \mathbb{R}\)：评价函数，给出优先级（这一步决定了「贪」在哪）；&lt;/li>
&lt;li>\(\kappa: 2^E \to \{0,1\}\)：可行性判定；&lt;/li>
&lt;li>\(\mathcal{F} = \{S \subseteq E : \kappa(S) = 1\}\)：可行域，通常向下封闭；&lt;/li>
&lt;li>\(S_0 = \varnothing\)：初始状态。&lt;/li>
&lt;/ul>
&lt;p>算法就是反复执行「按 \(\sigma\) 降序扫描，若 \(S \cup \{e\}\) 可行则并入」这一条规则。&lt;/p>
&lt;p>这个视角带来的第一个好处是：&lt;strong>三个经典问题的差别被压缩到了一个参数上&lt;/strong>。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题&lt;/th>
&lt;th>评价函数 \(\sigma\)&lt;/th>
&lt;th>结果&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>分数背包&lt;/td>
&lt;td>单位重量价值 \(v_i/w_i\)&lt;/td>
&lt;td>最优&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0-1 背包&lt;/td>
&lt;td>单位重量价值 \(v_i/w_i\)&lt;/td>
&lt;td>&lt;strong>错误&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>带截止期的任务调度&lt;/td>
&lt;td>截止期 \(d_i\) 升序&lt;/td>
&lt;td>最优&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>三者的代码结构完全一致——排序、遍历、判定、接受。分数背包与 0-1 背包用的是&lt;strong>同一个评价函数&lt;/strong>，一个对、一个错。这说明正确性根本不由评价函数决定，而由可行域 \(\mathcal{F}\) 的代数结构决定。这是全文的出发点。&lt;/p>
&lt;h2 id="二拟阵唯一一个充要条件">二、拟阵：唯一一个充要条件
&lt;/h2>&lt;p>如果问「什么样的可行域能让贪心对&lt;strong>任意&lt;/strong>权重都返回最优」，答案是唯一的，而且可以完整刻画。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>定理（Rado–Edmonds）&lt;/strong> 独立系统 \(M = (E, \mathcal{I})\) 上按权重降序的贪心算法对所有 \(w: E \to \mathbb{R}_{\ge 0}\) 都返回最大权基，当且仅当 \(M\) 是拟阵。&lt;/p>
&lt;/blockquote>
&lt;p>拟阵只需要三条公理：非空、遗传（向下封闭）、交换（\(|A| &lt; |B|\) 时存在 \(e \in B \setminus A\) 使 \(A \cup \{e\}\) 仍独立）。第三条是全部力量的来源——它保证了「局部的贪心选择总能被某个全局最优解容纳」。&lt;/p>
&lt;p>这个定理的价值在于它是&lt;strong>双向&lt;/strong>的。正向告诉你：识别出拟阵，正确性就免费了，不需要再写交换论证。最小生成树就是最好的例子——Kruskal 与 Prim 的正确性可以完全由「图拟阵」这三个字推出，割性质那套证明在逻辑上是冗余的（第 4 章仍然给了一遍，因为那套证法对理解结构更有帮助）。&lt;/p>
&lt;p>反向更狠：&lt;strong>不是拟阵，就必然存在一组权重让贪心失败&lt;/strong>。这不是「我们的策略没设计好」，而是结构层面的判决。所以遇到贪心失效时，正确的动作不是换评价函数继续试，而是先判断可行域是不是拟阵。&lt;/p>
&lt;h2 id="三三条失效边界">三、三条失效边界
&lt;/h2>&lt;p>拟阵之外的世界很大，但有三个边界特别值得记住，因为几乎所有「贪心错了」的案例都落在它们附近。&lt;/p>
&lt;p>&lt;strong>边界一：Huffman 不在其中。&lt;/strong> 最优前缀码的可行解是二叉树结构，不是 \(E\) 的子集。\(\mathcal{F}\) 在这里连定义都没有，拟阵理论完全失效。它的正确性只能靠交换论证。&lt;/p>
&lt;p>&lt;strong>边界二：背包不在其中。&lt;/strong> 容量约束下的 0-1 背包不满足交换公理：取 \(W=10\)，\(A=\{w=9\}\)，\(B=\{w=5, w=4\}\)，则 \(|A| &lt; |B|\) 但 \(A\) 无法再扩展。于是比值贪心确实会失败，且这个失败是结构性的。&lt;/p>
&lt;p>&lt;strong>边界三：最短路不在其中。&lt;/strong> 路径不能构成子集族意义上的独立系统——两条路径的并集通常不是路径。Dijkstra 的正确性依赖的是&lt;strong>非负边权&lt;/strong>这一数值条件，而不是任何组合结构。&lt;/p>
&lt;h2 id="四交换论证覆盖剩下的一切">四、交换论证：覆盖剩下的一切
&lt;/h2>&lt;p>不在拟阵里的问题，正确性怎么证？答案是一个统一的模板，我把它的证明负担压缩到了一句话：&lt;/p>
&lt;blockquote>
&lt;p>给定一个已经包含前 \(i-1\) 步选择的最优解，&lt;strong>能否改造它使其也包含第 \(i\) 步的选择，且不损失最优性&lt;/strong>。&lt;/p>
&lt;/blockquote>
&lt;p>只要能对每个 \(i\) 回答「能」，贪心就正确。所有交换论证——Huffman 的最深兄弟、Kruskal 的割交换、Dijkstra 的前缀替换——都是这一句话的不同实现方式。&lt;/p>
&lt;p>这个模板还有一个不太被强调的推论：&lt;strong>贪心输出的必然是一个极大可行解&lt;/strong>。因为任何被拒绝的元素在它被考察的那一刻就已经不可加入，而向下封闭性保证了之后更不可能。所以「最优解包含 \(G\)」与「\(G\) 极大」两条一夹，就只剩 \(G\) 本身。&lt;/p>
&lt;h2 id="五失效之后近似比与竞争比">五、失效之后：近似比与竞争比
&lt;/h2>&lt;p>拟阵覆盖不到的问题里，贪心往往不能给出最优解，但能给出&lt;strong>有保证的近似解&lt;/strong>。这时候讨论的对象从「对不对」变成「差多少」。&lt;/p>
&lt;p>集合覆盖的贪心给出 \(H_n \approx \ln n\) 的近似比，且这个界是紧的；带基数约束的次模最大化给出 \(1 - 1/e \approx 0.632\) 的保证因子，Nemhauser 证明了这也是最优的——任何多项式算法都做不到更好。这两个数字值得记住，因为它们划定了贪心在组合优化里的能力上限。&lt;/p>
&lt;p>在线场景又是另一种困难：不是算不动，而是&lt;strong>不知道未来&lt;/strong>。竞争比衡量的是「与离线最优的比值」。缓存替换的 LRU 有 \(k\) 竞争比且对所有确定性算法都是最优的，但随机化可以把界压到 \(O(\log k)\)——信息缺失造成的损失，可以用随机性部分买回来。&lt;/p>
&lt;p>这两种「最坏情况」形式相同、来源完全不同，第 5、6 章对照阅读会更清楚。&lt;/p>
&lt;h2 id="六还有一件事实现层面">六、还有一件事：实现层面
&lt;/h2>&lt;p>理论正确不等于代码正确。第 7 章专门处理那些让贪心在工程里悄悄出错的细节：比较器破坏传递性导致的排序未定义行为、用 \(\epsilon\) 比较浮点数破坏全序、平局处理对结果一致性的影响、以及「贪心选择性质」在增量更新场景下的失效。&lt;/p>
&lt;p>其中最容易踩的是第一条。\(\epsilon\) 容差比较看起来温和，实际上它连传递性都不满足，而排序算法的正确性前提就是严格弱序——一旦破坏，结果是未定义行为，不是「差不多正确」。&lt;/p>
&lt;hr>
&lt;h2 id="关于这份-pdf">关于这份 PDF
&lt;/h2>&lt;p>上面这些结论的完整推导、证明与习题，整理成了一份 50 页的教程：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>8 章 + 附录&lt;/strong>，52 个小节；&lt;/li>
&lt;li>&lt;strong>42 条定理、引理、定义与推论&lt;/strong>，全部带证明，共 30 处证明；&lt;/li>
&lt;li>&lt;strong>61 个编号公式&lt;/strong>，覆盖拟阵的秩函数、Kraft 不等式、近似比上界、竞争比分析；&lt;/li>
&lt;li>&lt;strong>35 道习题&lt;/strong>，附解答提示；&lt;/li>
&lt;li>5 幅图、8 张表，含定理索引与符号表。&lt;/li>
&lt;/ul>
&lt;div style="margin: 2.2em 0; padding: 1.4em 1.6em; border-left: 3px solid var(--card-separator-color, #d8d8d8); background: var(--card-background, rgba(127,127,127,0.06)); border-radius: 4px;">
&lt;div style="font-weight: 600; margin-bottom: .5em;">贪心算法：原理、证明与复杂度&lt;/div>
&lt;div style="font-size: .92em; opacity: .78; margin-bottom: 1em;">A4 · 50 页 · 3.7 MB · 中文&lt;/div>
&lt;a href="greedy-algorithms-handbook.pdf" download style="display: inline-block; padding: .5em 1.1em; border: 1px solid currentColor; border-radius: 6px; text-decoration: none; font-size: .95em;">下载 PDF&lt;/a>
&lt;/div>
&lt;h3 id="目录结构">目录结构
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>章&lt;/th>
&lt;th>内容&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>第 1 章&lt;/td>
&lt;td>贪心的形式化框架：五元组模型、决策序列、冲突与可回溯性&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 2 章&lt;/td>
&lt;td>拟阵：贪心最优性的代数刻画（Rado–Edmonds），三条失效边界&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 3 章&lt;/td>
&lt;td>交换论证：通用证明模板，Huffman 的压缩论证&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 4 章&lt;/td>
&lt;td>图上的贪心：Kruskal、Prim、Dijkstra 的割性质与正确性&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 5 章&lt;/td>
&lt;td>近似：集合覆盖的 \(H_n\) 界、次模的 \(1-1/e\) 界&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 6 章&lt;/td>
&lt;td>在线与随机：竞争比、LRU 的 \(k\) 界、随机化的收益&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 7 章&lt;/td>
&lt;td>复杂度工程：平局、浮点精度、比较器陷阱&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>第 8 章&lt;/td>
&lt;td>五个完整案例 + 习题解答提示&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>附录&lt;/td>
&lt;td>定理索引、符号表、参考文献、排版说明&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="三条阅读路线">三条阅读路线
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>想尽快用起来&lt;/strong>：1 → 3 → 7 → 8。先建判定框架，再学交换论证，最后处理实现陷阱。&lt;/li>
&lt;li>&lt;strong>关心为什么对&lt;/strong>：2 → 3 → 5。拟阵给代数解释，交换论证覆盖非拟阵，近似比处理失效补偿。&lt;/li>
&lt;li>&lt;strong>做近似与在线&lt;/strong>：5 → 6 对照读，注意两种「最坏情况」的区别。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="一点排版上的说明">一点排版上的说明
&lt;/h2>&lt;p>这份 PDF 的产生流程本身也是「用合适的工具做合适的事」的例子。内容以 Markdown 撰写；公式由自建的一个 LaTeX 子集转换器编译成 MathML（覆盖分式、根式、求和与积分、矩阵环境、定界符自动伸缩）；最后由 Chromium 排版引擎按 A4 输出，页边距、页眉页脚与分页规则由 CSS Paged Media 控制。中文正文用宋体，西文与数学符号用 STIX Two，标题用黑体，行内公式与汉字间距做了紧缩处理。&lt;/p>
&lt;p>之所以没用现成的排版链，是因为需要在离线环境下同时满足三个要求：数学公式接近 LaTeX 的质量、中文字体与标点的正确处理、以及可脚本化的批量修订。三者叠加之后，这条路反而最短。&lt;/p></description></item></channel></rss>