C++数据结构与算法分析——Floyd算法

介绍

Floyd算法是一种求多源汇最短路的算法,它可以求出任意两点间的最短距离(如果这两点连通的话),并且Floyd算法非常容易实现:

算法模板 O ( n 3 ) O(n^3) O(n3)

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

它总共有3层循环,每层n次,所以时间复杂度为 O ( n 3 ) O(n^3) O(n3)

Floyd算法的基本应用

例题

题目描述

给定一个n个点m条边的有向图,图中可能存在重边自环,边权可能为负数
再给定k个询问,每个询问包含两个整数xy,表示查询从点x到点y的最短距离,如果路径不存在,则输出impossible
数据保证图中不存在负权回路

输入格式

第一行包含三个整数n,m,k
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点x到点y的有向边,边长为z
接下来 k 行,每行包含两个整数 x,y,表示询问点x到点y的最短距离

输出格式

共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出impossible

数据范围

1 ≤ n ≤ 200 1≤n≤200 1n200,
1 ≤ k ≤ n 2 1≤k≤n^2 1kn2
1 ≤ m ≤ 20000 1≤m≤20000 1m20000,
图中涉及边长绝对值均不超过 10000 10000 10000

输入样例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:

impossible
1

算法设计

本题题意很简单,给定k个询问,每个询问给出两个点,让我们输出这两点的最短距离,如果这两点不在同一连通块就输出impossible
这正完美契合了Floyd算法,因此我们只要将细节处理一下即可(由于Floyd模板中用到了k,因此代码会将此处的k换成Q)
代码初始化:将 d [ i , i ] d[i,i] d[i,i]都初始化为0,其余初始化为+∞,因为从i到i的距离是为0的,要求最小值,所以其它距离刚开始时要为∞,后续才能顺利地更新

代码

#include
using namespace std;

const int N = 210,M = 20010,INF = 0x3f3f3f3f;
int n,m,Q;
int d[N][N]; // d[i][j] 表示从i到j的距离

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(){
    scanf("%d%d%d",&n,&m,&Q);
    
    // 初始化
    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 a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        
        d[a][b] = min(d[a][b],c); // 两点之间如果有多条边,那么选最短的边即可
    }
    
    // floyd
    floyd();
    
    // 处理询问
    while(Q --){
        int a,b;
        scanf("%d%d",&a,&b);
        
        if(d[a][b] > INF / 2) puts("impossible"); // 边权可能有负数,所以不会直接等于INF
        else printf("%d\n",d[a][b]);
    }
    
    return 0;
}

Floyd算法的证明

说是证明,其实不如说是原因,为什么Floyd算法的代码实现是这样的呢?(因为我是先学的板子才学的为什么,所以这里也姑且这么写)
这一切都要从动态规划开始说起。
闫氏DP分析法
C++数据结构与算法分析——Floyd算法_第1张图片

  1. 状态表示:我们用 d [ k , i , j ] d[k,i,j] d[k,i,j]表示从i走到j,且中间经过的点的编号不超过k的所有路径长度的最小值,什么叫做中间经过点的编号不超过k呢?假设ij中间经过所有点为 a 1 , a 2 , . . . , a m a_1,a_2,...,a_m a1,a2,...,am,那么这m个点的编号值都 < = k <=k <=k,注意不要与总共经过不超过k个点混淆。

  2. 状态计算 d [ k , i , j ] d[k,i,j] d[k,i,j]的计算可以分为两类:

    1. 从i走到j,且中间经过的点的编号不超过k且不包含k的所有节点路径:那么它就等价于从i走到j且经过的点的编号不超过k - 1的所有路径的最小值,即 d [ k − 1 , i , j ] d[k - 1,i,j] d[k1,i,j]

    2. 从i走到j,且中间经过的点的编号不超过k且包含k的所有节点路径:那么我们就会发现,它一定会从i走到k,并且一定会从k走到j,因此我们可以将它分为两段之和:

      1. 从i走到k,且中间经过的点的编号不超过k - 1的所有节点路径的最小值,即 d [ k − 1 , i , k ] d[k - 1,i,k] d[k1,i,k]
      2. 从k走到j,且中间经过的点的编号不超过k - 1的所有节点路径的最小值,即 d [ k − 1 , k , j ] d[k - 1,k,j] d[k1,k,j]

      并且前后两段是没有关系的,所以这种情况的表示就是 d [ k − 1 , i , k ] + d [ k − 1 , k , j ] d[k - 1,i,k] + d[k - 1,k,j] d[k1,i,k]+d[k1,k,j]

    因此,我们可以得出 d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , k ] + d [ k − 1 , k , j ] ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,k] + d[k - 1,k,j]) d[k,i,j]=min(d[k1,i,j],d[k1,i,k]+d[k1,k,j])

  3. 优化:我们在01背包中已经知道DP问题的状态转移方程可以降维,那么此刻我们发现状态转移方程的第一维只和 k , k − 1 k,k - 1 k,k1有关,那么能否把它也降维呢?
    只需要看看当计算到 d [ k , i , j ] d[k,i,j] d[k,i,j]时使用的k是当前的k还是上一个k即可,此时我们考虑特殊情况:当k = j时, d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , j ] + d [ k − 1 , j , j ] ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + d[k - 1,j,j]) d[k,i,j]=min(d[k1,i,j],d[k1,i,j]+d[k1,j,j]),由于刚开始初始化时我们将从i到i的距离都初始化为0了,因此状态转移等价于 d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , j ] + 0 ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + 0) d[k,i,j]=min(d[k1,i,j],d[k1,i,j]+0)相当于没有运算,因此每次的 d [ k , i , j ] d[k,i,j] d[k,i,j]都必然是第k - 1层的 d [ k − 1 , i , j ] d[k - 1,i,j] d[k1,i,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算法的扩展应用

Floyd可以应用于许多方面。

  1. 求多源汇最短路
  2. 求传递闭包
  3. 找最小环
  4. 求恰好经过k条边的最短路(用倍增思想)

你可能感兴趣的:(题解,笔记,最短路,算法,数据结构,c++)