Bellman-ford算法及模板题

Bellman-ford
贝尔曼-福特算法(英语:Bellman–Ford algorithm),求解单源最短路径问题的一种算法,由理查德·贝尔曼和小莱斯特·伦道夫·福特创立。有时候这种算法也被称为贝尔曼-福特-摩尔算法(Bellman–Ford–Moore algorithm),因为爱德华·F·摩尔也为这个算法的发展做出了贡献。它的原理是对图进行v-1次松弛操作,得到所有可能的最短路径。其优于戴克斯特拉算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE),但算法可以进行若干种优化,提高了效率。
floyd算法
dijkstra算法

⭐️注意

循环

每次循环操作实际上是对相邻节点的访问,第n次循环操作保证了所有深度为n的路径最短。由于图的最短路径最长不会经过超过v-1条边,所以可知贝尔曼-福特算法所得为最短路径。

负边权操作

与戴克斯特拉算法不同的是,戴克斯特拉算法的基本操作“拓展”是在深度上寻路,而“松弛”操作则是在广度上寻路,这就确定了贝尔曼-福特算法可以对负边进行操作而不会影响结果。

负权环判定

因为负权环可以无限制的降低总花费,所以如果发现第n
次操作仍可降低花销,就一定存在负权环。

查找负回路

当使用这个算法查找最短路径时,有负回路会使算法找不到正确的答案。但是,由于在找到负回路后会中止算法,所以可以被用来查找目标,例如在网络流分析中的消圈算法(Cycle Cancellation Algorithms)

循环的提前跳出

在实际操作中,贝尔曼-福特算法经常会在未达到v-1次前就出解,v-1其实是最大值。于是可以在循环中设置判定,在某次循环不再进行松弛时,直接退出循环,进行负权环判定。

原理具体讲解

一个有n个点的图,给每个点n次机会询问邻居,是否有到起点s的更短的路径,如果有就更新;经过n轮更新,就得到了所有点到起点s的最短路。
  第1轮:起点s的邻居点中,肯定有一个点u是最近的;第1轮确定了s到u的最短路。
  第2轮:所有点再次询问邻居,是否有到s的更短的路;显然,要么是s的某个邻居,要么是u的某个邻居,能确定最短路。
  重复以上步骤,每一轮能确定一个点的最短路。n个点共n轮计算,每一轮需要检查所有的m条边,总复杂度O(m×n)。Bellman-ford算法能用于边权为负数的图,这是它对Dijkstra算法的优势,基于“扩散+贪心”的Dijkstra算法,边的权值不能为负。
  Bellman-Ford算法的特点是只对相邻结点进行计算,可以避免floyd那种大撒网式的无效计算,大大提高了效率。为理解这个算法,可以想象图上的每个点都站着一个人,初始时,所有人到s的距离设为INF,即无限大。用下面的步骤求最短路径:
  (1)第一轮,给所有的n个人每人一次机会,问他的邻居,到s的最短距离是多少?如果他的邻居到s的距离不是INF,他就能借道这个邻居到s去,并且把自己原来的INF更新为较短的距离。显然,开始的时候,起点s的直连邻居(例如u)肯定能更新距离,而u的邻居(例如v),如果在u更新之后问u,那么v有机会更新,否则就只能保持INF不变。特别地,在第一轮更新中,存在一个与s最近的邻居t;t到s的直连距离,就是全图中t到s的最短距离。因为它通过别的邻居绕路到s,肯定更远。t的最短距离已经得到,后面不会再更新。
  (2)第二轮,重复第一轮的操作,再给每个人一次问邻居的机会。这一轮操作之后,至少存在一个s或t的邻居v,可以算出它到s的最短距离。v要么与s直连,要么是通过t到达s的。v的最短距离也得到了,后面不会再更新。
  (3)第三轮,再给每个人一次机会…
  继续以上操作,直到所有人都不能再更新最短距离为止。
  一共需要几轮操作?每一轮操作,都至少有一个新的结点得到了到s的最短路径。所以,最多只需要n轮操作,就能完成n个结点。在每一轮操作中,需要检查所有m个边,更新最短距离。根据以上分析,Bellman-Ford算法的复杂度是O(nm)。
  Bellman-Ford有现实的模型,即问路。每个十字路口站着一个警察;在某个路口,路人问一个警察,怎么走到s最近?如果这个警察不知道,他会问相邻几个路口的警察:“从你这个路口走,能到s吗?有多远?”这些警察可能也不知道,他们会继续问新的邻居。这样传递下去,最后肯定有个警察是s路口的警察,他会把s的信息返回给他的邻居,邻居再返回给邻居。最后所有的警察都知道怎么走到s。而且是最短的路。
  问路模型里有趣的一点,并且能体现Bellman-Ford思想的是:警察并不需要知道到s的完整的路径,他只需要知道从自己的路口出发,往哪个方向走能到达s,并且路最近。

模板题

问题描述

A国有N个城市,编号为1 . . . N。小明是编号为1的城市中一家公司的员工,今天突然接到了上级通知需要去编号为N的城市出差。由于疫情原因,很多直达的交通方式暂时关闭,小明无法乘坐飞机直接从城市1到达城市N,需要通过其他城市进行陆路交通中转。小明通过交通信息网,查询到了M条城市之间仍然还开通的路线信息以及每一条路线需要花费的时间。同样由于疫情原因,小明到达一个城市后需要隔离观察一段时间才能离开该城市前往其他城市。通过网络,小明也查询到了各个城市的隔离信息。(由于小明之前在城市1,因此可以直接离开城市1,不需要隔离)由于上级要求,小明希望能够尽快赶到城市N,因此他求助于你,希望你能帮他规划一条路线,能够在最短时间内到达城市N。


格式输入

第1行:两个正整数N, M, N表示A国的城市数量,M表示未关闭的路线数量。
第2行:N个正整数,第i个整数Ci表示到达编号为i的城市后需要隔离的时间。
第3 . . . M + 2行:每行3个正整数,u, v, c,表示有一条城市u到城市v的双向路线仍然开通着,通过该路线的时间为c。


格式输出

第1行:1个正整数,表示小明从城市1出发到达城市N的最短时间(到达城市N,不需要计算城市N的隔离时间)。


样例输入

4 4
5 7 3 4
1 2 4
1 3 5
2 4 3
3 4 5


样例输出

13


评测用例规模与约定

对于100%的数据,1≤N≤1000 , 1≤M≤10000, 1≤Ci≤200, 1≤u, v≤N, 1≤c≤1000


解析


参考程序

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int M = 20010;           
int t[M];
int dist[M];                   
struct edge{int a,b,c;}e[M];    
int main(){
    int n,m; cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>t[i];
    for(int i=1;i<=m;i++){
        int a,b,c;  cin >>a>>b>>c;
        e[i].a=a;   e[i].b=b;   e[i].c=c;    
        e[m+i].a=b; e[m+i].b=a; e[m+i].c=c;  
    }
    memset(dist,INF,sizeof(dist)); 
    dist[1]=0;                     
    for(int k=1;k<=n;k++){         
        for(int i=1;i<=2*m;i++){   
            int u=e[i].a,v=e[i].b;  
            int res = t[v];        
            if(v==n) res = 0;      
            dist[v]=min(dist[v],dist[u]+e[i].c+res);  /
        }
    }
    cout<<dist[n];
    return 0;
}

以个人刷题整理为目的,如若侵权,请联系删除~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值