文章目录

  • 最短路问题分类
  • 单源最短路
    • 朴素Dijkstra算法
    • 堆优化版Dijkstra
    • Bellman-Ford算法
    • SPFA算法
      • **SPFA算法判断负环**
  • 多源汇最短路
    • Floyd算法

最短路问题分类

最短路算法知识结构图

注:n为顶点数,m为边数
每种不同的情况都有相应最适合的算法,但不用拘泥于一定要用某个算法。
单源最短路:求一个点到其他所有点的最短距离
多源汇最短路:源 指 起点,汇 指 终点。任意两点间的最短距离(起点、终点不确定)
稠密图:m至少和n^2大致是一个级别 (稠密图用邻接矩阵存储)
稀疏图:m至少和n大致是一个级别 (稀疏图用邻接表存储)
重点:建图,如何把原问题抽象成一个最短路问题,如何定义点和边。

单源最短路

朴素Dijkstra算法

实现步骤:
1.初始化距离 dist[1] = 0,dist[i] = + ∞ (dist数组表示起点到i点的距离)
2.for循环 1~n循环n次 (s:当前已确定最短距离的点)
①找到不在s中的距离起点最近的点,赋给t O(n^2)
②把t加到s中去 O(n)
③用t来更新其他所有点的距离 O(m)(看从1号点<起点>到x的距离是否大于到t的距离,即dist[x] > dist[t] 如果是,则用t来更新)
找到没有确定最短路且距离起点最近的点,并通过这个点更新其他点到起点的最短距离(即以这个点为过渡点)
每次循环都可以确定一个点的最短距离,循环n次就可以确定每个点到起点的最短距离了。
模板

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[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 || dist[t] > dist[j]))t = j;// 用t更新其他点的距离for (int j = 1; j <= n; j ++ )dist[j] = min(dist[j], dist[t] + g[t][j]);st[t] = true;}if (dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}

典例——AcWing 849.Dijkstra求最短路I

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 510;int n,m;
int g[N][N];  //邻接矩阵
int dist[N]; //当起点(1号点)到每个点的最短距离
bool st[N]; //标记当前点是否确定最短距离int dijkstra()
{//初始化memset(dist,0x3f,sizeof(dist)); //除起点外全部初始化为正无穷dist[1] = 0;for(int i = 0; i < n; i ++ ){int t = -1;for(int j = 1; j <= n; j ++ )   //在没有确定最短路的所有点中找出距离起点最短的点if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;st[t] = true; //标记t为已确定最短路for(int j = 1; j <= n; j ++ )   //用t更新其他店的最短距离dist[j] = min(dist[j],dist[t] + g[t][j]);}if(dist[n] == 0x3f3f3f3f) return -1;  //起点和终点不连通return dist[n];
}int main()
{cin >> n >> m;memset(g,0x3f,sizeof g);while(m -- ){int a,b,c;cin >> a >> b >> c;g[a][b] = min(g[a][b],c);  //如果有重边,保留其中最短的一条}cout << dijkstra();return 0;
}

堆优化版Dijkstra

实现步骤:
1.初始化距离 dist[1] = 0,dist[i] = + ∞ (dist数组表示起点到i点的距离)
2.for循环 1~n循环n次 (s:当前已确定最短距离的点)
①找到不在s中的距离起点最近的点,赋给t <优化:用小根堆来找> O(1)
②把t加到s中去 O(n)
③用t来更新其他所有点的距离 O(mlogn) <利用堆更新每个点的时间复杂度为O(logn)>
整个算法的时间复杂度就可以优化成 O(mlogn)

m≤n^2 mlogm≤2mlogn 所以两种写法的时间复杂度是一个级别的, 所以堆优化版的Dijkstra一般是不需要手写堆的。

典例——AcWing 850.Dijkstra求最短路II

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 2e5;int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储
int dist[N];
bool st[N];typedef pair<int,int> PII; //first存距离,second存结点编号void add(int a,int b,int c)
{e[idx] = b,w[idx] = c,ne[idx] = h[a], h[a] = idx ++ ;
}int dijkstra()
{//初始化memset(dist,0x3f,sizeof(dist));dist[1] = 0;priority_queue<PII,vector<PII>,greater<PII>> heap; //小根堆heap.push({0,1});while(heap.size()){PII t = heap.top();heap.pop();int ver = t.second, distance = t.first; //定义ver为编号,distance为距离if(st[ver]) continue; st[ver] = true;for(int i = h[ver]; i != -1; i = ne[i]) //用t更新其他点的最短距离{int j = e[i];if(dist[j] > distance + w[i]){dist[j] = distance + w[i];heap.push({dist[j],j});}}}if(dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}int main()
{//初始化邻接表memset(h,-1,sizeof h);cin >> n >> m;while(m -- ){int a,b,c;scanf("%d%d%d",&a,&b,&c);add(a,b,c);}cout << dijkstra();return 0;
}

Bellman-Ford算法

思路:循环n次,每次循环都遍历所有边,每次都更新一个结点的最短距离,那么循环n次,就可以确定n个点的最短路。
注:存在最短路时,一般不存在负权回路,即使存在,这个负环也不能在这条路径上(若存在负权回路,那么每在这个回路上转一圈,距离都会减小,这样可以无限次停留在负权回路,导致距离变为 - ∞)
实现步骤
1.初始化: dist[1] = 0,dist[i] = + ∞
2.for循环n次:
for循环m次:遍历所有边
dist[b] = dist[a] + w; (松弛条件)
时间复杂度O(nm)
循环n此后所有的边都满足 dist[b] ≤dist[a] + w (三角不等式)
迭代k次的含义:从1号点经过不超过k条边走到每个点的最短距离
找负环的方法:若果第n次迭代时,又更新了路径,说明存在长度≥n的最短路径。如果一个路径有n条边,那么就意味着有n+1个点,而图中一共只有n个点,由抽屉原理,就一定有两个点编号相同,那么这条路径上就一定存在环,而且是在更新过之后,所以这个环就一定是负环。所以这个算法可以用来找负环,方法就是看第n次是否有更新。(了解即可,一般找负环不用Bellman-Ford,而用SPFA)

典例——AcWing 853.有边数限制的最短路

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 510,M = 10010;int n,m,k;
int dist[N],backup[N]; //backup 为备份数组,存储dist上一次的值struct edges
{int a,b,w;
}edge[M];//存储边和权值int bellman_ford()
{//初始化memset(dist,0x3f,sizeof(dist));dist[1] = 0;//迭代k次for(int i = 0;  i < k; i ++ ){memcpy(backup,dist,sizeof(dist)); //注1:备份for(int j = 0; j < m; j ++ ){int a = edge[j].a, b = edge[j].b, w = edge[j].w;dist[b] = min(dist[b],backup[a] + w); //用备份的值更新最短路}}if(dist[n] > 0x3f3f3f3f / 2) return -1; //注2return dist[n];
}
int main()
{cin >> n >> m >> k;for(int i = 0; i < m; i ++ ){int a,b,w;cin >> a >> b >> w;edge[i] = {a,b,w};}int t = bellman_ford();if(t == -1) puts("impossible");else cout << t << endl;return 0;
}

思路解析:
这道题是Bellman-Ford算法的一个典型应用,而且此题只能用Bellman-Ford来做。
这道题的特殊性在于:要求的是从1号点到n号点最多经过k条边的最短距离,这里对边数做了限制,我们只需要迭代k此就可以了。
注1 memcpy(backup,dist,sizeof(dist)); 这里我们用backup数组来存储上一次迭代dist的值,防止发生串联,影响结果。

注2 if(dist[n] > 0x3f3f3f3f / 2) return -1; 这里判断最短路不存在的条件为什么不能写成 if(dist[n] == 0x3f3f3f3f )呢?
如果有负权边存在,我们在更新最短路的时候,可能会减掉一个数,∞减掉一个数,可能变成一个很大的而非∞的数,这时我们也认为,最短路是不存在的。

SPFA算法

SPFA适用于没有负环的图,但是绝大多数题目都是不存在负环的。SPFA算法是对上述Bellman-Ford算法的优化。Bellman-Ford是开两个循环,遍历每个点,每条边,这其中有很多重复的工作,SPFA对其进行了优化。
Bellman-Ford中dist[b] ≤dist[a] + w,dist[a]不一定是之前更新过的点,然而事实上,只有== 用之前被更新过的点去更新其他点,才会得到最短路 ==(只有一条路径上前面的点变小了,经过这个点的路径才会变小)。SPFA正是在这一点上进行了优化。
实现步骤:(用BFS来优化,队列中存储待更新的点)

  1. 1号点入队
  2. while(queue不空)
    ①取队头并入队(t = q.front() )
    q.pop();
    ②更新t的所有出边 t → b
    更新成功后,如果b不在队中,则入队。(b→queue)
    SPFA算法实现的过程和Dijkstra算法实现过程非常相似。
    典例——AcWing 851. spfa求最短路
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int  N = 1e5 + 10,M = 1e5 + 10;int n,m;
int h[N],e[N],w[N],ne[N],idx;
int dist[N];
bool st[N]; //标记第i个点是否在队列中,防止存储重复的点void add(int a,int b,int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}int spfa()
{memset(dist,0x3f,sizeof(dist));dist[1] = 0;queue<int> q; //存储待更新的点q.push(1); //1号点入队st[1] = true; while(q.size()) //队列不空{int t = q.front(); //取队头q.pop();st[t] = false;for(int i = h[t]; i != -1; i = ne[i]) //更新t的临边结点{int j = e[i];if(dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if(!st[j]){q.push(j);st[j] = true;}}}}if(dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
int main()
{memset(h,-1,sizeof h);cin >> n >> m;while( m -- ){int a,b,c;scanf("%d%d%d",&a,&b,&c);add(a,b,c);}int t = spfa();if(t == -1) puts("impossible");elsecout << t ;return 0;}

SPFA算法判断负环

思路与上面所说的Bellman-Ford算法判断负环是相同的,这里我们另开一个cnt数组来存当前最短路的边数。每次更新边的同时,更新cnt,当== cnt ≥ n ==时,就说明存在负环。(多出来的边一定是负环,不然不会被更新到最短路中)

典例——AcWing 852.spfa判断负环

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>using namespace std;const int N = 2010,M = 10010;int n,m;
int h[N],e[M],w[M],ne[M],idx;
int dist[M],cnt[M];
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 spfa()
{queue<int> q;//判断整个图中是否存在负环,需要将所有点入队for(int i = 1; i <= n; i ++ ){q.push(i);st[i] = true;}   while(q.size()){int t = q.front();q.pop();st[t] = false;for(int i = h[t]; i != -1; i = ne[i]){int j = e[i];if(dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; //更新最短路上的边数if(cnt[j] >= n) return true;if(!st[j]){q.push(j);st[j] = true;}}}}return false;
}int main()
{cin >> n >> m;memset(h,-1,sizeof h);while(m -- ){int a,b,c;cin >> a >> b >> c;add(a,b,c);}if(spfa()) puts("Yes");else puts("No");return 0;
}

多源汇最短路

Floyd算法

原理:基于动态规划,状态表示(三维):d[k,i,j]:从点 i 只经过1~k这些中间点到达点 j 的最短距离。
那么就有:d[k,i,j] = d[k-1,i,k] + d[k-1,k,j] (状态更新)
思路:我们可以把点i到j的方式归为两类

  1. 从i 直接到 j;
  2. 从i 经过 若干个点 到 j;
    模板:三重循环,d(i,j)表示从i到j的最短路的长度
//初始化:for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )if (i == j) d[i][j] = 0; //Floyd算法要求不能存在负环else d[i][j] = INF;
//算法实现
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]);
}

典例——AcWing 854.Floyd 求最短路

#include <iostream>using namespace std;const int N = 210,M = 20010,INF = 1e9;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;int t = d[x][y];if(t > INF/2) puts("impossible");//最短路不存在的判断条件写成t > INF/2,是因为可能存在负环,这个问题在前面有讲过。 else cout << t << endl;}return 0;
}

以上内容就是我总结的最短路问题的学习笔记。欢迎大佬们批评指正。

最短路问题(超详细~~)相关推荐

  1. k8s核心组件详细介绍教程(配超详细实例演示)

    本文实验环境基于上篇文章手把手从零开始搭建k8s集群超详细教程 本文根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps学习总结而来 k8s核心组件介绍 1 ...

  2. 手把手从零开始搭建k8s集群超详细教程

    本教程根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps同步所做笔记教程 k8s集群搭建超详细教程 1. 基本环境搭建 1. 创建私有网络 2. 创建服务 ...

  3. 归并排序(代码注释超详细)

    归并排序: (复制粘贴百度百科没什么意思),简单来说,就是对数组进行分组,然后分组进行排序,排序完最后再整合起来排序! 我看了很多博客,都是写的8个数据呀什么的(2^4,分组方便),我就想着,要是10 ...

  4. 超详细的Java面试题总结(四 )之JavaWeb基础知识总结

    系列文章请查看: 超详细的Java面试题总结(一)之Java基础知识篇 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java ...

  5. 400 多行代码!超详细 Rasa 中文聊天机器人开发指南 | 原力计划

    作者 | 无名之辈FTER 责编 | 夕颜 出品 | 程序人生(ID:coder_life) 本文翻译自Rasa官方文档,并融合了自己的理解和项目实战,同时对文档中涉及到的技术点进行了一定程度的扩展, ...

  6. 一份超详细的数据科学路线图!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:魔王.陈萍,来源:机器之心 从头开始学习数据科学的免费资源. 如何 ...

  7. OSSIM系统的安装教程(超详细)

    OSSIM系统的安装教程(超详细) 一.创建虚拟机 二.安装ossim系统 三.web登录 一.创建虚拟机 首先在网上下载OSSIM系统的镜像,记住自己保存的位置. 打开vm,新建虚拟机. 选择自定义 ...

  8. 超详细的 Redis Cluster 官方集群搭建指南,适用于 redis 5.x, 6.x

    今天从 0 开始搭建 Redis Cluster 官方集群,解决搭建过程中遇到的问题,超详细. 旧版本使用 redis-trib.rb ruby 脚本安装集群,5.0版本redis-cli 已经自带 ...

  9. Python的零基础超详细讲解(第十三天)-Python的类与对象

    基础篇往期文章如下: Python的零基础超详细讲解(第一天)-Python简介以及下载 Python的零基础超详细讲解(第二天)-Python的基础语法1 Python的零基础超详细讲解(第三天)- ...

  10. Python的零基础超详细讲解(第十二天)-Python函数及使用

    基础篇往期文章: Python的零基础超详细讲解(第一天)-Python简介以及下载_编程简单学的博客-CSDN博客 Python的零基础超详细讲解(第二天)-Python的基础语法1_编程简单学的博 ...

最新文章

  1. ZLAN串口转接以太网ZLSN3003S
  2. 初学Java--计算器
  3. ChannelFactory创建和销毁昂贵
  4. 查看Linux下网卡状态或 是否连接(转)
  5. LeetCode----13. 罗马数字转整数
  6. xml解析:Sax,Dom,pull解析
  7. 【CodeForces - 520B】Two Buttons (bfs或dp或时光倒流,trick)
  8. apache多域名绑定手记
  9. Centos打开、关闭、结束tomcat,及查看tomcat运行日志
  10. 微信的cookie 和 session
  11. 方方格子access_安装了这两款Office插件,我确信你的办公效率会大大提高!
  12. LoadRunner教程(8)-LoadRunner 负载生成器
  13. Ubuntu 解析迅雷链接
  14. bsd协议开源框架tcp服务器,BSD协议栈架构浅析
  15. Sovit3D三维可视化开发工具动画定义新功能
  16. 基于Radon滤波反投影算法的CT图像重建matlab仿真
  17. regedit命令应用
  18. Cocos--叠加打印log
  19. [转]NAT类型与检测
  20. Bt(宝塔面板)phpmyadmin打不开的解决办法

热门文章

  1. 第13周项目4 立体类族公有的抽象类
  2. Parameter ‘ew‘ not found. Available parameters are [wrapper, page, param1, param2]
  3. html5行星环绕,Two.js实现星球环绕动画效果
  4. 安装jdk并配置环境变量
  5. 网络安全与网站安全及计算机安全:如何使用Kali Linux进行内网或局域网安全演练?
  6. win10无法连接网络里的其他计算机名,win10系统无法访问局域网的其他电脑的解决办法...
  7. 使用OpenCV读取视频和视频的相似性度量
  8. 猫 老鼠 人的编程题
  9. 人生下来就是一个矛盾体
  10. 网页制作经典技巧24条