Bellman-Ford算法核心代码只有四行
for( k=1;k<=n-1;k++){
for( i=1;i<=m;i++){
if(dis[v[i]>dis[u[i]]+w[i]]){
dis[v[i]]=dis[u[i]+w[i]];
}
}
}
外循环循环n-1次(n为顶点个数),内循环循环m次(m是边的个数),也即是枚举每一条边,dis数组和Dijkstra算法一样,用来记录源点到其余各顶点的最短路径。u,v,w三个数组用来记录边的信息。
例如第i条边存储在u[i]、v[i]、w[i]
中,表示u[i]
到顶点v[i]
这条边(u[i]→v[i])
权值为w[i]
.
if(dis[v[i]>dis[u[i]]+w[i]]){
dis[v[i]]=dis[u[i]+w[i]];
}
上面的代码是进行松弛操作,看能否通过u[i]→v[i]
这条边,使得源点到顶点v[i]
号顶点的距离变短。即源点到u[i]
号顶点的距离dis[u[i]]
加上u[i]→v[i]
这条边(权值为w[i]
)的值是否会比原先的源点到v[i]
号顶点的距离(dis[v[i]])
要小,这个算法就是将所有的边都松弛一遍
一句话概括这个算法:对所有的边进行n-1次“松弛”
/*
测试数据
5 5 1
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
#include<cstdio>
#include<iostream>
using namespace std;
const int INF=9999999;
int dis[100],v[100],u[100],w[100];
int n,m,cnt,i,k;
int main(){
cin>>n>>m>>cnt;//cnt到其他顶点的最短路径
for(i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
}
//初始化dis数组
for(i=1;i<=n;i++){
dis[i]=INF;
}
dis[cnt]=0;
//Bellman-Ford算法核心语句
for( k=1;k<=n-1;k++){
for( i=1;i<=m;i++){
dis[v[i]]=min(dis[v[i]],dis[u[i]]+w[i]);
}
}
//输出结果
for( i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
检测负权回路
此算法可以检测一个图是否存在负权回路,如果在进行n-1次松弛之后,仍然存在
if(dis[v[i]>dis[u[i]]+w[i]]){
dis[v[i]]=dis[u[i]+w[i]];
}
的情况,也就是说在进行n-1轮松弛之后,仍然可以继续松弛,那么此图必然存在负权回路
原因:一个图如果没有负权回路,那么最短路所包含的边最多有n-1条,即进行n-1轮松弛之后最短路不会再发生变化。如果在n-1轮松弛之后最短路仍然会发生变化,则改图必然存在负权回路
关键代码:
//Bellman-Ford核心语句
for(int k=1;k<=n-1;k++){
for(int i=1;i<=m;i++){
dis[v[i]]=min(dis[v[i]],dis[u[i]]+w[i]);
}
}
bool flag=false;
for(int i=1;i<=m;i++){
if(dis[v[i]]>dis[u[i]]+w[i])flag=true;
}
if(flag)printf("此图有负权回路\n");
进一步优化:
在实际操作中此算法经常在未达到n-1轮松弛就提前计算出最短路了,因此可以添加一个变量check用来标记数组dis在本轮中是否发生了变化,如果没有发生变化,则可以提前跳出循环:
#include<cstdio>
#include<iostream>
using namespace std;
const int INF = 2147483647;
int n,m,cnt,u[500100],v[500100],w[500100];
long long dis[10100];//dis数组需要开到long long
int main(){
//输入
cin>>n>>m>>cnt;
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
}
//初始化
for(int i=1;i<=n;i++){
dis[i]=INF;
}
dis[cnt]=0;
//Bellman-Ford核心语句
for(int k=1;k<=n-1;k++){
bool check=false;//判断本轮松弛最短路有没有变化
for(int i=1;i<=m;i++){
if(dis[v[i]]>dis[u[i]]+w[i]){//不能直接使用min函数
dis[v[i]]=dis[u[i]]+w[i];
check=true;//dis数组发生更新,改变check的值
}
}
if(check==false)break;//没有变化就没必要继续松弛了,提前结束
}
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
队列优化
/*
5 5 1
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int INF = 2147483647;
int n,m,start,dis[10005];
struct node{
int v,w;
node(int vv,int ww){
v=vv;
w=ww;
}
};
vector<node> ve[10005];
queue<int> que;
int main(){
//输入
cin>>n>>m>>start;
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
ve[x].push_back(node(y,z));//建立邻接表
}
//初始化dis
for(int i=1;i<=n;i++){
dis[i]=INF;
}
dis[start]=0;
//初始化队列
que.push(start);
//Bellman算法队列优化
while(!que.empty()){
int u = que.front();
for(int i=0;i<ve[u].size();i++){
int v = ve[u][i].v;
int w = ve[u][i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
que.push(v);
}
}
que.pop();
}
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}