算法学习笔记(28)——Floyd算法(多源汇最短路)

Floyd 算法

Floyd 算法用于多源汇最短路问题。时间复杂度为 \(O(n^3)\)

算法思想

首先用邻接矩阵里的d[i][j]存储所有的边(重边的时候取min),然后就是三重循环,思路也是如果从ik,再从kj,这个距离(d[i][k] + d[k][j])能比d[i][j]小,就更新一下:d[i][j] = min(d[i][j], d[i][k] + d[k][j])
需要注意的是三重循环的顺序,先循环中间位置k,再循环源点i,最后循环汇点j
初始化时,d[i][j]存储的是从ij最短边,算法执行结束后d[i][j]就是从ij最短距离

由于存在负权边,如果d[i][j] = INFd[i][k] = INF,而d[k][j] < 0,那么d[i][j]实际是不可达但也会被更新,所以此时我们不能通过与INF比较来判断是不是可达的,选择一个较大的数(通常是INF/2)进行比较判断,大于该阈值则判为不可达。

Floyd算法属于动态规划算法

原理分析

状态表示:f[k][i][j]表示从i点出发,只经过1~k这些中间点,最后到达j点的最短距离。
那么我们就可以根据第k个点选或不选进行状态转移

  • 如果不选第k个点:f[k-1][i][j],表示从ij经过了1~k-1这些点。
  • 如果选第k个点:只能选择一次,否则最短路存在环。则状态表示为f[k-1][i][k] + f[k-1][k][j],意味着先从ik经过了1~k-1这些中间点,然后经过1~k-1这些中间点从k到达j

转移方程可以表示为:

\[f[k][i][j] = min(f[k-1][i][j], f[k-1][i][k] + f[k-1][k][j]) \]

由于转移方程左侧是k,右侧是k-1,所以只要从小到大枚举k即可将这一维度省去,也就演变成了d[i][j]

题目链接:AcWing 854. Floyd求最短路

#include <iostream>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int n, m, k;
int d[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 ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
    cin >> n >> m >> k;
    
    // 初始化邻接矩阵
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;
    
    while (m -- ) {
        int x, y, z;
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z); //两个点之间只存最短的那条边
    }
    
    floyd();
    
    while (k -- ) {
        int x, y;
        cin >> x >> y;
        if (d[x][y] > INF / 2) puts("impossible");
        else cout << d[x][y] << endl;
    }
    
    return 0;
}
posted @ 2022-12-10 09:32  S!no  阅读(109)  评论(0编辑  收藏  举报