有向无环图(DAG)的最小路径覆盖(不相交)问题
字数 3297 2025-12-18 20:46:46

有向无环图(DAG)的最小路径覆盖(不相交)问题

今天我们来讨论一个经典问题:在有向无环图(DAG) 中,如何用最少的互不相交的路径覆盖图中所有顶点。每条路径可以是一个或多个顶点,但路径之间不能共享顶点。这个问题被称为 DAG的最小(顶点)不相交路径覆盖

问题定义

给定一个有向无环图 \(G = (V, E)\),目标是找到一组数量最少的路径,满足:

  1. 每条路径是图 \(G\) 中的一条有向路径(顶点序列 \(v_1 \to v_2 \to \dots \to v_k\),且相邻顶点间有边)。
  2. 这些路径覆盖了 \(V\) 中的所有顶点,即每个顶点恰好出现在一条路径中(不相交)。
  3. 路径的数量最小。

示例
考虑下图(一个DAG):

顶点:1, 2, 3, 4
边:1 → 2, 1 → 3, 2 → 4, 3 → 4

一种覆盖是所有顶点单独作为一条路径,共4条。但更优的方案是:

  • 路径1: 1 → 2 → 4
  • 路径2: 3
    或者:
  • 路径1: 1 → 3 → 4
  • 路径2: 2
    这样只需2条路径。

核心思想:转化为二分图最大匹配

这个问题可以通过构建一个二分图,并求其最大匹配来解决。步骤如下:

步骤1:构建二分图模型

  1. 将原DAG \(G\) 的每个顶点 \(v\) 拆分成两个顶点:左部顶点 \(v_{\text{out}}\) 和右部顶点 \(v_{\text{in}}\)
    • 左部对应“路径的起点”或“前驱”,右部对应“路径的终点”或“后继”。
  2. 对于原图中的每条有向边 \(u \to v\),在二分图中添加一条从 \(u_{\text{out}}\)\(v_{\text{in}}\) 的边。
  3. 这样,二分图 \(B = (L, R, E_B)\) 就构建好了,其中 \(L = \{v_{\text{out}} \mid v \in V\}\)\(R = \{v_{\text{in}} \mid v \in V\}\),且 \(|L| = |R| = |V|\)

为什么这样建模?

  • 在DAG中,一条路径可以看作一连串的边连接。
  • 在二分图中,匹配边 \(u_{\text{out}} \to v_{\text{in}}\) 表示在原图中,顶点 \(u\)\(v\) 的直接前驱(即 \(u \to v\) 在路径中)。
  • 一个匹配对应一组原图中的边连接关系,这些连接关系可以组合成若干条路径。

步骤2:理解匹配与路径覆盖的关系

关键定理(Dilworth定理的一个推论):

最小路径覆盖数 = |V| - 最大匹配数

直观解释

  • 初始时,假设每个顶点自成一条路径,那么路径数 = |V|。
  • 每当我们在二分图中找到一条匹配边 \(u_{\text{out}} \to v_{\text{in}}\),就相当于将 \(u\) 所在路径和 \(v\) 所在路径合并(因为 \(u\) 变成了 \(v\) 的前驱,两条路径首尾相接成一条)。
  • 一个匹配对应一组合并操作。每增加一条匹配边,路径数就减少1(因为两条路径合并为一条)。
  • 因此,最大匹配使得合并次数最多,从而路径数最少。

步骤3:求解二分图最大匹配

二分图最大匹配可以用匈牙利算法(\(O(VE)\))或更高效的Hopcroft-Karp算法(\(O(E\sqrt{V})\))求解。这里我们简述匈牙利算法的思路:

  1. 初始化所有匹配为空。
  2. 对左部每个顶点 \(u\),尝试为它寻找一个右部顶点 \(v\) 进行匹配。
  3. 寻找过程使用DFS:如果 \(v\) 未被匹配,则直接匹配;如果 \(v\) 已匹配,则尝试为 \(v\) 的原配左部顶点寻找新的匹配(递归),即“增广”。
  4. 如果能找到一条增广路径(交替路径),则匹配数增加1。

步骤4:从匹配重构路径

得到最大匹配后,我们可以还原出最小路径覆盖:

  1. 在匹配中,每条边 \(u_{\text{out}} \to v_{\text{in}}\) 对应原图中 \(u\)\(v\) 的直接前驱。
  2. 根据匹配边,可以构建一个前驱关系图(仍是原图的一个子图):
    • 对于每个右部顶点 \(v_{\text{in}}\),如果它被匹配到某个 \(u_{\text{out}}\),则 \(u\)\(v\) 的前驱。
    • 未被匹配的右部顶点对应一条路径的起点(因为没有前驱)。
    • 未被匹配的左部顶点对应一条路径的终点(因为没有后继)。
  3. 从前驱关系出发,可以DFS或直接追踪出所有路径。

完整算法流程

  1. 输入有向无环图 \(G = (V, E)\)
  2. 构建二分图 \(B\)
    • 左部顶点集:\(\{v_{\text{out}} \mid v \in V\}\)
    • 右部顶点集:\(\{v_{\text{in}} \mid v \in V\}\)
    • 对每条边 \((u, v) \in E\),在 \(B\) 中添加边 \((u_{\text{out}}, v_{\text{in}})\)
  3. 在二分图 \(B\) 上求最大匹配 \(M\)(使用匈牙利算法或Hopcroft-Karp算法)。
  4. 最小路径覆盖数 = \(|V| - |M|\)
  5. 根据匹配 \(M\) 还原出具体路径:
    • 初始化每个顶点为单独路径。
    • 对于匹配边 \((u_{\text{out}}, v_{\text{in}}) \in M\),将 \(u\) 所在路径的末尾与 \(v\) 所在路径的开头连接。

举例说明

考虑DAG:

顶点:{1, 2, 3, 4}
边:1→2, 1→3, 2→4, 3→4
  1. 构建二分图:
    • 左部:{1_out, 2_out, 3_out, 4_out}
    • 右部:{1_in, 2_in, 3_in, 4_in}
    • 边:(1_out,2_in), (1_out,3_in), (2_out,4_in), (3_out,4_in)
  2. 求最大匹配。一种最大匹配是 {(1_out,2_in), (3_out,4_in)},大小|M|=2。
  3. 最小路径覆盖数 = 4 - 2 = 2。
  4. 还原路径:
    • 匹配边1_out→2_in:表示1→2。
    • 匹配边3_out→4_in:表示3→4。
    • 顶点1_in和4_out未匹配,对应路径起点1和终点4?不,要小心:未匹配的右部顶点(即没有被作为“后继”匹配的)是路径的起点。这里右部未匹配的是1_in和3_in?我们检查:
      • 右部顶点2_in匹配到1_out,所以2有前驱1。
      • 右部顶点4_in匹配到3_out,所以4有前驱3。
      • 右部顶点1_in和3_in未被匹配,所以1和3是路径起点。
      • 左部顶点1_out和3_out已匹配,2_out和4_out未匹配,所以2和4是路径终点?更准确地说,我们从起点开始追踪:
        从起点1:1→2(因为匹配1_out→2_in),然后2是终点吗?看2_out未匹配,所以2没有后继,路径结束:1→2。
        从起点3:3→4(匹配3_out→4_in),然后4_out未匹配,路径结束:3→4。
    • 因此两条路径:1→2 和 3→4,覆盖所有顶点。

复杂度分析

  • 二分图有 \(2|V|\) 个顶点,最多 \(|E|\) 条边(因为原图每条边对应一条二分图边)。
  • 使用匈牙利算法:\(O(VE)\)
  • 使用Hopcroft-Karp算法:\(O(E\sqrt{V})\)
    因为通常 \(|E| \leq |V|^2\),所以算法是多项式时间的。

扩展与变体

  • 如果允许路径相交(即顶点可以被多条路径共享),则问题变为“最小边覆盖”或“最小链覆盖”,可以用传递闭包后转化为二分图匹配。
  • 如果图不是DAG,问题变为NP-Hard(因为可以归约到哈密顿路径问题)。

这个算法巧妙地将路径覆盖问题转化为匹配问题,是图论中一个经典的二分图应用。

有向无环图(DAG)的最小路径覆盖(不相交)问题 今天我们来讨论一个经典问题:在 有向无环图(DAG) 中,如何用最少的 互不相交的路径 覆盖图中所有顶点。每条路径可以是一个或多个顶点,但路径之间不能共享顶点。这个问题被称为 DAG的最小(顶点)不相交路径覆盖 。 问题定义 给定一个有向无环图 \( G = (V, E) \),目标是找到一组数量最少的路径,满足: 每条路径是图 \( G \) 中的一条有向路径(顶点序列 \( v_ 1 \to v_ 2 \to \dots \to v_ k \),且相邻顶点间有边)。 这些路径覆盖了 \( V \) 中的所有顶点,即每个顶点恰好出现在一条路径中(不相交)。 路径的数量最小。 示例 : 考虑下图(一个DAG): 一种覆盖是所有顶点单独作为一条路径,共4条。但更优的方案是: 路径1: 1 → 2 → 4 路径2: 3 或者: 路径1: 1 → 3 → 4 路径2: 2 这样只需2条路径。 核心思想:转化为二分图最大匹配 这个问题可以通过构建一个二分图,并求其 最大匹配 来解决。步骤如下: 步骤1:构建二分图模型 将原DAG \( G \) 的每个顶点 \( v \) 拆分成两个顶点:左部顶点 \( v_ {\text{out}} \) 和右部顶点 \( v_ {\text{in}} \)。 左部对应“路径的起点”或“前驱”,右部对应“路径的终点”或“后继”。 对于原图中的每条有向边 \( u \to v \),在二分图中添加一条从 \( u_ {\text{out}} \) 到 \( v_ {\text{in}} \) 的边。 这样,二分图 \( B = (L, R, E_ B) \) 就构建好了,其中 \( L = \{v_ {\text{out}} \mid v \in V\} \),\( R = \{v_ {\text{in}} \mid v \in V\} \),且 \( |L| = |R| = |V| \)。 为什么这样建模? 在DAG中,一条路径可以看作一连串的边连接。 在二分图中,匹配边 \( u_ {\text{out}} \to v_ {\text{in}} \) 表示在原图中,顶点 \( u \) 是 \( v \) 的直接前驱(即 \( u \to v \) 在路径中)。 一个匹配对应一组原图中的边连接关系,这些连接关系可以组合成若干条路径。 步骤2:理解匹配与路径覆盖的关系 关键定理(Dilworth定理的一个推论): 最小路径覆盖数 = |V| - 最大匹配数 直观解释 : 初始时,假设每个顶点自成一条路径,那么路径数 = |V|。 每当我们在二分图中找到一条匹配边 \( u_ {\text{out}} \to v_ {\text{in}} \),就相当于将 \( u \) 所在路径和 \( v \) 所在路径 合并 (因为 \( u \) 变成了 \( v \) 的前驱,两条路径首尾相接成一条)。 一个匹配对应一组合并操作。每增加一条匹配边,路径数就减少1(因为两条路径合并为一条)。 因此,最大匹配使得合并次数最多,从而路径数最少。 步骤3:求解二分图最大匹配 二分图最大匹配可以用匈牙利算法(\(O(VE)\))或更高效的Hopcroft-Karp算法(\(O(E\sqrt{V})\))求解。这里我们简述匈牙利算法的思路: 初始化所有匹配为空。 对左部每个顶点 \( u \),尝试为它寻找一个右部顶点 \( v \) 进行匹配。 寻找过程使用DFS:如果 \( v \) 未被匹配,则直接匹配;如果 \( v \) 已匹配,则尝试为 \( v \) 的原配左部顶点寻找新的匹配(递归),即“增广”。 如果能找到一条增广路径(交替路径),则匹配数增加1。 步骤4:从匹配重构路径 得到最大匹配后,我们可以还原出最小路径覆盖: 在匹配中,每条边 \( u_ {\text{out}} \to v_ {\text{in}} \) 对应原图中 \( u \) 是 \( v \) 的直接前驱。 根据匹配边,可以构建一个前驱关系图(仍是原图的一个子图): 对于每个右部顶点 \( v_ {\text{in}} \),如果它被匹配到某个 \( u_ {\text{out}} \),则 \( u \) 是 \( v \) 的前驱。 未被匹配的右部顶点对应一条路径的起点(因为没有前驱)。 未被匹配的左部顶点对应一条路径的终点(因为没有后继)。 从前驱关系出发,可以DFS或直接追踪出所有路径。 完整算法流程 输入有向无环图 \( G = (V, E) \)。 构建二分图 \( B \): 左部顶点集:\( \{v_ {\text{out}} \mid v \in V\} \) 右部顶点集:\( \{v_ {\text{in}} \mid v \in V\} \) 对每条边 \( (u, v) \in E \),在 \( B \) 中添加边 \( (u_ {\text{out}}, v_ {\text{in}}) \)。 在二分图 \( B \) 上求最大匹配 \( M \)(使用匈牙利算法或Hopcroft-Karp算法)。 最小路径覆盖数 = \( |V| - |M| \)。 根据匹配 \( M \) 还原出具体路径: 初始化每个顶点为单独路径。 对于匹配边 \( (u_ {\text{out}}, v_ {\text{in}}) \in M \),将 \( u \) 所在路径的末尾与 \( v \) 所在路径的开头连接。 举例说明 考虑DAG: 构建二分图: 左部:{1_ out, 2_ out, 3_ out, 4_ out} 右部:{1_ in, 2_ in, 3_ in, 4_ in} 边:(1_ out,2_ in), (1_ out,3_ in), (2_ out,4_ in), (3_ out,4_ in) 求最大匹配。一种最大匹配是 {(1_ out,2_ in), (3_ out,4_ in)},大小|M|=2。 最小路径覆盖数 = 4 - 2 = 2。 还原路径: 匹配边1_ out→2_ in:表示1→2。 匹配边3_ out→4_ in:表示3→4。 顶点1_ in和4_ out未匹配,对应路径起点1和终点4?不,要小心:未匹配的右部顶点(即没有被作为“后继”匹配的)是路径的起点。这里右部未匹配的是1_ in和3_ in?我们检查: 右部顶点2_ in匹配到1_ out,所以2有前驱1。 右部顶点4_ in匹配到3_ out,所以4有前驱3。 右部顶点1_ in和3_ in未被匹配,所以1和3是路径起点。 左部顶点1_ out和3_ out已匹配,2_ out和4_ out未匹配,所以2和4是路径终点?更准确地说,我们从起点开始追踪: 从起点1:1→2(因为匹配1_ out→2_ in),然后2是终点吗?看2_ out未匹配,所以2没有后继,路径结束:1→2。 从起点3:3→4(匹配3_ out→4_ in),然后4_ out未匹配,路径结束:3→4。 因此两条路径:1→2 和 3→4,覆盖所有顶点。 复杂度分析 二分图有 \( 2|V| \) 个顶点,最多 \( |E| \) 条边(因为原图每条边对应一条二分图边)。 使用匈牙利算法:\( O(VE) \)。 使用Hopcroft-Karp算法:\( O(E\sqrt{V}) \)。 因为通常 \( |E| \leq |V|^2 \),所以算法是多项式时间的。 扩展与变体 如果允许路径相交(即顶点可以被多条路径共享),则问题变为“最小边覆盖”或“最小链覆盖”,可以用传递闭包后转化为二分图匹配。 如果图不是DAG,问题变为NP-Hard(因为可以归约到哈密顿路径问题)。 这个算法巧妙地将路径覆盖问题转化为匹配问题,是图论中一个经典的二分图应用。