最短路专题——Dijkstra、Floyd、Bellman-Ford、SPFA

news2024/9/22 3:38:20

目录

  • 前言
  • 一、全源最短路
    • 1.1 Floyd
  • 二、单源最短路
    • 2.1 Dijkstra
      • 2.1.1 堆优化版的Dijkstra
    • 2.2 Bellman-Ford
      • 2.2.1 队列优化版的Bellman-Ford:SPFA

前言

BFS是一种朴素的最短路算法,它可以找到无权图边权都相同的图的最短路,但是对于边权不完全相同甚至可能是负数的图,BFS并不能得到正确的结果,此时我们就需要使用其他的最短路算法来求解。

本文仅介绍最基础的几种最短路算法,思维导图如下:

单源最短路指的是给定一个源点,计算该源点到图中所有其他点的最短路径长度,而全源最短路则是计算图中任意两点之间的最短路径长度。

为方便叙述,接下来均假定图中节点的数量为 n n n,边的数量为 m m m,节点的编号为 1 ∼ n 1\sim n 1n

一、全源最短路

1.1 Floyd

Floyd算法是一种求解图中任意两点之间最短路径的经典算法,它适用于任何图,不管有向无向,边权正负,但是最短路必须存在(不能有个负环)。

该算法的基本思想是动态规划,具体来讲,定义一个三维数组 dp[k][x][y],表示从起点 x x x 经过子图 G ′ G' G 后到达终点 y y y 的最短路径的长度,其中 G ′ G' G 由节点 1 , 2 , ⋯   , k 1,2,\cdots,k 1,2,,k 构成( x x x y y y 不一定在 G ′ G' G 中)。

由上述定义可知,dp[n][x][y] 即为 x x x y y y 的最短路径长度(因为此时 G ′ = G G'=G G=G),dp[0][x][y] x x x y y y 的边权。

为计算 d p [ k ] [ x ] [ y ] dp[k][x][y] dp[k][x][y],我们可以将其分为以下两种情况考虑:

  • 不经过节点 k k k:即 d p [ k − 1 ] [ x ] [ y ] dp[k-1][x][y] dp[k1][x][y]
  • 经过节点 k k k:那么从 x x x y y y 的最短距离变成了 x x x k k k 的最短距离加上 k k k y y y 的最短距离,即 d p [ k − 1 ] [ x ] [ k ] + d p [ k − 1 ] [ k ] [ y ] dp[k-1][x][k]+dp[k-1][k][y] dp[k1][x][k]+dp[k1][k][y]

于是可得递推式:

d p [ k ] [ x ] [ y ] = min ⁡ ( d p [ k − 1 ] [ x ] [ y ] ,    d p [ k − 1 ] [ x ] [ k ] + d p [ k − 1 ] [ k ] [ y ] ) , k , x , y ≥ 1 dp[k][x][y] = \min(dp[k-1][x][y], \;dp[k-1][x][k]+dp[k-1][k][y]),\quad k,x,y\geq 1 dp[k][x][y]=min(dp[k1][x][y],dp[k1][x][k]+dp[k1][k][y]),k,x,y1

对应求解代码如下:

for (int k = 1; k <= n; k++)
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]);

我们可以使用滚动数组将其优化成二维的形式:

for (int k = 1; k <= n; k++)
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

注意到要想求 dp[1][..][..] 必须先求得 dp[0][..][..],而 dp[0][..][..] 实际上就是图的邻接矩阵,因此我们可以直接在图的邻接矩阵上进行动态规划。计算结束后,dp[a][b] 就代表了 a a a b b b 的最短距离。

🔗 AcWing 854. Floyd求最短路

#include <bits/stdc++.h>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int n, m, q;
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() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> 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 x, y, z;
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z);  // 本来应当是d[x][y] = z,这里取min是为了处理重边
    }

    floyd();

    while (q--) {
        int a, b;
        cin >> a >> b;
        if (d[a][b] > INF / 2) cout << "impossible\n";
        else cout << d[a][b] << "\n";
    }

    return 0;
}

容易看出,Floyd算法的时间复杂度为 O ( n 3 ) O(n^3) O(n3),空间复杂度为 O ( n 2 ) O(n^2) O(n2)

二、单源最短路

2.1 Dijkstra

Dijkstra算法是一种求解非负权图上单源最短路径的贪心算法。

具体来说,Dijkstra算法维护一个集合 S S S,其中包含已经确定最短路径的节点,以及一个集合 V \ S V\backslash S V\S,其中包含未确定最短路径的节点。初始时, S S S 只包含源节点, V \ S V\backslash S V\S 包含其余所有节点。然后,算法不断从 V \ S V\backslash S V\S 中选出距离源节点最近的一个节点,将其加入到 S S S 中,并且更新其邻居节点到源节点的距离。重复执行这个过程,直到目标节点被加入到 S S S 中,或者 V \ S V\backslash S V\S 为空为止。

我们需要维护一个距离数组 d d d,不妨设编号为 1 1 1 的节点是源点,则 d d d 初始时应当满足 d [ 1 ] = 0 ,   d [ 2.. n ] = + ∞ d[1]=0,\,d[2..n]=+\infty d[1]=0,d[2..n]=+

🔗 AcWing 849. Dijkstra求最短路 I

稠密图上,我们可以用邻接矩阵来实现:

#include <bits/stdc++.h>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];  // 稠密图用邻接矩阵
int d[N];  // 存储每个点到源点的距离
bool st[N];  // 用来标记一个节点是否已被加入到S中

int dijkstra() {
    // 初始化距离数组
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;

    // 循环n-1次即可,因为第n次循环毫无意义
    for (int i = 0; i < n - 1; i++) {
        // 找到距离源点最近且不在S中的节点
        int t = -1;
        for (int j = 1; j <= n; j++)
            if (!st[j] && (t == -1 || d[j] < d[t]))
                t = j;

        st[t] = true;

        // 用该点去更新其邻居节点的距离
        // 对于节点j,若j属于S,则d[j]并不会被覆盖掉,因为一定有d[j] <= d[t]
        // 若t与j不相连,则d[j]也不会更新,因为g[t][j] == INF
        for (int j = 1; j <= n; j++)
            d[j] = min(d[j], d[t] + g[t][j]);
    }

    return d[n] == INF ? -1 : d[n];
}


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    memset(g, 0x3f, sizeof(g));

    cin >> n >> m;
    while (m--) {
        int x, y, z;
        cin >> x >> y >> z;
        g[x][y] = min(g[x][y], z);  // 处理重边
    }

    cout << dijkstra() << "\n";

    return 0;
}

稀疏图上,我们可以用邻接表来实现(本题是稠密图,这里仅仅是为了展示邻接表的写法):

#include <bits/stdc++.h>

using namespace std;

const int N = 510, M = 1e5 + 10, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[M], ne[M], w[M], idx;
int d[N];
bool st[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dijkstra() {
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;

    for (int i = 0; i < n - 1; i++) {
        int t = -1;
        for (int j = 1; j <= n; j++)
            if (!st[j] && (t == -1 || d[j] < d[t]))
                t = j;

        st[t] = true;

        for (int j = h[t]; ~j; j = ne[j]) {
            int k = e[j];
            d[k] = min(d[k], d[t] + w[j]);
        }
    }

    return d[n] == INF ? -1 : d[n];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    memset(h, -1, sizeof(h));

    cin >> n >> m;
    while (m--) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }

    cout << dijkstra() << "\n";

    return 0;
}

容易看出,朴素版的Dijkstra算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

2.1.1 堆优化版的Dijkstra

先前我们在寻找 t 时(距离源点最近且不在 S S S 中的点)采用了暴力的做法,时间复杂度是 O ( n ) O(n) O(n)。如果用小根堆来存储距离和编号,则查询 t 的时间复杂度将降至 O ( 1 ) O(1) O(1)

🔗 AcWing 850. Dijkstra求最短路 II

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 2e5, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[N], ne[N], w[N], idx;
int d[N];
bool st[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dijkstra() {
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;

    priority_queue<PII, vector<PII>, greater<>> pq;
    pq.emplace(0, 1);  // 第一个放距离,第二个放节点编号,因为pair总是优先排序第一个元素

    while (!pq.empty()) {
        auto [_, t] = pq.top();  // 结构化绑定,因为不需要第一个元素所以用_来占位
        pq.pop();

        if (st[t]) continue;
        st[t] = true;

        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] > d[t] + w[i]) {
                d[j] = d[t] + w[i];
                pq.emplace(d[j], j);
            }
        }
    }

    return d[n] == INF ? -1 : d[n];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    memset(h, -1, sizeof(h));

    cin >> n >> m;
    while (m--) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }

    cout << dijkstra() << "\n";

    return 0;
}

优化后的时间复杂度为 O ( m log ⁡ n ) O(m\log n) O(mlogn)

由此可见,在稠密图中, m ≈ n 2 m\approx n^2 mn2,此时应当用朴素版的Dijkstra算法;而在稀疏图中, m ≪ n 2 m\ll n^2 mn2,此时应当用堆优化版的Dijkstra算法。

2.2 Bellman-Ford

Dijkstra不能解决负权边是因为当标记 st[j] = true 后,d[j] 就是最短距离了,之后就不能再被更新了(当有负权边时,贪心算法容易得到局部最优而不是全局最优)。如下图所示:

Dijkstra算法会依次标记 1 -> 2 -> 4 -> 5,当标记 5 之后,15 的最短路就确定了,而实际的最短路却是 1 -> 3 -> 4 -> 5

Bellman-Ford算法不断尝试对图上每一条边进行松弛,例如,对于边 a → w b a\xrightarrow{w} b aw b,该边的松弛操作为

d [ b ] = min ⁡ ( d [ b ] ,   d [ a ] + w ) d[b] = \min(d[b],\,d[a] + w) d[b]=min(d[b],d[a]+w)

其中 d [ x ] d[x] d[x] 表示 1 1 1 号点(起点)到 x x x 号点的最短距离。

每进行一轮循环,该算法就会对图上所有边都进行一次松弛操作。因此当循环 k k k 次后,边数不超过 k k k 的最短路就可以确定。

🔗 AcWing 853. 有边数限制的最短路

#include <bits/stdc++.h>

using namespace std;

const int N = 510, M = 10010, INF = 0x3f3f3f3f;

struct Edge {
    int a, b, w;
} edges[M];

int n, m, k;
int d[N];
int backup[N];

void bellman_ford() {
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;

    for (int i = 0; i < k; i++) {
        memcpy(backup, d, sizeof(d));  // 备份,防止发生串联更新,若无法理解可参考01背包问题中的dp数组的更新顺序
        for (int j = 0; j < m; j++) {
            auto e = edges[j];
            d[e.b] = min(d[e.b], backup[e.a] + e.w);  // 松弛操作
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m >> k;

    for (int i = 0; i < m; i++) {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }

    bellman_ford();

    if (d[n] > INF / 2) cout << "impossible\n";  // 可能会有负权边使得d[n]略小于INF,所以不能用d[n] == INF来判断
    else cout << d[n] << "\n";

    return 0;
}

时间复杂度为 O ( n m ) O(nm) O(nm)

2.2.1 队列优化版的Bellman-Ford:SPFA

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/401302.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AbstractQueuedSynchronizer从入门到踹门

概念设计初衷&#xff1a;该类利用 状态队列 实现了一个同步器&#xff0c;更多的是提供一些模板方法&#xff08;子类必须重写&#xff0c;不然会抛错&#xff09;。 设计功能&#xff1a;独占、共享模式两个核心&#xff0c;state、Queue2.1 statesetState、compareAndSetSta…

LayerNormalization

目录 1.BN的问题 1.1 BN与batch size 1.2 BN与RNN 2.LN详解 2.1 MLP中的LN 2.2 RNN中的LN 2.3 LN与ICS和损失平面平滑 BN不适用于RNN等动态网络和batchsize较小的时候效果不好。LayerNormalization的提出有效的解决BN的这两个问题。LN和BN不同点是归一化的维度是互相垂直…

SQL总结-排名的使用

##一、通过排名或者范围条件连表筛选特殊行 第一行最后一行区间&#xff08;第一行到第二行或者连续区间&#xff09;找中位数通过排名进行分组或者连续区间 ######1.使用条件筛选连表找区间 Employee 表保存了一年内的薪水信息。 请你编写 SQL 语句&#xff0c;来查询每个…

基于ChatRWKV智能问答和内容创作

ChatRWKV是对标ChatGPT的开源项目,希望做大规模语言模型的Stable Diffusion,测试很一段时间确实很像ChatGPT,从使用方法和内容结果上都很相似,但是还有一些差异。 文章目录 准备工作环境配置创建虚拟环境激活虚拟环境pip安装匹配版本ChatRWKV 使用模型替换常用参数设置使用…

手机磁吸背夹散热器制冷快速方案

手机散热器是什么&#xff1f;手机散热器分为几种类型&#xff1f;手机散热的方式都有哪些&#xff1f; 因为经常玩游戏&#xff0c;手机发热得厉害&#xff0c;都可以煎鸡蛋了&#xff0c;心想着要买个东西给手机散散热&#xff0c;没想到还真的有手机散热器。 不知道手机散…

mysql锁分类大全

前言 为什么会出现锁 MySQL中的锁是为了保证并发操作的正确性和一致性而存在的。 当多个用户同时对同一份数据进行操作时&#xff0c;如果不加控制地进行读写操作&#xff0c;就可能导致数据不一致的问题。例如&#xff0c;当多个用户同时对同一行数据进行写操作时&#xff…

uniapp使用webview嵌入vue页面及通信

最近刚做的一个需求&#xff0c;web端&#xff08;Vue&#xff09;使用了FormMaking库&#xff0c;FormMaking是一个拖拉拽的动态设计表单、快速开发的一个东西&#xff0c;拖拽完之后最终可以导出一个很长的json&#xff0c;然后通过json再进行回显&#xff0c;快速开发&#…

【Spring Boot】Spring Boot经典面试题分享

文章目录1. SpringBoot 简介2. SpringBoot 的优缺点3. SpringBoot 固定版本4. SpringBoot 的使用方式5. SpringBoot 自动配置原理6. PropertySource7. ImportResource8. springboot 的 profile 加载9. SpringBoot 激活指定 profile 的几种方式10. SpringBoot 项目内部配置文件加…

项目中用到的责任链模式

目录 1.什么是责任链&#xff1f;它的原理是什么&#xff1f; 2.应用场景 ​3.项目中的应用 传送门&#xff1a;策略模式&#xff0c;工作中你用上了吗&#xff1f; 1.什么是责任链&#xff1f;它的原理是什么&#xff1f; 将请求的发送和接收解耦&#xff0c;让多个接收对象…

NetApp AFF A900:针对任务关键型应用程序的解决方案

NetApp AFF A900&#xff1a;适用于数据中心的解决方案 AFF A 系列中的 AFF A900 高端 NVMe 闪存存储功能强大、安全可靠、具有故障恢复能力&#xff0c;提供您为任务关键型企业级应用程序提供动力并保持数据始终可用且安全所需的一切。 AFF A900&#xff1a;针对任务关键型应…

关于BLE的一些知识总结

数据包长度对于BLE4.0/4.1来说&#xff0c;一个数据包的有效载荷最大为20字节对于BLE4.2以上&#xff0c;数据包的有效载荷扩大为251字节传输速率在不考虑跳频间隔的情况下&#xff0c;最大传输速率为&#xff1a;1&#xff09;BLE4.0/4.1的理论吞吐率为39kb/s&#xff1b;2&am…

523-(ZCU102E的pin兼容替代卡) 基于 XCZU15EG的双 FMC通用信号处理板

&#xff08;ZCU102E的pin兼容替代卡&#xff09; 基于 XCZU15EG的双 FMC通用信号处理板 一、板卡概述 本板卡基于Xilinx Zynq Ultrascale MPSOC系列SOC XCZU15EG-FFVB1156架构&#xff0c;PS端搭载一组64-bit DDR4&#xff0c;容量32Gb&#xff0c;最高可稳定运行在240…

solidworks调用toolbox出现未配置怎么办

问题背景 本人最近在跟随B站恶补solidworks&#xff0c;学习链接如下 https://www.bilibili.com/video/BV1iw411Z7HZ/?spm_id_from333.337.search-card.all.click 但是在学习的过程中遇到了这样的问题 智能点现在配置&#xff0c;正常的应该是这样的 扒拉了网上所有的解决办…

04从零开始学Java之可能是最详细的Java环境配置教程

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者前言在上一篇文章中&#xff0c;壹哥给大家重点讲解了Java实现跨平台的原理&#xff0c;不知道你现在有没有弄清楚呢&#xff1f;如…

window vscode编辑appsmith源码

前言 本来最开始用的idea打开wsl中的appsmith&#xff0c;卡得一批。最后没办法&#xff0c;用自己的电脑装成ubuntu server&#xff0c;然后vscode的远程开发对appsmith源码进行编辑。如果自己电脑内存16个G或者更大可能打开wsl中的估计会还好&#xff0c;我公司电脑只有8g所…

Virtualbox Vagrant 迁移与恢复

前 言 window10电脑重新安装C盘。重装前正常使用的VirtualBox虚拟机&#xff0c;启动失败&#xff0c;先是启动报各种找不到{uuid.vmdk}文件的错误&#xff0c;使用原来的虚拟机配置文件虽然能正常启动&#xff0c;但是关闭虚拟机后&#xff0c;xxx.vbox配置文件的快照顺序又被…

MySQL主从复制,读写分离

目录 一、MySQL主从复制介绍 MySQL复制过程分成三步 二、主库配置master 1、步骤1 2、第二步:重启Mysql服务 3、第三步&#xff1a;登录Mysql数据库&#xff0c;执行下面SQL 4、第四步&#xff1a;登录Mysql数据库&#xff0c;执行下面SQL&#xff0c;记录下结果中File和…

vue2 使用 cesium 【第二篇-相机视角移动+添加模型】

vue2 使用 cesium 【第二篇-相机视角移动添加模型】 搞了一阵子 cesium&#xff0c;小白入门&#xff0c;这东西很牛逼&#xff0c;但是感觉这东西好费劲啊&#xff01;网上资料不多&#xff0c;每个人的用法又不一样&#xff0c;操作起来真的是绝绝子。之前写了一篇 vue2 使用…

【C#进阶】C# 反射

序号系列文章11【C#基础】C# 预处理器指令12【C#基础】C# 文件与IO13【C#进阶】C# 特性文章目录前言1&#xff0c;反射的概念2&#xff0c;使用反射访问特性3&#xff0c;反射的用途4&#xff0c;反射的优缺点比较4.1 优点&#xff1a;4.2 缺点&#xff1a;5&#xff0c;System…

吲哚菁绿-巯基,ICG-SH,科研级别试剂,吲哚菁绿可用于测定心输出量、肝脏功能、肝血流量,和对于眼科血管造影术。

ICG-THIOL,吲哚菁绿-巯基 中文名称&#xff1a;吲哚菁绿-巯基 英文名称&#xff1a;ICG-THIOL 英文别名&#xff1a;ICG-SH 性状&#xff1a;绿色粉末 溶剂&#xff1a;溶于二氯甲烷等其他常规有机溶剂 稳定性&#xff1a;冷藏保存&#xff0c;避免反复冻融。 存储条件&…