最短路径——Bellman-Ford

Bellman-Ford算法可以解决权值有负值的图的单源最短路径,但不能存在从源点可达的权为负的回路,能够检测负圈。可以是有向图,也可以是无向图,但无向图中不能有负边(因为无向图有负边的话,这两个点之间就有负环路了)。

Bellman-Ford算法的时间复杂度是O(V*E)。(V是定点数,E是边数)


Bellman-Ford算法的流程如下:
给定图G(V, E)(其中VE分别为图G的顶点集与边集),源点s数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n], Distant[s]0

以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).

BellmanFord算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1n1n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edgeuv)),判断是否存在这样情况:
dv) > d (u) + w(u,v),若返回false,表示途中存在从源点可达的权为负的回路。

之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。


代码:

#include <stdio.h>
#define INF 0xfffffff
#define N 10050

struct Link{
	int from;
	int to;
	int len;
};

Link Road[N];
int CityNum, RoadNum;
int dis[220], pre[220]; 
int Pa[N];
bool Bellman_Ford(int start){
	for(int i = 0; i < CityNum; i ++){
		dis[i] = INF;
	}
	dis[start] = 0;
	for(int i = 0; i < CityNum - 1; i ++){			//循环n-1次 ,因为最短路径最多n-1条边 
		for(int j = 0; j < RoadNum; j ++){		
			if(dis[Road[j].to] > dis[Road[j].from] + Road[j].len){  
	            dis[Road[j].to] = dis[Road[j].from] + Road[j].len;  
	            pre[Road[j].to] = Road[j].from;
	        } 
	    }
	}
	bool flag = true;
	for(int i = 0; i < RoadNum; i++){
		if(dis[Road[i].to] > dis[Road[i].from] + Road[i].len){
			flag = false;
			break;
		}
	} 
	return flag;
}
void Path(int start, int end){
	int i = 0, tmp = end;  
    while(tmp != start){  
        Pa[i++] = tmp;  
        tmp = pre[tmp];
    }  
    Pa[i++] = start;
     for(int j = i - 1; j >= 0; j--)  
        printf("%d ", Pa[j]);  
    puts("");  
} 
int main()
{
	int start, end;
	while(scanf("%d%d", &CityNum, &RoadNum) != EOF){
		for(int i = 0; i < RoadNum; i ++){
			scanf("%d%d%d", &Road[i].from, &Road[i].to, &Road[i].len);			
		}
		scanf("%d%d", &start, &end);
		if(Bellman_Ford(start)){
			if(dis[end] == INF){
				printf("-1\n");
			}
			else{
				printf("%d\n", dis[end]);
				Path(start, end);
			}
		}
		else
			printf("have negative circle\n");
	}
	
	return 0;
}

考虑:为什么要循环n-1次?

答:因为最短路径肯定是个简单路径,不可能包含回路的,

如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径

如果回路的权值是负的,那么肯定没有解了

图有n个点,又不能有回路

所以最短路径最多n-1边

又因为每次循环,至少relax一边

所以最多n-1次就行了


参考:http://www.wutianqi.com/?p=1912

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