最小环问题(有向图与无向图的 Floyd 算法解法)
题目描述
在一个带权图中(可以是有向图或无向图,边权可以是正、负或零,但不允许存在负权环),我们想要找出图中总边权最小的环。这个环至少需要包含两个顶点(在无向图中,一条边直接连接两个顶点构成一个环,但通常我们更关注至少包含三个不同顶点的简单环)。这就是最小环问题。
核心思想
我们可以利用全源最短路径的思想。考虑一个环,它由顶点 \(i\) 和 \(j\) 之间的最短路径,以及一条直接连接 \(i\) 和 \(j\) 的边构成。Floyd-Warshall 算法在动态规划的过程中,恰好能让我们在计算所有顶点对最短路径的同时,捕捉到这样的环。
解题步骤详述
我们以有向图为例进行说明,无向图的处理类似但稍有不同。
-
问题定义与符号约定
- 设图有 \(n\) 个顶点,顶点编号从 \(1\) 到 \(n\)。
- 用邻接矩阵
dist[][]来存储最短路径信息。初始时,dist[i][i] = 0。如果存在从 \(i\) 到 \(j\) 的边,则dist[i][j]为该边的权值;否则,dist[i][j] = INF(一个很大的数,表示无穷远)。 - 用另一个矩阵
direct[][]来存储原始的、直接的边权(即邻接矩阵的初始值),在算法过程中保持不变。这是为了在计算环权值时,我们能获取到顶点间的直接边权,而不是可能被其他路径更新的最短路径权值。 - 我们的目标是找到一个最小值
min_cycle = min(dist[i][j] + direct[j][i]),其中 \(i \neq j\),并且dist[i][j]和direct[j][i]都不是无穷大。这表示从 \(i\) 到 \(j\) 的最短路径,再加上从 \(j\) 直接回到 \(i\) 的边,构成了一个环。
-
算法框架:在 Floyd-Warshall 中嵌入检测
Floyd-Warshall 的主循环是一个三重循环,最外层枚举“中间点” \(k\):for k from 1 to n: for i from 1 to n: for j from 1 to n: if dist[i][k] + dist[k][j] < dist[i][j]: dist[i][j] = dist[i][k] + dist[k][j]其含义是:在考虑了前 \(k\) 个顶点作为中间点之后,
dist[i][j]存储的是从 \(i\) 到 \(j\) 的最短路径长度,且该路径上所有中间点的编号都 \(\le k\)。最小环的检测时机:
当我们以 \(k\) 作为中间点更新所有dist[i][j]之前,此时dist[i][j]存储的是只使用编号小于 \(k\) 的顶点作为中间点时,从 \(i\) 到 \(j\) 的最短路径长度。
考虑一个经过顶点 \(k\) 的环。我们可以把它看作是从 \(k\) 出发,经过一些编号小于 \(k\) 的顶点,到达某个顶点 \(i\)(路径1),再从 \(i\) 出发,经过一些编号小于 \(k\) 的顶点,到达某个顶点 \(j\)(路径2),最后通过一条直接边从 \(j\) 回到 \(k\)。
更精确且简单的形式是:**在当前这个 \(k\) 的迭代中,在更新任何dist[i][j]之前,dist[i][j]表示从 \(i\) 到 \(j\) 且中间点编号都< k\) 的最短路径。** 那么,对于所有满足 $i < k$ 且 $j < k$ 的顶点对 $(i, j)$,路径dist[i][j]不会经过 $k$ 本身。此时,如果我们有一条从 $j$ 直接到 $k$ 的边(权值为direct[j][k]),和一条从 $k$ 直接到 $i$ 的边(权值为direct[k][i]),那么dist[i][j] + direct[j][k] + direct[k][i]` 就构成了一个经过顶点 \(k\),且以 \(i, j, k\) 为部分顶点的环。我们检查所有这样的 \(i, j\) 来更新最小环值。 -
具体算法步骤
- 初始化:
- 读入图,构建
direct矩阵,表示直接的边权。 - 将
dist矩阵初始化为direct矩阵的拷贝。 min_cycle = INF。
- 读入图,构建
- 主循环:
for k in range(1, n+1): # 步骤1:检测以 k 为最大编号顶点的环 for i in range(1, k): # 保证 i, j 编号小于 k for j in range(1, k): if i != j: # 避免无意义的自环 # 检查路径 i->...->j 和边 j->k, k->i 是否都存在 if dist[i][j] < INF and direct[j][k] < INF and direct[k][i] < INF: cycle_weight = dist[i][j] + direct[j][k] + direct[k][i] min_cycle = min(min_cycle, cycle_weight) # 步骤2:标准的 Floyd-Warshall 更新,将 k 作为中间点引入 for i in range(1, n+1): for j in range(1, n+1): if dist[i][k] < INF and dist[k][j] < INF: new_dist = dist[i][k] + dist[k][j] if new_dist < dist[i][j]: dist[i][j] = new_dist - 结果输出:
- 如果
min_cycle仍然是INF,则说明图中不存在环(在无向图中,至少需要三个不同顶点的环)。 - 否则,
min_cycle就是最小环的边权之和。
- 如果
- 初始化:
-
无向图的处理
对于无向图,算法基本一致,但需要注意两点:- 无向图的边是双向的,所以
direct[i][j] = direct[j][i] = w。 - 在检测环时,公式变为
dist[i][j] + direct[j][k] + direct[k][i]。由于无向图中direct[j][k]和direct[k][i]都存在(如果对应边存在),这表示一个从 \(i\) 到 \(j\) 的路径,加上边 \(j-k\) 和边 \(k-i\) 构成的环。 - 为了避免将一条边来回走两次(
i->j的边,然后j->i的同一条边)这种平凡的二边环误认为环,我们通常要求环至少包含三个不同的顶点。在我们的循环中,i,j,k是三个不同的数(因为i, j < k且i != j),所以自然保证了是至少三个顶点的环。如果要包含二边环,需要特殊处理。
- 无向图的边是双向的,所以
-
算法复杂度与正确性
- 时间复杂度:与 Floyd-Warshall 算法相同,为 \(O(n^3)\)。因为我们在三重循环内部只增加了常数时间的操作。
- 空间复杂度:\(O(n^2)\),用于存储两个 \(n \times n\) 的矩阵。
- 正确性:算法枚举了所有可能的顶点 \(k\) 作为环中“编号最大”的顶点。在考虑 \(k\) 时,
dist[i][j]存储的是不经过 \(k\) 的、\(i\) 到 \(j\) 的最短路径。因此,dist[i][j] + direct[j][k] + direct[k][i]确实代表了一个经过 \(k\),且内部顶点编号都小于 \(k\) 的环。我们取所有 \(k\) 对应的最小值,就覆盖了所有可能的环。
总结
你学习了如何利用 Floyd-Warshall 算法的动态规划过程,巧妙地嵌入最小环的检测逻辑。其核心洞察是:在将顶点 \(k\) 正式引入作为中间点之前,利用当前已知的、不经过 \(k\) 的最短路径信息,与关联 \(k\) 的直接边组合成环。这个方法能同时处理有向图和无向图(注意无向图对二边环的界定),且在 \(O(n^3)\) 时间内求解稠密图的最小环问题是相当高效的。