明显地,dijkstra计算不了的存在负权边或负权环的图,因此我们需要了解更常用的最短路算法:Bellman-Ford算法。

负权回路对单源最短路径的影响

在某些单源最短路的问题中,可能存在负权为负的边。如果图G(V,E)不包含由源点s可达的负权回路,则对于所有的点v,从s抵达点v的最短路径权值&(s,v)依然正确,即使某一些边是负数也是如此。

但如果存在一个从s可达的负权回路,最短路径的定义就不能成立了。
从s到该节点不再存在最短路径,因为我们可以沿着负权环,每走一圈都能获得权值更小的路径,可以无限小的“最短路径”,因此如果从s到v的某条路径存在负权回路,我们定义&(s,v)=- ∞ \infty ∞

使用bellman-Ford算法可以处理存在负权边、负权环的图。

bellman-ford

存在一个图G<V,E>,设图G有n个点。
假设起点为u,终点为v
从u到v如果存在最短路径,且每个顶点最多经过一次,可以发现该路径上的点数最多为n个,有n-1条边;最长的路径经过n-1条边。
从dijkstra算法中,我们从原点出发,使用最小点向外扩展更优路径,直到终点为止。
可以发现,路径的长度逐渐延长。对于一条长度 <= k的路径可以由:
①长度 <= k-1的路径再加一条边得到;
②长度 <= k-1的路径直接抵达点v;

动规

定义 d[v][k] 的含义为源点s到每个点的最短距离经过不超过k条边
设存在一个点u,u->v的权值为w
则可以从点u添加一条边u->v,抵达点v。
则此时d[v][k]可更新为min(d[v][k] , d[u][k-1]+w);
当然地,对于d[v][k]也可以使用更少的边抵达,也就是说d[v][k]可以使用d[v][k-1]进行更新,
因此 d [ v ] [ k ] = m i n { d [ u ] [ k − 1 ] + w d [ v ] [ k − 1 ] d[v][k]=min \begin{cases} d[u][k-1]+w\\ d[v][k-1] \end{cases} d[v][k]=min{d[u][k−1]+wd[v][k−1]​
由于最长的路径最多有n-1条边,因此可以将k从1~n-1进行递推,递推结束后,图上的所有路径都会枚举结束,得到最小路径。

步骤:
for循环①,从1到n-1,枚举路径长度k
for循环②,对于每一个路径长度k,我们可以枚举每一条边u->v,松弛(u , v , w)
也就是进行动规递推: d [ v ] [ k ] = m i n { d [ u ] [ k − 1 ] + w d [ v ] [ k − 1 ] d[v][k]=min \begin{cases} d[u][k-1]+w\\ d[v][k-1] \end{cases} d[v][k]=min{d[u][k−1]+wd[v][k−1]​

d[]:从源点s到其他顶点的最短路径长初始为无穷大,自然地,从s到s的距离为0。代表从s到各个顶点的最短路径长度。
1.初始化:除了起点的距离为0外(d[s] = 0),其他均设为无穷大。
2.迭代求解:循环对边集合E的每条边进行松弛操作,使得顶点集合V中的每个顶点v的距离长逐步逼近最终等于其最短距离长;
3.验证是否负权环:再对每条边进行松弛操作。如果还能有一条边能进行松弛,那么就返回False,否则算法返回True

代码如下:

到此时,算法初步完成,但是如何去判断是否存在负权环呢?

思考,如果出现负权环,会发生什么?

如果出现负权环,最短路径的长度一定会无限增长。再走一遍该算法,依然会继续更新负权环路径上的更优路径,
而如果没有负权环,该算法循环n-1次后,将会获得最短路径,就算再走一遍也不会获得更优路径
因此,再循环一次上诉步骤,一旦发现存在一条边u->v需要进行松弛,也就是d[v]>d[u]+w;
则存在负权回路,中止算法。

时间复杂度:

可以发现,算法的运行时间并不理想,如何优化?

分析一次bellman-Ford运行过程,发现存在大量的边不能进行松弛,却依然被枚举了

思考:在bellman-ford中,哪些边才是真正需要松弛地呢?

可以发现,如果某个点,在本次循环中被松弛了,那么显然它也有了松弛别人的资本
因此,只有被松弛的点,才能在下一次循环中松弛别人。
也就是说,只有被松弛的点对应的邻接边,会继续进行松弛,我们只需要考虑这些边就可以了,怎么修改算法呢?

记录被松弛过的点,然后在以后的循环中只枚举这些点对应的邻接边,如何记录点呢?

很简单地,我们就想到了队列,枚举队头的邻接边,执行松弛操作,将被松弛过的点入队。如此反复,便可实现,只枚举有需要枚举的边。
而使用这种方法优化的算法就是:

SPFA算法

SPFA算法是Bellman-Ford的一个优化算法,
与dijkstra和Bellman-Ford算法一样,使用邻接表vector存图,用数组记录每个节点的最短路长。
①设立队列q,用来保存被松弛过的点,这些被松弛过的点才有更优解去松弛它的邻接点,我们称这些被松弛过的点为“待优化”的节点。
②从队头取出待优化的节点u,枚举它的邻接点进行松弛,通过路径u->v,权值为w,如果存在d[u]+w<d[v],则更新d[v]。
也就是使用d[u]去更新它的邻接点d[v]的更优路径,而这些邻接点v一旦被更新,显然,在下一次循环中:
③我们又可以以v为出发点,去继续更新点v的邻接点,因此我们把点v入队。
这样不断地从队列中取出节点进行松弛操作,直到队列为空。
代码如下:

void spfa(int st){memset(d,0x7f,sizeof(d));d[st]=0;q.push(st);while(!q.empty()){int u=q.front();q.pop();for(int i=0;i<g[u].size();i++){int v=g[u][i].v;int w=g[u][i].w;if(d[v]>d[u]+w){d[v]=d[u]+w;q.push(v); }}}
}

我们大功告成了吗?
如果存在图,如右图所示:

可以发现,节点u多次入队,那我们应该怎么办?
显然,某一个节点入队了,表示它处于待优化状态,后面如果依然发现存在更优路径,d[v]被更新。然而如果此时点v仍在队内,当它出队时我们使用的是新的d[v]对邻接点进行松弛,所以没有必要在后面再次入队,已经在队里面的,不需要重复入队了。
简单地,使用数组标记即可,所以代码修改为:

void spfa(int st){memset(d,0x7f,sizeof(d));d[st]=0;q.push(st);inque[st]=1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=0;i<g[u].size();i++){int v=g[u][i].v;int w=g[u][i].w;if(d[v]>d[u]+w){d[v]=d[u]+w;if(inque[v]==0){q.push(v);inque[v]=1;} }}}
}

再次灵魂拷问,解决问题了吗?
如果存在负权回路那怎么办呢?
观察每一个点v,它最多被其他节点更新几次呢?
如果不存在负权回路,显然其他的各个节点都有可能更新节点v的路径d[v],最坏情况下,其他的所有节点都更新一次点v,也就是说点v最多被更新n-1次,如果点v被更新了n次,意味着什么?
显然存在负权环,可以无限更新。
那么我们判环的操作就简单了,只需要记录节点被更新的次数即可,如何记录?
简单地,依然使用数组计数,另开数组count,在哪里记录?
松弛操作:if(d[v]>d[u]+w) count[v]++???
代码如下:

bool spfa(int st){memset(d,0x7f,sizeof(d));d[st]=0;q.push(st);inque[st]=1;count[st]++;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=0;i<g[u].size();i++){int v=g[u][i].v;int w=g[u][i].w;if(d[v]>d[u]+w){d[v]=d[u]+w;if(inque[v]==0){q.push(v);inque[v]=1;count[v]++;if(count[v]==n)return false;} }}}return true;
}

步骤如下:
(1)初始化:将源点到每一点的距离dis初始化为+∞,将标记数组vis初始化为false,表示所有点都没有入队;更新源点的dis为0,同时将源点入队,改变vis为true;

(2)松弛更新操作:每次取队头元素tmp(更新vis[ tmp ]为false),枚举从tmp出发的每一条边,如果某条边能够松弛且该点没有在队列中,那么将该点入队;直至队列为空;

(3)判负环:如果图中某点入队的次数超过n,那么图中存在负环(SPFA无法处理带负环的问题)。

SPFA与Bellman-Ford算法的区别:
同:两者都属于标号修正的范畴,计算过程都是迭代式的,最短路长估计值都是临时的,都采用了不断逼近最优解的贪心策略,只在最后一步才确定想要的结果。
差异:在Bellman-Ford算法中,要是某个点的最短路长估计值被更新了,那么就必须对所有边的尾做一次松弛操作;在SPFA算法中,某个点的最短路径估计值被更新,仅需要对该点出边的端点做一次松弛操作。

Bellman-Ford Spfa相关推荐

  1. SPFA or bellman ford松弛法--单源最短路

    问题概述:有编号1-n的n个站点,有m条公交车路线,公交车只从一个起点站直接到达终点站,是单向的且每条路线有它自己的车费,有P个人早上从1出发,他们要到达每一个公交站点,然后到了晚上再返回点1,求所有 ...

  2. bellman ford 算法 判断是否存在负环

    Flyer 目录视图 摘要视图 订阅 微信小程序实战项目--点餐系统        程序员11月书讯,评论得书啦        Get IT技能知识库,50个领域一键直达 关闭 bellman for ...

  3. Bellman——Ford算法

    Bellman--Ford 算法介绍 思路讲解 案例分析与代码实现 案例分析 代码实现 优先队列优化(SPFA) 优化原理 代码实现 算法介绍 我们知道Dijkstra算法只能用来解决正权图的单源最短 ...

  4. Bellman Ford算法详解

    一.用途 1. Bellman Ford算法是解决拥有负权边最短路问题的方法之一.还有一种方法是SPFA算法. 2. 二者相比,SPFA算法在效率方面是优于Bellman Ford算法的.但在某些情况 ...

  5. LeetCode 787. K 站中转内最便宜的航班(图/Bellman Ford算法)

    文章目录 贝尔曼-福特算法(Bellman-Ford) 简介 算法思想 算法执行过程 应用 题目描述 分析 代码 LeetCode 787. K 站中转内最便宜的航班 题目描述 Bellman For ...

  6. C++实现bellman ford贝尔曼-福特算法(最短路径)(附完整源码)

    C++实现bellman ford贝尔曼-福特算法 实现bellman ford贝尔曼-福特算法的完整源码(定义,实现,main函数测试) 实现bellman ford贝尔曼-福特算法的完整源码(定义 ...

  7. bellman - ford算法c++

    (最短路III)bellman - ford算法(适用于含负权边的图) 注意:用该算法求最短路,在有边数限制的情况下可以存在负权回路!且对所走的边的数量有要求时只能用该算法实现! 解析:因为如果没有边 ...

  8. 图解Bellman Ford算法

    Bellman Ford 单源最短路径算法[中字] Bellman Ford 单源最短路径算法[中字]_哔哩哔哩_bilibili 贝尔曼-福特算法(Bellman–Ford algorithm )油 ...

  9. Bellman ford算法(贝尔曼·福特算法)

    Bellman - ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小.其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无 ...

  10. 图论(迪杰斯特拉,Floyd,bellman,spfa)

    对图论和搜索的学习感想 Dijkstra 迪杰斯特拉求最短路的暴力的思路是三重循环去更新所有点到起点的最短距离. 首先先初始化让第一个点到自己的距离是0即: dist[1]=0; 然后在省下的点中找到 ...

最新文章

  1. mgr未同步 mysql_MySQL Group Replication(多主同步复制MGR)
  2. spring boot: GlobalDefaultExceptionHandler方法内的友好错误提示,全局异常捕获
  3. petshop4.0 详解之五(PetShop之业务逻辑层设计)[转]
  4. 【dfs】益智游戏(2017 特长生 T2)
  5. Javascript处理时间
  6. 想深度探究数据库内核技术,墙裂推荐你看看这个
  7. 论文阅读:Semantic Human Matting
  8. 从技术角度比较CCD与CMOS的区别
  9. 经典公开课、好的学习网站
  10. 说明 RISC 和 CISC 指令系统的区别?
  11. Xception: DeepLearning with Depthwise Separable Convolutions2017Google【论文理解】
  12. 经典遗传算法及MATLAB实例
  13. 海湾汉字编码表全部_汉字编码对照表
  14. cad断点快捷键_CAD如何打断?CAD打断点和CAD打断命令操作方法
  15. java运维工程师简历模板_系统运维工程师个人个人简历模板.doc
  16. 1700x关闭超线程超频_关闭锐龙9 3900X超线程 游戏帧数居然更高
  17. CTU CU CB PU TU
  18. HTML 引用小图标
  19. 柳下惠_拔剑-浆糊的传说_新浪博客
  20. 系统架构演变和远程调用

热门文章

  1. JS实现常见文件类型的下载/保存
  2. 基础题库:10 带余除法
  3. Panda read_csv() 在anaconda中读入中文列时显示为乱码的问题
  4. 对于企业,8种常见的无形资产评估项目
  5. 矩阵乘法的算法实现 [转载]
  6. 基于QT的网络视频会议系统---KNVM
  7. 帧率、码流与分辨率相关知识
  8. iphone手机上卸载非主界面上的软件
  9. 使用scrapy爬虫,爬取17k小说网的案例-方法一
  10. 适用于Windows 10的所有Microsoft PowerToys的全部解释