最短路径之Bellman-Ford算法----解决负权边

最短路径之Bellman-Ford算法—-解决负权边 

//核心代码,只有4行,可以解决带有负权边的图。 

 for(k=1;k<=n-1;k++) 	//外循环循环了n-1次(n为顶点个数) ,为什么只需要进行n-1轮就可以???因为在一个含有n个顶点的图中,任意两点之间的最短路径 //最多包含n-1条边 
 	for(i=1;i<=m;i++)	//内循环循环了m次(m为边的个数),即枚举每一条边 
 		if(dis[v[i]] > dis[u[i]+w[i]])  
 			dis[v[i]] = dis[u[i]+w[i]];

dis数组作用与Dijkstra算法一样,用来记录源点到其余各个顶点的最短路径 最短路径之Dijkstra算法 .

u、v、w三个数组是用来记录边的信息。

for(i=1;i<=m;i++)	//内循环循环了m次(m为边的个数),即枚举每一条边 
 	if(dis[v[i]] > dis[u[i]+w[i]])  
 		dis[v[i]] = dis[u[i]+w[i]];

上述代码的意思:看看能否通过u[i]到v[i]这条边(权值为w[i]),使得1号顶点到v[i]号顶点的距离变短。即1号顶点到u[i]号顶点的距离dis[u[i]]加上 u[i]到v[i]这条边(权值为w[i])的值是否会比原先1号顶点到v[i]号顶点的距离(dis[v[i]])要小。 

 

第一轮在对所有的边进行松弛之后,得到的是从1号顶点“只能经过一条边”到达其余各顶点的最短路径长度。第二轮在对所有的边进行松弛之后,得到的是从1号顶点“最多经过两条边”到达其余各顶点的最短路径长度。如果进行K轮的话,得到的是1号顶点“最多进行k条边”到达其余各顶点的最短路径长度。需要进行多少轮?

需要进行n-1轮就可以。因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。

最短路径中是否包含回路?

不可能。最短路径肯定是一个不包含回路的简单路径。回路分为正权回路(即回路权值之和为正)和负权回路(即回路权值之和为负)。如果最短路径中包含正权回路,那么去掉这个回路,一定可以得到更短的路径。如果最短路径中包含负权回路,那么肯定没有最短路径,因为每多走一次负权回路就可以得到更短的路径。因此,最短路径肯定是一个不包含回路的简单路径,即最多包含n-1条边,所以进行n-1轮松弛就可以。

Bellman-Ford算法完整代码

#include<stdio.h>
int main() {
	int d[10],i,k,n,m,u[10],v[10].w[10];
	int inf=99999999;
	scanf("%d %d",n,m);
	
	for(i=1;i<=m;i++)
		scanf("%d %d %d",&u[i],&v[i],&w[i]);
	//初始化dis数组,这里是1号顶点到其余各个顶点的初始路程 
	for(i=1;i<=n;i++)
		dis[i]=inf;
	dis[1]=0;
	
	//核心代码 
	 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]];
 				
 	for(i=1;i<=n;i++)
	 printf("%d ",dis[i]);
	 
	getchar();getchar();
	return 0; 
} 

测试数据:

5 5

2 3 2

1 2 -3

1 5 5

4 5 2

3 4 3

运行结果:0 -3 -1 2 4

总结:因为最短路径上最多有n-1条边,因此Bellman-Ford算法最多有n-1个阶段。在每一个阶段,我们对每一条边都要执行松弛操作。其实每实施一次松弛操作,就会有一些顶点已经求得其最短路,即这些顶点的最短路的“估计值”变为“确定值”。此后这些顶点的最短路的值就会一直保持不变,不再受后续松弛操作的影响(但是每次还是会判断是否需要松弛,这里浪费了时间,还可以优化)。在前k个阶段结束后,就已经找出了从源点发出“最多经过k条边”到达各个顶点的最短路。直到进行完n-1个阶段后,便得出了最多经过n-1条边最短路 。


 
############################################################################ 
Bellman-Ford算法可以检测一个图是否含有负权回路。如果在进行n-1次松弛后,仍然存在

 if(dis[v[i]] > dis[u[i]+w[i]])  
 				dis[v[i]] = dis[u[i]+w[i]];

说明进行n-1次松弛后,仍然可以继续成功松弛,此图必然存在负权回路。关键代码如下:

 

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]];
//检测负权回路 
 flag=0;
 for(i=1;i<=m;i++)
 	if(dis[v[i]] > dis[u[i]+w[i]]) flag=1;
 if(flag==1)	printf("此图必然含有负权回路");

 
Bellman-Ford算法时间复杂度是O(NM),比dijkstra复杂度还要高,还可以进行优化。
实际操作中,该算法经常会在未达到n-1轮松弛前就计算出最短路,n-1其实是最大值。因此可以添加一个变量check来标记dis数组在本轮松弛中是否发生变化,如果没有发生变化,则可以提前跳出循环。代码如下: 

#include<stdio.h>
int main() {
	int d[10],i,k,n,m,u[10],v[10],w[10],check,flag;
	int inf=99999999;
	scanf("%d %d",n,m);
	
	for(i=1;i<=m;i++)
		scanf("%d %d %d",&u[i],&v[i],&w[i]);
	//初始化dis数组,这里是1号顶点到其余各个顶点的初始路程 
	for(i=1;i<=n;i++)
		dis[i]=inf;
	dis[1]=0;
	
	//核心代码 
	 for(k=1;k<=n-1;k++)  {
	 	check=0;//用来标记在本轮松弛中数组dis是否会发生更新
	 	for(i=1;i<=m;i++)	
 			if(dis[v[i]] > dis[u[i]+w[i]]) {
 				dis[v[i]] = dis[u[i]+w[i]];
 				check=1;	//数组dis发生更新,改变check的值 
			 }  
 		//松弛完毕后检测数组dis是否更新 
		 if(check==0)	break;//如果数组dis没有更新,提前退出循环结束算法		
	 }	
 		
 	//检测负权回路 
	flag=0;
 	for(i=1;i<=m;i++)
 		if(dis[v[i]] > dis[u[i]+w[i]]) flag=1;
	if(flag==1)	printf("此图必然含有负权回路");
 	else { 	
 		for(i=1;i<=n;i++)
	 	printf("%d ",dis[i]);
	} 
	getchar();getchar();
	return 0; 
} 

Bellman-Ford算法的优化:在每一次松弛操作后,就会有一些顶点已经求得其最短路,此后这些顶点的最短路的估计值就会一直保持不变,不再受后续松弛操作的影响,但是每次还要判断是否需要松弛,这里浪费了时间。这就启发:每次仅对最短路径估计值发生变化了的顶点的所有出边执行松弛操作。

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/wangdd_199326/article/details/70473760
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞