Bellman-Ford算法分析:
给定一张有向图,若对于图中的某一条边 (x, y,z)
(x
、y
、z
分别表示一条边的起点、终点、权重),有 dist[y] ≤dist[x]+z
成立,则称该边满足三角形不等式。
若所有边都满足三角形不等式,则dist
数组就是所求最短路。
介绍一下基于迭代思想的Bellman-Ford
算法。
它的流程如下:
1.扫描所有边(x, y,z)
,若dist[y] > dist[x]+z
,则用dist[x]+z
更新dist[y]
。
2.重复上述步骤,直到没有更新操作发生。
Bellman-Ford算法的时间复杂度为O(nm)。
代码片段:
int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离
struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{
int a, b, w;
}edges[M];
// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i = 0; i < n; i ++ )//最多n-1条边,那为啥不写成"i<n-1"?是因为判断负环至少要循环n层,如果不需要判断负环则写成这样也没问题
{
for (int j = 0; j < m; j ++ )//每一轮迭代松弛m条边
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
//结合三角不等式可以清楚理解这里
dist[b] = min(dist[b], dist[a] + w);
}
}
if(dist[n]>inf/2)
cout<<-1<<endl;
else
cout<<dist[n]<<endl;
}
上面是最朴素的Bellman-Ford算法,AcWing 849.Dijkstra求最短路 I可以用上面的模板直接套用,且无需改变数据范围。
通过的代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f3f
const int N = 510,M = 1e5+10;
int dist[N];
int n,m;
int a,b,w;
struct edge
{
int a,b,w;
}edges[M];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], dist[a] + w);
}
}
if(dist[n]>inf/2)
cout<<-1<<endl;
else
cout<<dist[n]<<endl;
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>a>>b>>w;
edges[i]={a,b,w};
}
bellman_ford();
return 0;
}
上面给的例子是关于有向图的,对于无向图,可以这样做:AcWing 1129. 热浪
下面给出的例题的问法比较特殊(不超过k
条边的最短路),因此还需要对模板进行改动。
例题:AcWing 853. 有边数限制的最短路
与上面分析的算法基本一致,只不过题目增加了额外的限制:经过边数不超过k
条。
代码上较原模板有所改动,具体见代码。
代码:(时间复杂度:o(nm))
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f3f
const int N = 500+10,M = 10000+10;
int dist[N],backup[N];
int n,m,k;
int a,b,w;
struct edge
{
int a,b,w;
}edges[M];
//考虑源点到终点的距离正好是-1的情况
void bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++)//最多经过k条边
{
memcpy(backup,dist,sizeof dist);//每次进行新的迭代前将dist备份,防止因节点串联造成的影响
for(int j=0;j<m;j++)//每一轮迭代松弛m条边
{
int A=edges[j].a,B=edges[j].b,W=edges[j].w;
dist[B]=min(dist[B],backup[A]+W);//backup[A]用的是上一轮迭代更新的A的距离,类似于滚动数组,可以防止因串联而对B更新造成的影响
}
}
if(dist[n]>inf/2)
cout<<"impossible"<<endl;
else
cout<<dist[n]<<endl;
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++)
{
cin>>a>>b>>w;
edges[i]={a,b,w};
}
bellman_ford();
return 0;
}