| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 算法小讲堂之最短路算法(Floyd+bellman+SPFA+Dijkstra) -> 正文阅读 |
|
[数据结构与算法]算法小讲堂之最短路算法(Floyd+bellman+SPFA+Dijkstra) |
前言如果你对图论相关知识一点也没有,那么建议您先去了解这些知识:https://acmer.blog.csdn.net/article/details/122310835,然后就可以快乐的学习最短路算法啦 视频中绘图软件:https://csacademy.com/app/graph_editor/ 配套讲解视频:https://www.bilibili.com/video/BV1Fa411C7wX/ 如果哪里讲的有问题欢迎在评论区指出,感谢支持! 一、Floyd算法1.1简介
1.2复杂度1.2.1时间复杂度O ( N 3 ) O(N^3) O(N3) 1.2.2空间复杂度O ( N 2 ) O(N^2) O(N2) 1.3优缺点1.3.1优点常数小,容易实现,思路简单,能处理大部分图 1.3.2缺点复杂度较高、不能处理负环图 1.4算法原理我们定义一个三维数组
f
[
k
]
[
u
]
[
v
]
f[k][u][v]
f[k][u][v]表示的是允许经过
[
1
,
k
]
[1,k]
[1,k]的点的
u
u
u到
v
v
v的最小距离,换句话说从
1
1
1到
k
k
k这些点可以作为
u
u
u到
v
v
v的中间节点,当然没也可以不经过,很显然我们如果要求解
u
u
u到
v
v
v的最小距离那么就是
f
[
n
]
[
u
]
[
v
]
f[n][u][v]
f[n][u][v](假设当前的图中有n个点的话),那么我们考虑怎么来维护这个关系呢,首先初始化来说,
f
[
0
]
[
u
]
[
v
]
f[0][u][v]
f[0][u][v]先初始化为 f [ k ] [ u ] [ v ] = m i n ( f [ k ? 1 ] [ u ] [ v ] , f [ k ? 1 ] [ u ] [ k ] + f [ k ? 1 ] [ k ] [ v ] ) f[k][u][v] = min(f[k-1][u][v],f[k-1][u][k] + f[k-1][k][v]) f[k][u][v]=min(f[k?1][u][v],f[k?1][u][k]+f[k?1][k][v]) 我们对经过
我们发现我们这个第一维的k其实最多能用到当前这一层以及上一层的状态,那么我们可以通过滚动数组优化将其去掉,那么新的代码即为:
关于第一维对结果无影响的证明:
模板题:多源最短路 代码实现
二、Bellman-Ford 算法2.1简介
2.2复杂度2.2.1时间复杂度O ( N M ) O(NM) O(NM) 2.2.2空间复杂度邻接矩阵: O ( N 2 ) O(N^2) O(N2) 邻接表: O ( M ) O(M) O(M) 2.3优缺点2.3.1优点能够处理负权图、能处理边数限制的最短路 2.3.2缺点复杂度不太理想,很容易被卡 2.4算法原理2.4.1松弛操作在介绍该算法前,先来介绍一下松弛操作,对于一个边 ( u , v ) (u,v) (u,v),松弛操作对应下面的式子: d i s [ v ] = m i n ( d i s [ v ] , d i s [ u ] + w ( u , v ) ) dis[v]=min(dis[v],dis[u]+w(u,v)) dis[v]=min(dis[v],dis[u]+w(u,v))。也就是我们将源点到v点的距离更新的一个操作 也就是开始可能源点 S S S到 v v v的路径为 S ? > v S->v S?>v,如果说经过 u u u点后再到 v v v的权值比直接到v小那么我们就更新一下路径最小值,这就是松弛操作 2.4.2 具体流程Bellman算法要做的事就是对于图中所有的边,我们都进行一次松弛操作,那么完成这整个操作的复杂度大概在 O ( M ) O(M) O(M),然后我们就一直循环的进行这个操作,直到我们不能进行松弛操作为止,就说明我们的单源最短路以及全部求完,那么我们需要多少次这样的完整操作呢,在最短路存在的情况下,由于一次松弛操作会使最短路的边数至少+1 ,而最短路的边数最多为 N ? 1 N-1 N?1 ,因此整个算法最多执行 N ? 1 N-1 N?1轮松弛操作。故总时间复杂度为 O ( N M ) O (NM) O(NM)。 2.4.3 负环问题上面提到了我们在求最短路存在的情况最多执行 N ? 1 N-1 N?1轮松弛操作,如果数据中出现了负环,那么我们在第N轮操作的时候也会更新 注意一点: 以 S S S点为源点跑 Bellman-Ford 算法时,如果没有给出存在负环的结果,只能说明从 S S S点出发不能抵达一个负环,而不能说明图上不存在负环。因为这个图可能是不连通的,那么对于不连通的图我们应该建一个虚点或者称之为超级源点,让这个点连向每一个其他的点并且权值为0,然后再来跑 b e l l m a n _ f o r d bellman\_ford bellman_ford 2.4.4 算法图解[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wdf53AKx-1645406487540)(D:\Typora\算法小讲堂之最短路算法.assets\image-20220220190624691.png)]
模板题:https://ac.nowcoder.com/acm/contest/27274/E 2.5代码实现
2.6判负环实现如果我们发现第 N N N轮操作也更新了那么说明存在负权回路
三、SPFA3.1简介
3.2复杂度3.2.1时间复杂度理想复杂度为 O ( K M ) O(KM) O(KM),这里的 K K K可以看作一个常数 最坏为 O ( N M ) O(NM) O(NM)但是一般情况下是跑不到这么多(除非出题人卡SPFA) 3.2.2空间复杂度邻接表: O ( M ) O(M) O(M) 邻接矩阵: O ( N 2 ) O(N^2) O(N2) 3.3优缺点3.3.1优点好写、效率挺快(一般来说即不被卡的话),能处理几乎所有类型的图 3.3.2缺点容易被网格菊花图卡成傻b 3.4算法原理3.4.1思想其实 S P F A SPFA SPFA算法就是 b l l m a n _ f o r d bllman\_ford bllman_ford算法加上了队列优化,我们在上面的 b e l l m a n _ f o r d bellman\_ford bellman_ford算法能知道我们实际上是将每一个边都松弛了 N ? 1 N-1 N?1次,实际上我们没必要松弛每一个点,因为有些点实际上是不用松弛太多或者说不用松弛的,那么我们希望去掉一些无用的松弛操作,这个时候我们用队列来维护哪些点可能会需要松弛操作,这样就能只访问必要的边了。同样的由于SPFA是队列优化的 b e l l m a n _ f o r d bellman\_ford bellman_ford那么同样能处理负权回路的图 tips: 虽然在大多数情况下 S P F A SPFA SPFA 跑得很快,但其最坏情况下的时间复杂度为 O ( N M ) O(NM) O(NM),将其卡到这个复杂度也是不难的,所以考试时要谨慎使用(在没有负权边时最好使用 D i j k s t r a Dijkstra Dijkstra 算法,在有负权边且题目中的图没有特殊性质时,若 S P F A SPFA SPFA 是标算的一部分,题目不应当给出 B e l l m a n ? F o r d Bellman-Ford Bellman?Ford 算法无法通过的数据范围)。 3.4.2流程用dis数组记录源点到有向图上任意一点距离,其中源点到自身距离为0,到其他点距离为INF。将源点入队,并重复以下步骤:
我们会发现SPFA的这个过程就和BFS是类似的,如果图是随机生成的,时间复杂度为 O(KM) (K可以认为是个常数,m为边数,n为点数)但是实际上SPFA的算法复杂度是 O(NM) ,可以构造出卡SPFA的数据,让SPFA超时。所以使用 S P F A SPFA SPFA前一定要三思 3.4.3算法图解[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWFcyHAC-1645406487542)(D:\Typora\算法小讲堂之最短路算法.assets\image-20220220190624691.png)]
3.5bellman-ford的其他优化除了队列优化(SPFA)之外,Bellman-Ford 还有其他形式的优化,这些优化在部分图上效果明显,但在某些特殊图上,最坏复杂度可能达到指数级。
既然有了优化,那么就会有相应的卡的方法,具体请看这一篇回答:https://www.zhihu.com/question/292283275/answer/484871888 模板题:https://ac.nowcoder.com/acm/contest/27274/E 3.6 SPFA最短路代码实现
3.7 SPFA判负环实现
3.8 SPFA判断正环关于 S P F A SPFA SPFA判断正环的可以参考这一题:https://blog.csdn.net/m0_46201544/article/details/123011318 四、Dijkstra算法4.1简介
4.2复杂度4.2.1空间复杂度4.2.1.1朴素DijkstraO ( N 2 ) O(N^2) O(N2) 4.2.1.2链式前向星优化+DijkstraO ( M ) O(M) O(M) 4.2.2时间复杂度4.2.2.1 朴素DijkstraO ( N 2 ) O(N^2) O(N2) 4.2.2.2 链式前向星+堆优化的DijkstraO ( ( n + m ) log ? 2 n ) O((n+m)\log_{2}n) O((n+m)log2?n) 4.3优缺点4.3.1 优点朴素 D i j k s t r a Dijkstra Dijkstra和堆优化的 D i j k s t r a Dijkstra Dijkstra基本上能解决所有的正权图最短路问题,时间复杂度不会受到限制 4.3.2 缺点不能处理负权图,如果需要处理负权图请移步 S P F A SPFA SPFA 但是在某些特定的含有负边的图DJ也是对的例如: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCnKNtnx-1645406487543)(D:\Typora\算法小讲堂之最短路算法.assets\image-20220220144637453.png)] 但是我们稍加变换,迪杰斯特拉就不能处理了: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGxba3uw-1645406487543)(D:\Typora\算法小讲堂之最短路算法.assets\image-20220220220211049.png)] 4.4 算法原理4.4.1思想D i j k s t r a Dijkstra Dijkstra的核心思想其实就是贪心思想,每次寻找一个临近点的dis值最小的点,然后我们再来对该点进行松弛操作 4.4.2 流程将结点分成两个集合:已确定最短路长度的点集(记为 S S S集合)的和未确定最短路长度的点集(记为 T T T集合)。一开始所有的点都属于 T T T集合。
4.4.3 算法图解[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bA6PpWMk-1645406487544)(D:\Typora\算法小讲堂之最短路算法.assets\image-20220220190645778.png)]
那么最终我们的dis值就变成了: d i s [ 1 ] = 0 d i s [ 2 ] = 1 d i s [ 3 ] = 4 d i s [ 4 ] = 3 dis[1] = 0 \\ dis[2] = 1 \\ dis[3] = 4 \\ dis[4] = 3 dis[1]=0dis[2]=1dis[3]=4dis[4]=3 4.5 优化我们发现,对于寻找 d i s dis dis值最小的点的操作,我们通过不同的方式维护的话那么算法的整体复杂度就会不同
注意的是:在稠密图中通过暴力方式维护效率更好, O ( N 2 ) O(N^2) O(N2),在稀疏图中通过堆优化的方式维护效率更高, O ( ( n + m ) log ? 2 n ) O((n+m)\log_{2}n) O((n+m)log2?n) 4.6 正确性证明(引自算法导论)d i j k s t r a dijkstra dijkstra 为什么是正确的呢?,当我们存储的所有的边都是正权边时,整个图的最小值不可能再被其他节点更新,所以我们在T集合中寻找dis最小值其实就是再选择全局最小值,也就是贪心的思想 下面用数学归纳法证明,在 所有边权值非负 的前提下,Dijkstra 算法的正确性。 简单来说,我们要证明的,就是在执行 1 操作时,取出的结点 u u u最短路均已经被确定,即满足 D ( u ) = d i s ( u ) D(u)=dis(u) D(u)=dis(u) 。
4.7 代码实现模板题:https://ac.nowcoder.com/acm/contest/27274/E 4.7.1 朴素Dijkstra(稠密图)
4.7.2 优先队列优化Dijkstra(稀疏图)
4.7.3 链式前向星+优先队列优化Dijkstra
4.8 路径打印问题我们可以定义一个 p r e pre pre数组,然后 p r e [ i ] pre[i] pre[i]记录的是上一个位置是哪一个节点,当然初始的时候我们全部初始化为 ? 1 -1 ?1,然后每次松弛操作的时候就更新一下上一个节点的位置,你有没有发现这就是链式前向星,然后最后打印的时候要么递归打印,那么手动写栈打印,这个方法不只是适用于Dijkstra,而且也适用于其他最短路算法,如 S P F A SPFA SPFA、 b e l l m a n _ f o r d bellman\_ford bellman_ford、 F l o y d Floyd Floyd等等 那么简单描述一下打印函数
4.9 路径统计问题其实我们在松弛操作的时候就能记录or更新从源点到当前点的路径条数,模板题可以参见下面的:最短路计数 五、Johnson 全源最短路径算法待补充 训练题单 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 15:43:14- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |