【图论】总结 2:Floyd 算法

Floyd 算法求全源最短路

全源最短路问题是指对于一张图,要求图上任意两点之间的最短路径长度的问题。我们当然可以以每个节点为起点跑 \(n\) 遍单源最短路,但 Floyd 算法给出了一种更为简洁的求法。

我们按照 DP 的思想理解 Floyd 算法,设计 \(f(i,j)\) 表示从 \(i\)\(j\) 的最短路径长度。考虑转移,钦定点 \(k\),那么有 \(f(i,j)=\min\{f(i,j),f(i,k)+f(k,j)\}\),其正确性显然。其中我们枚举 \(k\) 时将其看作 DP 的阶段,因此 Floyd 算法实现时必须将 \(k\) 置于最外层循环

因为要枚举 \(k,i,j\),因此 Floyd 算法的时间复杂度是 \(O(n^3)\),且 Floyd 算法能在无负环的情况下求解含有负权边的全源最短路问题。Floyd 算法参考代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N = 3e2 + 10, INF = 1e9;
int n, m;
int f[N][N];
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
			f[i][j] = INF;
	for(int i = 1; i <= m; i ++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		f[u][v] = min(w, f[u][v]);
	}
	for(int i = 1; i <= n; i ++) f[i][i] = 0;
	for(int k = 1; k <= n; k ++)
		for(int i = 1; i <= n; i ++)
			for(int j = 1; j <= n; j ++)
				f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
		for(int i = 1; i <= n; i ++)
		{
			for(int j = 1; j <= n; j ++)
				printf("%d ", f[i][j]);
			puts("");
		}
	return 0;
}

Floyd 算法求最小环

问题:给定一个正权无向图,求图中所有环中边权和最小的(环至少包含 \(3\) 个点)。

我们采用 Floyd 算法求解这个问题。考虑 Floyd 的 DP 过程,在最外层循环刚枚举到 \(k\) 时,\(f(i,j)\) 实际上存储了“不经过编号大于 \(k-1\) 的节点”的从 \(i\)\(j\) 的最短路径的长度。那么所有满足由编号不大于 \(k\) 的节点组成且经过 \(k\) 的环中边权和最小的长度为:

\[\min_{1\le i<j<k}\{f(i,j)+w(j,k)+w(k,i)\} \]

我们枚举 \(k\),并取最小值,同时记录方案即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 3e2 + 10, INF = 1e9;
int n, m, ans = INF;
int w[N][N], f[N][N], pos[N][N];
vector<int> circle;
void work(int u, int v)//记录方案 
{
	if(pos[u][v] == 0) return;
	work(u, pos[u][v]);
	circle.push_back(pos[u][v]);
	work(pos[u][v], v);
}
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
			w[i][j] = INF;
	for(int i = 1; i <= n; i ++) w[i][i] = 0;
	for(int i = 1; i <= m; i ++)
	{
		int u, v, w_;
		scanf("%d%d%d", &u, &v, &w_);
		w[v][u] = w[u][v] = min(w[u][v], w_);
	}
	memcpy(f, w, sizeof w);
	for(int k = 1; k <= n; k ++)
	{
		for(int i = 1; i < k; i ++)
			for(int j = i + 1; j < k; j ++)
			{
				if(f[i][j] + w[j][k] + w[k][i] < ans)
				{
					ans = f[i][j] + w[j][k] + w[k][i];
					circle.clear();
					circle.push_back(i);
					work(i, j);
					circle.push_back(j);
					circle.push_back(k);
				}
			}
		for(int i = 1; i <= n; i ++)
			for(int j = 1; j <= n; j ++)
				if(f[i][j] > f[i][k] + f[k][j])
				{
					f[i][j] = f[i][k] + f[k][j];
					pos[i][j] = k;
				}
	}
	if(ans == INF)//无解 
	{
		puts("No solution.");
		return 0;
	}
	for(auto i : circle) printf("%d ", i);
	return 0;
}

Floyd 算法求传递闭包

问题:给定 \(n\) 个元素,并给定若干形如 \(a_i\odot a_j\) 的二元关系(\(\odot\) 具有传递性,例如 \(<\)\(>\) 等),求尽可能多的元素之间的关系。

考虑每个二元关系 \(a_i\odot a_j\),将其刻画为在 \(a_i\)\(a_j\) 之间连接一条边,那么所有的二元关系可构成一张图。此时将 Floyd 算法中的 \(f(i,j)\) 改为 bool 类型,并用 \(1/0\) 刻画节点 \(i\)\(j\) 之间有无关系(令 \(f(i,i)=1\)),再进行位运算即可。

for(int k = 1; k <= n; k ++)
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
			f[i][j] |= f[i][k] & f[k][j];

类似于传递闭包问题,Floyd 算法也可判断图中任意两点是否连通,方法与上类似。

posted @ 2025-07-22 09:10  cold_jelly  阅读(48)  评论(0)    收藏  举报