Bellman-Ford算法是通过每一条边对除源点外的其他顶点最短路更新,求得最短路径;Bellman-Ford算法可以解决负边权问题;
存边:把图的每一条边存在u[i] , v[i] , w[i] 中,第i条边表示从顶点u[i]到顶点v[i],边权为w[i]的一条边;
核心算法:
for(int k=1;k<n;k++){//n-1轮更新最短路径,因为任何一定的最短路径不会超过n-1条边 for(int i=i;i<=m;i++){//每一轮枚举所有边,更新边的终点的最短路径 if(dis[v[i]]>dis[u[i]]+w[i])//如果通过这条边,路径变短 dis[v[i]]=dis[u[i]]+w[i];//更新最短路径 } }
所以Bellman-Ford算法的时间复杂度为O(nm)
完整代码
#include <string.h> #include<iostream> #define _Max 200010 #define INF 100000000 using namespace std; int v[_Max],u[_Max],w[_Max]; int dp[20010]; int main() { int n,m; cin>>n>>m; for(int i=0;i<m;i++){ cin>>u[i]>>v[i]>>w[i]; } for(int i=2;i<=n;i++){//其他点最短路径初始为无穷大 dp[i]=INF; } dp[1]=0;//源点最短路径初始为零 for(int j=0;j<n;j++){ for(int i=0;i<m;i++){ if(dp[v[i]]>dp[u[i]]+w[i]) dp[v[i]]=dp[u[i]]+w[i]; } } for(int i=2;i<=n;i++){ cout<<dp[i]<<endl; } return 0; }
优化:
1. Bellman-Ford算法最多更新n-1次就可以得到所有点的最短路径,但有时候可能不需要n-1次就已经获得所有点最短路径;其实如果枚举一次所有边,没有点更新最短路径,则说明所有点都已取得最短路径,则可以结束循环;
for(int k=1;k<n;k++){//n-1轮更新最短路径,因为任何一定的最短路径不会超过n-1条边 check=1; for(int i=i;i<=m;i++){//每一轮枚举所有边,更新边的终点的最短路径 if(dis[v[i]]>dis[u[i]]+w[i]){//如果通过这条边,路径变短 dis[v[i]]=dis[u[i]]+w[i];//更新最短路径 check=0; } } if(check==1) break; //如果check等于一,则说明没有更新过最短路径,则可以结束循环 }
2. 其实Bellman-Ford算法每次更新路径,都是从以上一次更新了最短路径的顶点为起点的边进行更新;所以可以利用一个队列存放更新了的最短路径的顶点;
具体思路:将源点路径设为零,源点进队,只要队列不为空,从队列取出一个顶点,并通过循环以这个顶点为起点的边来判断这些边的终点的最短路径是否可以更新,如果可以更新则将终点的最短路径更新并且将终点进队(需要注意的是,已经在队列里的元素再进队没有意义,所以需要判断是否元素已经在队列里)
#include <string.h> #include<iostream> #include<vector> #include<queue> #define _Max 200020 #define INF 1<<30 using namespace std; int v[_Max],u[_Max],w[_Max],next[_Max];//v[i]第i条边的起点,u[i]第i条边的终点,w[i]第i条边的权值,next[i]与第i条边同起点的下一条边 int dp[20010]; int flag[_Max];//用来判断元素是否在队列 int head[20010];//存放每个顶点为起点的第一条边 queue<int> que;//存放更新了最短路径的顶点 int main() { int n,m; cin>>n>>m; memset(head,-1,sizeof(head)); for(int i=0;i<m;i++){//存储数据,前向星结构存储 cin>>u[i]>>v[i]>>w[i]; next[i]=head[u[i]]; head[u[i]]=i; } for(int i=0;i<=n;i++){ dp[i]=INF; } dp[1]=0; que.push(1);//源点进队 memset(flag,0,sizeof(flag)); flag[1]=1;//标记源点在队列中 while(!que.empty()){ int k=que.front(); que.pop(); flag[k]=0; for(int i=head[k];i!=-1;i=next[i]){ int bian=i; if(dp[v[bian]]>dp[u[bian]]+w[bian]){ dp[v[bian]]=dp[u[bian]]+w[bian]; if(flag[bian]==0){//如果顶点不在队列,这进队 que.push(v[bian]); flag[bian]=1; } } } } for(int i=2;i<=n;i++){ cout<<dp[i]<<endl; } return 0; }
扩展:
检查负权回路:因为n-1次更新就可以获得所有顶点的最短路径,所以第n次更新如果还有顶点更新最短路径,则说明存在负权回路;