最短路径经典算法其二Bellman-Ford

  • 前言
  • Bellman-Ford 原理讲解
  • 代码实现
    • 样例测试
  • BFS实现
    • 样例测试
  • 总结
  • Reference

前言

  hello,各位快要回家happy的盆友们,现在是布谷布谷鸟的算法小课堂。

  上周我们讲了 Dijkstra最短路径搜索算法,还用BFS的思想实现了它的第二个版本。还记得它的核心思想吗?(疯狂翻博客ing

  就是不断找到当前可到达的新的最近点,它相当于中介的角色,我们用其与周围的邻接点的权重来更新起点到这些邻接点的距离。

  今天我们来讲解另一种最短路径算法——Bellman-Ford算法。

CSDN (゜-゜)つロ 干杯

Bellman-Ford 原理讲解

  要理解它的思路,首先需要有个提前,那就是一个不含有环且,n个节点的有向图,从起点到达终点(起点不算)最短路最多只经过n-1个节点,这个很好理解,就是最短路上每个节点都利用到了。

  为什么说这里要求不含环呢,环即指图里有节点形成的回环,有零环,正环和负环。其中,零环和正环去掉后并不影响最短路的长度,因为本身他们就是多余的,实际理解就是不去兜这个圈子。

  而负环一旦存在,最短路就不会存在了。 想想是为啥?因为既然有负权重的路,而且还是环路,那么每经过一次环路,最短路的长度都会减少,即不存在最短路,而负权重在实际上也不会经常遇见。

  言归正传,既然最短路最多有n-1个节点,那么是不是在一个循环里如果我能找到一个不同的最短点并更新距离,最多只需要n-1次循环就可以找到最短路。没错,Bellman_Ford就是利用这个简单的想法。

  今天我们沿用上一次的例子并稍作修改:


  根据上面的思路,我们每一轮循环确定一个最近点,假设我们的路径输入是:

u v weight
0 1 9
2 3 2
1 2 5
1 3 20

  一开始起点本身到自己的距离为0,到所有其他节点的初始距离设置为INF

  第一轮循环,我们的过程如下:

0 → 1 : D i s [ 0 ] = 0 , D i s [ 1 ] = inf ⁡ > D i s [ 0 ] + w [ 0 ] [ 1 ] = 9 ; 2 → 3 : D i s [ 2 ] = inf ⁡ 未 到 达 节 点 2 , 无 法 更 新 ; 1 → 2 : D i s [ 1 ] ≠ inf ⁡ , D i s [ 2 ] = inf ⁡ > D i s [ 1 ] + w [ 1 ] [ 2 ] ; 1 → 3 : D i s [ 1 ] ≠ inf ⁡ , D i s [ 3 ] = inf ⁡ > D i s [ 1 ] + w [ 1 ] [ 3 ] \begin{aligned} 0 &\rightarrow \ 1:Dis[0]=0 \ , \ Dis[1]=\inf \gt Dis[0]+w[0][1]=9; \\ 2 &\rightarrow \ 3:Dis[2]=\inf \ 未到达节点2,无法更新; \\ 1 &\rightarrow \ 2:Dis[1]\neq \inf \ ,\ Dis[2]=\inf \gt Dis[1]+w[1][2];\\ 1 &\rightarrow \ 3:Dis[1]\neq \inf \ ,\ Dis[3]=\inf \gt Dis[1]+w[1][3] \end{aligned} 0211​→ 1:Dis[0]=0 , Dis[1]=inf>Dis[0]+w[0][1]=9;→ 3:Dis[2]=inf 未到达节点2,无法更新;→ 2:Dis[1]​=inf , Dis[2]=inf>Dis[1]+w[1][2];→ 3:Dis[1]​=inf , Dis[3]=inf>Dis[1]+w[1][3]​
  第二步里由于节点2还未到达,所以无法更新,但是我们通过节点1已经更新了到达节点2和节点3的距离。这里,可以发现一次循环里最差的情况是只更新到一个最短节点,但大多数情况数量是更多的,所以找到最短路径的速度其实是很快的。

  接下来就是看看在第二次循环里能不能通过节点2到节点3的路径使起点到达节点3的距离更短:

2 → 3 : D i s [ 0 ] ≠ inf ⁡ , D i s [ 3 ] = 20 > D i s [ 2 ] + w [ 2 ] [ 3 ] = 16 2 \rightarrow \ 3:Dis[0]\neq \inf \ ,\ Dis[3]=20 \gt Dis[2]+w[2][3]=16 2→ 3:Dis[0]​=inf , Dis[3]=20>Dis[2]+w[2][3]=16

代码实现

  按照上面的思路,我们可以实现我们的想法:

def bellman_ford(directed=True):"""bellman_ford ImplementationArgs:directed (bool, optional): [whether directed graph or not]. Defaults to True."""# 适用于有向图import timelimit = 10000# num of node, num of links, start_node, end_noden, k, s, e= list(map(int, input().split()))# path weightW = [list(map(int, input().split())) for i in range(k)]start=time.time()Dis = [limit for i in range(n+1)]  # Dis[i]表示起点到节点i的距离,初始设为一个较大的值Dis[s] = 0  # 起点到自身为0# fresh flagf = Truewhile f:# 当没有更新时,退出循环f = False# 全盘扫描降距for d in W:  u, v, dis = d# 当已存在有起点到u的路径,尝试是否通过u->v可使起点到v的路径变短,本质是贪心加循环if Dis[u] != limit:Dis[v], f = (Dis[u]+dis, True) if Dis[u]+dis < Dis[v] else (Dis[v], f)# show answerprint('Shortest distance from s to e: {}'.format(Dis[e]))print('Time used: {:.5f}s'.format(time.time()-start))return

样例测试

  测试一下上面的例子:

test_example:
7 9 0 6
0 1 9
1 2 5
2 3 2
1 3 20
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7>>> bellman_ford()
Shortest distance from s to e: 34
Time used: 0.01303s

BFS实现

  同样的,我们也可以用BFS的思想来实现Bellman_Ford算法,但是这里我们不需要得到每次循环里新一个离起点最近的节点,所以不需要优先队列,普通的FIFO队列即可。

  然后节点也是可以重复进入的,因为是全盘搜索,不需要标记节点是否访问过,只要可以进入松弛条件,那就是有意义的更新。

  我们还可以加入是否存在负环的判断,来避免不存在最短路的情况(参考《算法竞赛入门经典》刘汝佳著 [Page-364]), 参考上一次的BFS_Dijkstra,我们可以简单修改:

def BFS_bellman_ford(directed=True):"""Functions: Implementation of bellman_ford using BFS and PriorityQueueArgs:directed (bool, optional): [whether directed graph or not]. Defaults to True."""import timefrom queue import Queue# init distancelimit = 10000# number of nodes, number of links, start_index, end_indexN, K, s, e = list(map(int, input().split()))start = time.time()# graph matad_mat = [[0 for i in range(N)] for j in range(N)]# distance to start_nodeDis = [limit for i in range(N)]# number of adjacent nodes of one nodeG = [[] for i in range(N)]# use links to fresh graph matfor i in range(K):u, v, w = list(map(int, input().split()))ad_mat[u][v] = wG[u].append(v)if directed == False:ad_mat[v][u] = wG[v].append(u)# init distance of start_nodeDis[s] = 0# counter the num of entering some nodescnt = [0 for i in range(N)]# Queue object definitionclass queue_obj:def __init__(self, s):self.s = s  # node index# BFS with QueueQ = Queue()Q.put(queue_obj(s))while Q.qsize() != 0:node = Q.get()s = node.s# fresh distancefor i in range(len(G[s])):ad_node = G[s][i]if Dis[ad_node] > (ad_mat[s][ad_node]+Dis[s]):Dis[ad_node] = ad_mat[s][ad_node]+Dis[s]Q.put(queue_obj(ad_node))cnt[ad_node] += 1# negative loop checkif cnt[ad_node] > N:print("The graph has negative circle path")loop= ''.join(str(k)+' ' for k in range(N) if cnt[k]==cnt[ad_node] or cnt[k]==cnt[ad_node]-1)print("Checked nodes are {}".format(loop))return print('Shortest distance from s to e: {}'.format(Dis[e]))print('Time used: {:.5f}s'.format(time.time()-start))return

样例测试

  先来测试刚才的例子:

test_example:
7 9 0 6
0 1 9
1 2 5
2 3 2
1 3 20
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7>>> BFS_bellman_ford()
Shortest distance from s to e: 34
Time used: 0.01500s

  我们来测试一下检测负环的功能:


  我们加入节点3到节点1的路径,并把节点1到节点2的路径权重改为负,这样就构成了由节点1,2,3所组成的负环。

  这里我们判断负环的逻辑是,当存在负环,算法会不断的进入负环中的点,而我们又有n个节点无环有向图最短路径最多n-1个节点,所以查询进入节点的次数,如果超过n,则可以说明存在负环。

test_example:
7 10 0 6
0 1 9
1 2 -5
2 3 2
1 3 20
3 1 2
3 4 14
4 5 3
3 5 8
5 6 10
6 1 7>>> BFS_bellman_ford()
The graph has negative circle path
Checked nodes are 1 2 3

  可以看到找到了可疑的负环节点1,2,3,是符合图示的例子的。

总结

  三大最短路径经典算法讲了两个,还差一个Flody,相信大家dddd。我们下期来继续填坑。

快来跟小刀一起头秃~

Reference

[1] 算法小专栏:最短路径Bellman-Ford

最短路径经典算法其二Bellman-Ford相关推荐

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

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

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

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

  3. 求解两点间最短路径的算法

    最短路径算法 1.Dijkstra算法 2.Bellman-Ford算法 3.SPFA算法 4.Floyd算法 几种最短路径算法的对比 Dijkstra算法.Bellman-Ford算法和SPFA算法 ...

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

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

  5. Bellman——Ford算法

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

  6. Bellman Ford算法详解

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

  7. 图解Bellman Ford算法

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

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

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

  9. bellman - ford算法c++

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

最新文章

  1. 3种mysql的储存机制_MySQL三种InnoDB、MyISAM和MEMORY存储引擎对比
  2. 金邦黑金刚4G内存 VS Vista系统
  3. navc mysql函数备份_入门MySQL——备份与恢复
  4. selenium+python中,框架中,怎么返回上一个菜单
  5. Linux与jvm内存关系分析
  6. bst java_图解:二叉搜索树算法(BST)
  7. MySQL主从复制原理(原理+实操)
  8. C++ Primer Plus学习(十)——类和对象
  9. ACL 2020投稿破 3 千,到底有多少人在做 NLP 研究?
  10. NYOJ201-作业题(最长升降子序列)
  11. MySQL 查询部门工资前三高的员工信息
  12. u盘写保护无法格式化的修复
  13. html实现太极图效果
  14. 文本分类上分利器: Bert微调trick大全
  15. C++,OpenCV 中template(模板)的简单理解
  16. CMD快捷指令之磁盘检查(管理员身份运行命令提示符)
  17. Linux 编辑doc,玩转Linux vi编辑器.doc
  18. Kruskal理解+代码解析
  19. 学生用计算机怎么没音效,电脑有声音用播放器没有声音怎么处理啊???
  20. 并查集算法(有趣的讲解)

热门文章

  1. python | 基础学习(一)了解Bug、pycharm、变量、程序的三大流程(顺序、if、while)、运算符、快捷键
  2. 英国工程专业最佳大学TOP10成绩要求多高?
  3. LA 4513 Stammering Aliens
  4. python 回溯法 子集树模板
  5. ORACLE考试例题
  6. 9.1 什么是包图?
  7. 阿里巴巴首席DBA成甲骨文全球第100个ACE
  8. 100%代码覆盖率神话
  9. linux centos7 安装gc,Linux(Centos7)安装Java JDK及卸载
  10. mysql 循环控制语句介绍