【刷题】图论——最短路:Floyd【证明+模板】

定义图有:n个点,m条边 。

这Floyd算法是处理多源最短路的算法,可以应对边权为负的情况,但不能处理负环。

时间复杂度是 O ( n 3 ) O(n^3) O(n3)

算法步骤

  • f o r ( k = 1 ; k < = n ; k + + ) for(k = 1; k <= n; k ++ ) for(k=1;k<=n;k++)
    • f o r ( i = 1 ; i < = n ; i + + ) for(i = 1; i <= n; i ++ ) for(i=1;i<=n;i++)
      • f o r ( j = 1 ; j < = n ; j + + ) for(j = 1; j <= n; j ++ ) for(j=1;j<=n;j++)
        • d [ i , j ] = m i n ( d [ i , j ] , d [ i , k ] + d [ k , j ] ) d[i, j] = min(d[i, j], d[i, k] + d[k, j]) d[i,j]=min(d[i,j],d[i,k]+d[k,j])

算法证明

floyd是基于动态规划的思路实现的。
d [ k , i , j ] d[k, i, j] d[k,i,j]表示从点 i i i出发,只经过 1 , 2 , . . . , k 1, 2,...,k 1,2,...,k的中间点,到达 j j j的最短距离,设这条路径为 p p p

假设我们已经有了所有的 d [ k − 1 , x , y ] d[k-1, x, y] d[k1,x,y],现在想要求 d [ k , i , j ] d[k, i, j] d[k,i,j]

  1. 结点 k k k不是 p p p上的点,那么 p p p只由 1 , 2 , . . . , k − 1 1,2,...,k-1 1,2,...,k1这些点组成, d [ k , i , j ] = d [ k − 1 , i , j ] d[k,i,j]=d[k-1,i,j] d[k,i,j]=d[k1,i,j]
  2. 结点 k k k p p p上的点,那么可以将路径分解为 i i i p 1 p_1 p1—> k k k p 2 p_2 p2—> j j j
    因为 p 1 p_1 p1不含 k k k,所以 p 1 p_1 p1的最短长度是 d [ k − 1 , i , k ] d[k-1, i, k] d[k1,i,k]
    同理, p 2 p_2 p2的最短长度是 d [ k − 1 , k , j ] d[k-1, k, j] d[k1,k,j]
    p 1 p_1 p1 p 2 p_2 p2的最短长度相加就是最短总长度。

【刷题】图论——最短路:Floyd【证明+模板】_第1张图片

综上可以得到递推式:
d [ k , i , j ] = d [ k − 1 , i , k ] + d [ k − 1 , k , j ] d[k, i, j] = d[k-1, i, k] + d[k-1, k, j] d[k,i,j]=d[k1,i,k]+d[k1,k,j]

d d d的第一维 k k k可以用滚动数组去掉,只用保留后两维。

由递推顺序,先知道k-1才能知道k,因此k循环放在最外面

算法实现

【刷题】图论——最短路:Floyd【证明+模板】_第2张图片
注意这题边权可能为负,哪怕不存在路径,dis[i]=INF也可能被更新,会比INF更小,不能直接判断dis[i] = INF,可以判断dis[i] > INF / 2输出impossible。

此外该题有自环,因为不存在负环,因此没有负自环,那么走自环肯定不如不走,要将dis[i][i]置为0。

#include 
using namespace std;

const int N = 205, M = 20005, INF = 0x3f3f3f3f;

int n, m, k;
int dis[N][N];

void 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]);
            }
        }
    }
}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    int u, v, w;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            if (i == j) dis[i][j] = 0; // 自环为0
            else dis[i][j] = INF;
        }
    }
    while (m --)  {
        scanf("%d%d%d", &u, &v, &w);
        dis[u][v] = min(w, dis[u][v]); // 重边留最小
    }
    floyd();
    while (k -- ) {
        scanf("%d%d", &u, &v);
        if (dis[u][v] > INF / 2) printf("impossible\n");
        else printf("%d\n", dis[u][v]);
    }
    return 0;
}

你可能感兴趣的:(刷题,图论,算法,数据结构)