Skip to content

7.3 拟阵理论——贪心正确性的数学充分条件

这一节回答什么问题?

贪心算法没有一个通用的正确性证明模板——每个问题的贪心策略都需要单独证明。但问题是:有没有一个数学框架,能一劳永逸地判断贪心是否有效?

答案是:有,叫做拟阵(Matroid)。

但有个陷阱:拟阵只是充分条件,不是必要条件。有些问题不是拟阵,贪心仍然有效。


拟阵的直觉

拟阵是对"独立性"的抽象。什么意思?

想象你在玩积木:

  • 你可以任意选择积木组合,只要它们"独立"——不冲突
  • 独立集可以扩展:如果小集合独立,大集合也可能独立
  • 独立集可以替换:如果你有一小堆独立积木,别人有一大堆,你可以从别人那里拿一块加到你的堆里,仍然独立

这种"独立性"结构,就是拟阵。


拟阵的正式定义

定义:拟阵是一个有序对 M=(E,I)M = (E, \mathcal{I}),其中:

  • EE 是一个有限集合(称为基础集
  • I\mathcal{I}EE 的子集族(称为独立集族

满足以下三条公理:

公理 1:空集独立

I\emptyset \in \mathcal{I}

这意味着至少有一个独立集——空集。

公理 2:遗传性(Hereditary Property)

\text{若 } A \in \mathcal{I} \text{ 且 } B \subseteq A \text{,则 } B \in \mathcal{I}

直觉:独立集的任何子集也独立。

公理 3:交换性(Exchange Property)

\text{若 } A, B \in \mathcal{I} \text{ 且 } |A| < |B| \text{,则存在 } x \in B \setminus A \text{ 使得 } A \cup \{x\} \in \mathcal{I}

直觉:如果有一个更大的独立集,你总能从它里面拿一个元素扩展你的独立集。


拟阵实例一:图拟阵

问题背景:给定一个无向图 G=(V,E)G = (V, E),找出不形成环的边集(即森林)。

拟阵建模

  • 基础集 EE:图的边集
  • 独立集:不形成环的边子集(即森林)

验证公理

  1. 空集独立:空边集不形成环 ✓

  2. 遗传性:森林的子集仍是森林 ✓

    • 如果 AA 没有环,BAB \subseteq A 也必然没有环
  3. 交换性:这是关键。设 AABB 都是森林,且 A<B|A| < |B|

    • BB 有更多边,意味着 BB 连接了更多的连通分量
    • BB 中必有一条边连接 AA 中不同的连通分量
    • 把这条边加入 AA 不会形成环
    • 因此 A{e}IA \cup \{e\} \in \mathcal{I}

拟阵上的贪心 = Kruskal MST

图拟阵上的权重拟阵问题是:在所有最大独立集中找权重最小的。

这正是最小生成树问题!贪心算法如下:

python
def kruskal_mst(n: int, edges: List[Tuple[int, int, int]]) -> List[Tuple[int, int, int]]:
    """
    Kruskal 最小生成树算法
    
    这是拟阵贪心的一个实例:
    - 基础集:所有边
    - 独立集:无环边集(森林)
    - 贪心策略:每次选最小权重的边,若不形成环则加入
    
    Args:
        n: 顶点数
        edges: 边列表,每个元素是 (u, v, weight)
        
    Returns:
        最小生成树的边列表
    """
    # 按权重排序(贪心:每次选最小)
    edges = sorted(edges, key=lambda e: e[2])
    
    parent = list(range(n))
    rank = [0] * n
    
    def find(x):
        if parent[x] != x:
            parent[x] = find(parent[x])  # 路径压缩
        return parent[x]
    
    def union(x, y):
        px, py = find(x), find(y)
        if px == py:
            return False  # 已经在同一集合,会形成环
        if rank[px] < rank[py]:
            px, py = py, px
        parent[py] = px
        if rank[px] == rank[py]:
            rank[px] += 1
        return True
    
    mst = []
    for u, v, w in edges:
        if union(u, v):  # 不形成环
            mst.append((u, v, w))
            if len(mst) == n - 1:
                break
    
    return mst

为什么 Kruskal 正确? 因为图拟阵满足交换性,贪心选择不会丢失最优解。


拟阵实例二:矩阵拟阵

问题背景:给定一个矩阵,找出线性无关的行/列。

拟阵建模

  • 基础集 EE:矩阵的行(或列)
  • 独立集:线性无关的行子集

验证公理

  1. 空集独立:空集是线性无关的 ✓

  2. 遗传性:线性无关集合的子集仍然线性无关 ✓

    • 如果一组向量线性无关,任意子集必然线性无关
  3. 交换性:设 AABB 都是线性无关的行向量集,且 A<B|A| < |B|

    • BB 张成的空间维度 B\geq |B|
    • AA 张成的空间维度 =A= |A|
    • BB 中必有一个向量不在 AA 张成的空间内
    • 把这个向量加入 AA 仍线性无关 ✓

应用:矩阵的基选择、向量空间的基底变换


拟阵实例三:均匀拟阵

问题背景:从 nn 个元素中选出 kk 个。

拟阵建模

  • 基础集 EEnn 个元素
  • 独立集:大小 k\leq k 的子集

验证公理

  1. 空集独立:空集大小为 0 k\leq k

  2. 遗传性:如果 Ak|A| \leq kBAB \subseteq A,则 BAk|B| \leq |A| \leq k

  3. 交换性:如果 A<Bk|A| < |B| \leq k,则 A<k|A| < k,存在 xBAx \in B \setminus A 使得 A{x}k|A \cup \{x\}| \leq k

应用:简单的选择问题


拟阵上的贪心算法

通用拟阵贪心算法

GREEDY(M, w):
    A = ∅  // 初始独立集
    将 E 中的元素按权重 w 降序排序
    for each x in E (按权重降序):
        if A ∪ {x} ∈ I:  // 如果加入后仍独立
            A = A ∪ {x}
    return A

定理:对于拟阵 M=(E,I)M = (E, \mathcal{I}) 和权重函数 w:ER+w: E \to \mathbb{R}^+,GREEDY 算法返回一个最大权重的独立集。

证明思路

  1. 设贪心结果为 A={a1,a2,,ak}A = \{a_1, a_2, \ldots, a_k\}(按加入顺序)
  2. 设最优解为 B={b1,b2,,bm}B = \{b_1, b_2, \ldots, b_m\}(按权重降序)
  3. 用归纳法证明:对于所有 iiw(ai)w(bi)w(a_i) \geq w(b_i)

关键步骤:利用交换性。

如果 A<B|A| < |B|,由交换性,存在 bjBAb_j \in B \setminus A 可以加入 AA。但贪心算法已经按权重降序检查过所有元素,所以这是矛盾的。

因此 A=B|A| = |B|,且每个位置的元素权重 AA 都不小于 BB\square


为什么活动选择不是拟阵但贪心仍有效?

活动选择问题:选择最多的不重叠活动。

尝试建模为拟阵

  • 基础集 EE:所有活动
  • 独立集 I\mathcal{I}:不重叠的活动子集

验证公理

  1. 空集独立

  2. 遗传性 ✓:不重叠活动的子集仍不重叠

  3. 交换性:❌ 失败!

反例

活动 A: [1, 3]
活动 B: [3, 5]  
活动 C: [2, 4]

独立集 A={A}A = \{A\}(只选活动 A),独立集 B={A,B}B = \{A, B\}(选活动 A 和 B)。

A<B|A| < |B|,但能从 BB 中取出元素加入 AA 吗?

BA={B}B \setminus A = \{B\}A{B}={A,B}A \cup \{B\} = \{A, B\} 是独立的 ✓

这看起来是对的... 让我们换一个例子:

活动 A: [1, 4]
活动 B: [2, 5]
活动 C: [4, 6]

独立集 X={A,C}X = \{A, C\}(大小为 2),独立集 Y={B}Y = \{B\}(大小为 1)。

等等,这里 Y<X|Y| < |X|,应该从 XX 取元素加入 YY

XY={A,C}X \setminus Y = \{A, C\}Y{A}={B,A}Y \cup \{A\} = \{B, A\} 不是独立的(A 和 B 重叠)。Y{C}={B,C}Y \cup \{C\} = \{B, C\} 也不独立(B 和 C 重叠)。

交换性失败!


为什么贪心仍然有效?

活动选择虽然不是拟阵,但它有另一个性质:贪心选择性质

定义:一个问题有贪心选择性质,如果存在一个最优解包含了贪心选择。

证明活动选择的贪心选择性质

SS 是按结束时间排序的活动序列,a1a_1 是第一个活动(最早结束)。

AA 是某个最优解。

情况 1a1Aa_1 \in A,则 AA 包含贪心选择 ✓

情况 2a1Aa_1 \notin A,设 aka_kAA 中结束最早的活动。

由于 a1a_1aka_k 结束更早,我们可以用 a1a_1 替换 aka_k

  • a1a_1 不与 A{ak}A \setminus \{a_k\} 中的任何活动冲突(因为 aka_kAA 中结束最早的)
  • 替换后 A=(A{ak}){a1}A' = (A \setminus \{a_k\}) \cup \{a_1\} 仍是可行解,且大小相同

所以存在包含贪心选择的最优解。\square

关键洞察

  • 拟阵保证贪心正确(充分条件)
  • 但贪心正确的条件更宽泛:只需贪心选择性质 + 最优子结构

拟阵 vs 其他贪心正确性条件

条件强度说明
拟阵最强结构性保证,可直接套用贪心算法模板
贪心选择性质 + 最优子结构中等需要单独证明,但适用范围更广
无形式化保证最弱靠直觉和实验,可能错误

例子

  • 拟阵:Kruskal MST(图拟阵)、最大独立行集(矩阵拟阵)
  • 贪心性质但非拟阵:活动选择、Huffman 编码
  • 贪心失效:0-1 背包、加权活动选择

本节小结

这一节解决了什么问题?

有没有数学框架能判断贪心一定有效?有,但只是充分条件。

核心方法是什么?

拟阵 = 遗传性 + 交换性。若问题能建模为拟阵,贪心必然有效。

它为什么正确?

交换性保证了:如果存在更大的独立集,总能扩展当前独立集。贪心不会"错过"最优解。

它在什么情况下不适用?

非拟阵问题——但贪心可能仍然有效(如活动选择、Huffman)。需要其他证明方法。

LLM 时代映射

LLM 的注意力机制有拟阵的影子:

  • 遗传性:关注子序列,子序列的注意力模式继承原序列的性质
  • 交换性:位置编码允许不同位置的信息交换

但 Transformer 的注意力更复杂,不是严格的拟阵——贪心解码(选最高概率 token)不能保证全局最优。


习题

  1. 概念题:拟阵的三条公理分别保证什么性质?

  2. 证明题:证明有向图的边集不能构成拟阵(提示:找交换性的反例)。

  3. 编程题:实现一个通用拟阵贪心算法框架,支持自定义独立性测试。

  4. 思考题:哈夫曼编码问题能建模为拟阵吗?如果不能,它的贪心正确性来自哪里?

  5. LLM 联系:Beam Search 是一种"受限贪心"——保留前 k 个候选。这与拟阵有什么联系?(提示:均匀拟阵)

新时代的算法课程