Floyd 算法

Floyd 算法本质是动态规划

基本要点

  1. 时间复杂度是 O(n^3)O(n3),因此,只适用于n<500n<500 的这种结点数不多的情况。
  2. 它是多源最短路的,可以求出任何两点之间的最短路。
  3. 支持负边权
  4. 能够判断图中是否存在负回路

代码:

#include<bits/stdc++.h>using namespace std;int g[N][N];int dis[N][N];   // dis[i][j]: i->j 最短距离

/*

证明:定义 dis[i][j][k] 表示从 i 到 j 的可以经过 1~k 号结点的最短路径,此问题可以被分解为两个子问题

(1)不经过 k 号结点,即  dis[i][j][k-1]

(2)经过 k 号结点,即 dis[i][k][k-1]+dis[k][j][k-1];

因此,有:

dis[i][j][k] = min(dis[i][j][k-1], dis[i][k][k-1], dis[k][j][k-1]);

上式中第三维 k 可以省略,则有:

dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);

虽然 i,j,k 的顺序不保证,导致上式中 dis[i][k] 和 dis[k][j] 可能是已经被更新过的 dis[i][k][k] 和 dis[k][j][k] 了,

但是这并不影响其正确性。因为更新后的值一定会变得更小,不会影响最终结果。

*/ 

int main(){

    int n;

    cin >> n;

    for(int i = 1; i <= n; i++)

        for(int j = 1; j <= n; j++)

        {

            cin >> g[i][j];

            dis[i][j] = g[i][j];

        }

    // floyd 求任意两点间的最短路径

    for(int k = 1; k <= n; k++)   // 枚举中间点

        for(int i = 1; i <= n; i++)

            for(int j = 1; j <= n;j++)

                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);

                

    // 判断负环

    for(int i =1; i <= n; i++)

    {

        if(dis[i][i] < 0)

            cout<<"存在负环";

        break;

    }       

    return 0;}

Copy

证明:

定义 dis[i][j][k]dis[i][j][k] 表示从 ii 到 jj 的可以经过 11~kk 号结点的最短路径,此问题可以被分解为两个子问题

  1. 不经过 kk 号结点,即 dis[i][j][k-1]dis[i][j][k−1]
  2. 经过 kk 号结点,即 dis[i][k][k-1]+dis[k][j][k-1]dis[i][k][k−1]+dis[k][j][k−1]

因此,有:
dis[i][j][k] = min(dis[i][j][k-1], dis[i][k][k-1], dis[k][j][k-1]);dis[i][j][k]=min(dis[i][j][k−1],dis[i][k][k−1],dis[k][j][k−1]);
上式中第三维 k 可以省略,则有:
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
虽然去掉一维后,i,j,ki,j,k 的顺序不保证,导致上式中 dis[i][k]dis[i][k] 和 dis[k][j]dis[k][j] 可能是已经被更新过的 dis[i][k][k]dis[i][k][k] 和 dis[k][j][k]dis[k][j][k] 了,但是这并不影响其正确性。因为更新后的值一定会变得更小,不会影响最终结果。

Floyd 算法为什么把k放在最外层?

采用动态规划思想,f[k][i][j]f[k][i][j] 表示 ii 和 jj 之间可以通过编号不超过 kk,即编号 1,2,...,k1,2,...,k 的节点的最短路径。初值 f[0][i][j]f[0][i][j] 为原图的邻接矩阵。

则该问题可以划分为两个子问题:

f[k][i][j]f[k][i][j] 可以从 f[k-1][i][j]f[k−1][i][j] 转移来,表示 ii 到 jj 不经过 kk 这个节点。

也可以从 f[k-1][i][k] + f[k-1][k][j]f[k−1][i][k]+f[k−1][k][j] 转移过来,表示经过 kk 这个点。

于是:f[k][i][j] = \min (f[k-1][i][j], f[k-1][i][k]+f[k-1][k][j])f[k][i][j]=min(f[k−1][i][j],f[k−1][i][k]+f[k−1][k][j])

然后你就会发现 ff 最外层一维空间可以省略,因为 f[k][][]f[k][][] 只与 f[k-1][][]f[k−1][][] 有关,虽然省略后,无法确定 k,i,jk,i,j 的关系,但是 f[k][i][k]f[k][i][k] 和 f[k][k][j]f[k][k][j] 是由 f[k-1][i][k]f[k−1][i][k] 和 f[k-1][k][j]f[k−1][k][j] 计算而来,因此前者一定小于等于后者,省略后再取最小值并不会影响最终结果的正确性。

练习题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值