Floyd算法及常见应用总结

本文深入讲解Floyd算法,包括其原理、应用及代码实现。详细介绍了如何使用Floyd算法求解最短路径、最小环、最短路径数量及传递闭包等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Floyd求最短路

用邻接矩阵表示图,如果 V i V_i Vi V j V_j Vj有点可到达,那么更新 G [ i ] [ j ] G[i][j] G[i][j],否则 G [ i ] [ j ] G[i][j] G[i][j]为无穷大(一般设为 0 x 3 f 3 f 3 f 3 f 0x3f3f3f3f 0x3f3f3f3f,这样两个无穷大相加也不会溢出)。另外需要注意的是 G [ i ] [ i ] = 0 G[i][i]=0 G[i][i]=0,因为我们要默认一个点到本身的最短路为 0 0 0,如果忘记有的题目会卡。

算法思想

动态规划,对于任何两个节点 i i i j j j而言, i i i j j j的最短距离不外乎存在 i i i j j j之间经过 k k k和不经过 k k k两种情况,这样我们设 G [ i ] [ j ] [ k ] G[i][j][k] G[i][j][k] i i i j j j之间经过 k k k和不经过 k k k两种情况,枚举时就先枚举 i i i再枚举 j j j再枚举 k k k,但是这样的空间开销大。于是可以省去数组的第三维,改为先枚举 k k k然后分别枚举 i , j i,j i,j,这样 D P DP DP数组就定义为 G [ i ] [ j ] G[i][j] G[i][j]表示 i i i j j j的最短距离。

松弛时比较 G ( i , j ) G(i,j) G(i,j) G ( i , k ) + G ( k , j ) G(i,k)+G(k,j) G(i,k)+G(k,j)的大小。在此 G ( i , k ) + G ( k , j ) G(i,k)+G(k,j) G(i,k)+G(k,j)分别是目前为止所知道的 i i i k k k k k k j j j的最短距离,因此 G ( i , k ) + G ( k , j ) G(i,k)+G(k,j) G(i,k)+G(k,j)就是 i i i j j j经过 k k k的最短距离。所以若有 G ( i , j ) > G ( i , k ) + G ( k , j ) G(i,j)>G(i,k)+G(k,j) G(i,j)>G(i,k)+G(k,j),就表示从 i i i出发经过 k k k再到 j j j的距离要比原来的 i i i j j j距离短,自然把 i i i j j j G ( i , j ) G(i,j) G(i,j)重写为 G ( i , k ) + G ( k , j ) G(i,k)+G(k,j) G(i,k)+G(k,j),每当一个 k k k比较完了, G ( i , j ) G(i,j) G(i,j)就是目前的i到j的最短距离。重复这一过程,最后当遍历完所有的 k k k时,对于任意的 i , j i,j i,j G ( i , j ) G(i,j) G(i,j)里面存放的就是 i i i j j j之间的最短距离。

时间复杂度 O ( n 3 O(n^3 O(n3),空间复杂度 O ( V 2 ) O(V^2) O(V2),应用于求多源最短路(可以含有负权但不能含有负环)

代码实现:

int G[505][505];  //邻接矩阵
int n;  //n是节点个数

void init() {
    memset(G, 0x3f, sizeof(G));
    for (int i = 1; i <= n; i++) G[i][i] = 0; //每个节点到自身的最短路为0
}

void Floyd(int n) { 
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (G[i][j] > G[i][k] + G[k][j])
                    G[i][j] = G[i][k] + G[k][j];
            }
        }
    }
}
Floyd打印最短路径

我们用 P a t h Path Path数组保存路径, P a t h [ i ] [ j ] Path[i][j] Path[i][j]代表从 i i i j j j的下一个节点。初始肯定是假设每个 i i i都直接和 j j j相连,那么 P a t h [ i ] [ j ] Path[i][j] Path[i][j]就是 j j j

如果某一点 k k k能够在 i , j i,j i,j之间松弛,那么令 P a t h [ i ] [ j ] = P a t h [ i ] [ k ] Path[i][j]=Path[i][k] Path[i][j]=Path[i][k],这样,再加上 P a t h [ k ] [ j ] Path[k][j] Path[k][j]刚好是最短路径 i → k → j i \rightarrow k \rightarrow j ikj

因此我们得到如下代码:

int G[505][505], Path[505][505];
int n;

void init() {
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            G[i][j] = (i == j ? 0 : inf);
            Path[i][j] = j;
        }
}

void Floyd() {
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (G[i][j] > G[i][k] + G[k][j]) {
                    G[i][j] = G[i][k] + G[k][j];
                    Path[i][j] = Path[i][k];
                }
}

void Print(int u, int v) {
    if (u == v) {
        printf("%d\n", v); //最后一个点
        return;
    }
    printf("%d ", u);  //因为考虑到先打印起始节点,所以先打印后递归
    Print(Path[u][v], v);
}

求最小字典序的路径

我们知道两个节点之间的最短路径不一定唯一,那么如何求打印的字典序最小的路径?

只需要在 F l o y d Floyd Floyd的三重循环里添加一个特判:

for (int k = 1; k <= n; k++)
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (G[i][j] > G[i][k] + G[k][j]) {
				G[i][j] = G[i][k] + G[k][j];
				Path[i][j] = Path[i][k];
			} else if (G[i][j] == G[i][k] + G[k][j] && Path[i][j] > Path[i][k])  //即只要最短路径相同我们只保存序号最小的节点  
				Path[i][j] = Path[i][k];
            }
Floyd求最小环

最小环

从起点出发,经过一条简单路径回到起点成为环。图的最小环就是所有环中长度最小的。有向图的环至少含 2 2 2个顶点,而无向图的环至少含 3 3 3个顶点。

怎么求最小环

无向图:

F l o y d Floyd Floyd保证了最外层循环到 k k k 时所有顶点间已求得以 [ 1 , k − 1 ] [1,k-1] [1,k1]为中间点的最短路径。一个环至少有 3 3 3 个顶点,设某环编号最大的顶点为 k k k,在环中直接与之相连的两个顶点编号分别为 i i i j j j ( i , j , k i,j,k i,j,k两两不同且 i , j < k i,j<k i,j<k),则最大编号为 k k k 的最小环长度即为 G ( j , k ) + G ( k , i ) + d ( i , j ) G(j,k) + G(k,i) +d(i,j) G(j,k)+G(k,i)+d(i,j),其中 d ( i , j ) d(i,j) d(i,j)表示以 [ 1 , k − 1 ] [1,k-1] [1,k1] 号顶点为中间点时的最短路径,刚好符合 F l o y d Floyd Floyd 最外层循环到 k k k 时的情况,则经过最外层 k k k 次循环,即可找到整个图的最小环。

注意下面是先找[1,k-1]的最小环,再求经过k点松弛的最短路

有向图:

由于此时最小环至少包含两个节点,那么进行一次 F l o y d Floyd Floyd就得到最小环 d [ i ] [ j ] d[i][j] d[i][j]

#define INF 10000000   //因为下面涉及到三个数相加,那么0x3f3f3f3f会溢出int
int G[505][505], d[505][505];
int n;

void init() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            G[i][j] = d[i][j] = (i == j ? 0 : INF);
        }
    }
}

int Floyd() {
    int ans = INF;
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i < k; i++) {
            for (int j = i + 1; j < k; j++) {                //保证3个不相同的点 
                ans = min(ans, G[i][k] + G[k][j] + d[i][j]);  //取最小的环
            }
        }
        //正常的求i,j经过点k松弛的最短路
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
    return ans;
}
求出最短路的数量

无向图

不难想到最终从终点到起点的所有最短路构成的是一颗搜索树,那么我们只需沿着终点开始搜到起点。对于边 u − v u - v uv,如果起点到 u u u的最短路等于起点到 v v v的最短路加上 w ( u , v ) w(u,v) w(u,v),那么就可以继续搜索,每搜到一次起点答案加一。

有向图

思想和上面类似,只需再反向建图。

int s, e, cnt;  //起点,终点,答案
int g[505][505], G[505][505];  //g数组为任意两点间的最短路,G数组为初始的图

void dfs(int u) {
    if (u == s) {
        cnt++;
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (i != u && G[u][i] < inf && g[s][u] == g[s][i] + G[u][i]) {
            dfs(i);
        }
    }
}

void solve () {
    dfs(e);
    cout << cnt << endl;
}
Floyd求传递闭包

a > b , b > c a>b,b>c a>b,b>c能够推出 a > c a>c a>c,那么 a , c a,c a,c关系的确定就是一个传递闭包。对于这样的给定大小、排名关系的问题,只需要当成图然后跑 f l o y d floyd floyd算法,常见模板如下:

int vis[505][505];
int n;

void Floyd() {
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (vis[i][k] && vis[k][j])
                    vis[i][j] = 1;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值