6.3 最短路径:Bellman-Ford算法
Bellman-Ford算法
很强的一个算法,无论是思路、思想、代码实现都很优秀
而且,它可以解决负权边的问题
简介
一句话概括这个算法就是:“对所有的边进行n-1次松弛操作”
一样的,我们用uvw三者表示“从顶点u[i]到顶点v[i]的这条边,权值为w[i]”
随后检查,新的距离会不会比原本的距离短
1.用dis数组初始化估计值,并且把除了起始点之外的,都设置为正无穷大(解释见后)
2.按边的图的数组中的顺序,遍历检查"这条路会不会距离变短"
3.由于起始点到自身的距离是0,所以一定存在它到相邻区域的更小值,于是更新
比如到2的预设值是正无穷,而有一条(u=1,v=2,w=-3)的路,那么这个正无穷就会被更新为-3
4.重复2-3步,不断得到新的结果
5.返回最终结果
我们实验发现,可以知道“在第一轮后,实际上得到的是'从一号顶点,只经过一条边,到其余点的最短路径'”
而实际2-3的重复,就是利用“一次次的松弛”,达到对条件 “只经过k条边”的递增
最终,只需要进行n-1轮,就可以了。再包含n个顶点的图中,两点之间的最短路径最多包含n-1个边
(而且不含回路:正回路的距离会越来越远,负回路不可能有最短路径,因为它会一直一直递减)
实现
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是点个数,m是边个数
Bellman-Ford算法还可以检测负权回路,如果在n-1次松弛后,最短路径还在变化,那就是有负权回路了
具体代码实现如下:
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算法的完整代码如下,实质上还有地方可以优化
优化的地方可以单独列为一小节,我们这里先给出最初的算法代码:
#include <stdio.h>
int main()
{
int dis[10],bal[10],i,k,n,m,u[10],v[10],w[10],check,flag ;
int inf = 99999999 ; //用inf存储我们认为的正无穷值
//读入n和m,n表示顶点个数,m表示边的条数
scanf("%d %d",&n,&m);
//读入边
for(i = 1 ; i< m ; i++){
scanf("%d %d %d",&u[i],&v[i],&w[i]);s
}
//初始化dis数组,这里是1号顶点到其余各个顶点的初始路程
for(i=1;i<=n;i++)
dis[i] = inf ;
dis[1] = 0 ;//我们这里把1作为起始点
//开始Bellman-Ford算法
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是否有更新
}
}
//检测负权回路
flag = 0 ;
for(i=1;i<=m;i++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1 ;
if(flag == 1)
print("此图存在负权回路");
else{
//输出最终结果(无负权回路)
for(i=1;i<=n;i++)
printf("%d" , dis[i]);
}
getchar();getchar();
return 0 ;
}