最短路径问题 3.Bellman-Ford算法
简要:Bellman-Ford算法计算的仍然是从一个点到其他所有点的最短路径算法,其时间复杂度是O(NE),N表示点数,E表示边数,不难看出,当一个图稍微稠密一点,边的数量会超过点数那么实际上效率是低于Dijkstra算法的。但是本算法可以计算存在负权边的情况(不存在负回路),因此可以用于更广泛的情况,但其实在日常解题应用中我们基本不会用到该算法,因为有一个比它效率更高的算法即SPFA算法,下一章会介绍到。SPFA算法其实是从Bellman-Ford算法演变而来的,那么从基础的开始,我们先来理解一下Bellman-Ford算法。
算法描述:s为起点,dis[v]为s到v的最短距离,pre[v]是v的前驱结点,w[j]是边 j 的长度,j 边的起点和终点分别是u,v。
1、初始化:dis[s]=0, dis[v]=∞(v≠s), pre[s]=0
2、for(i=1;i<=n-1;i++)
for(j=1;j<=e;j++)
if(dis[u]+w[j]<dis[v]){
dis[v]=dis[u]+w[j];
pre[v]=u;
}
算法理解:一开始已标记的点只有起点,每一次枚举所有的边,总会有一些边连接着已标记的点和未标记的点,即已经计算过最短距离和为计算过最短距离的点。因此每次枚举都会更新一些未标记的点成为已标记的点。而n-1次则保证了最坏情况下所有的点均可以被标记,即图的样子是一“串”的情况。
对于负权回路的情况,因为每一次枚举都会走过一圈负权回路,那么恰好在该回路两边的点之间的最短距离就会无限减小,因此会造成错误。对此,大家给出了一个不是解决方法的解决方法,就是如果在两重循环结束后,再次枚举每条边,如果再次出现某两点的距离减少,那么就返回"错误",已表示有负权回路,无法算出答案。
当然,对于负权回路也有解决方法,在介绍完SPFA算法后会补充一下。
代码如下:
#include<stdio.h> #include<stdlib.h> #include<string.h> int n,e,s; struct node{ int x; int y; int val; }m[105]; int dis[105],pre[105],a,b,w[105][105]; int bellmanford(int s){ int i,j; for(i=1;i<=n;i++) dis[i]=w[s][i]; dis[s]=0;pre[s]=0; for(i=1;i<=n-1;i++) for(j=1;j<=e;j++) if(dis[m[j].x]+m[j].val<dis[m[j].y]) { dis[m[j].y]=dis[m[j].x]+m[j].val; pre[m[j].y]=m[j].x; } for(j=1;j<=e;j++){ if(dis[m[j].x]+m[j].val<dis[m[j].y]) return 0; } return dis[5]; } int main(){ int i,j; scanf("%d%d",&n,&e); memset(dis,100,sizeof(dis)); memset(w,100,sizeof(w)); for(i=1;i<=e;i++){ scanf("%d%d%d",&m[i].x,&m[i].y,&m[i].val); a=m[i].x; b=m[i].y; w[a][b]=m[i].val; } for(i=1;i<=5;i++) for(j=1;j<=5;j++) printf("%d ",w[i][j]); scanf("%d",&s); printf("%d",bellmanford(s)); return 0; }