最短路径算法—Bellman-Ford

Bellman-Ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

#include<stdio.h>

void main() {
	//dis数组存储1号顶点到各个顶点的距离,n表示顶点个数,m表示边数
	//u,v,w分别储存输入的每行数据,表示从u到v的距离为w
	//flag判断负权回路问题
	int dis[10], i, k, n, m, u[10], v[10], w[10], 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数字,默认到自己为0,到其余定点为无限大
	for (i = 1; i <= n; i++) {
		dis[i] = inf;
	}
	dis[1] = 0;

	//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];
			}
		}
	}
	
	//判断是否存在负权回路
	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]);
		}
	}
	system("pause");
}

可以看出时间复杂度为O(NM),Bellman-Ford算法能成立的原理也很简单,当dis[u[i]]的值改变时,dis[u[i]]+w[i]的值即dis[v[i]]也会跟着改变,这就是松弛,以此类推,一个顶点的dis值改变,后续和其相关的顶点dis值才有可能会跟着改变,即可算出每个顶点距离源点的最短距离。这样也就很容易看出,其实不需要每次for循环都对所有的边进行松弛。所以我们可以使用队列,将哪些改变的顶点放入队列,然后下次只针对这个顶点对应的边进行松弛即可。至于为什么只需要n-1此循环即可,以为n个顶点,最短距离最多需要n-1个边,即n-1此松弛,即从源点开始n-1轮松弛。

通过队列方式优化Bellman-Ford算法:

#include<stdio.h>

void main() {
	//n为顶点个数,m为边数,u,v,w表示顶点u到顶点v的距离为w
	int n, m, i, j, k;
	int u[8], v[8], w[8];
	//first要比n最大值大1,next要比m的最大值大1,因为for循环式从1开始的
	int first[6], next[8];
	//dis记录源点到各个顶点的距离,book记录那些顶点在队列中
	int dis[6] = { 0 }, book[6] = { 0 };
	//定义一个队列
	int que[101] = { 0 }, head = 1, tail = 1;
	//表示正无穷
	int inf = 99999999;

	scanf("%d %d", &n, &m);

	//初始化dis数组
	for (i = 1; i <= n; i++) {
		dis[i] = inf;
	}
	dis[1] = 0;

	//初始化book,firtst
	for (i = 1; i <= n; i++) {
		book[i] = 0;
		first[i] = -1;
	}

	//读入每一条边
	for (i = 1; i <= m; i++) {
		scanf("%d %d %d", &u[i], &v[i], &w[i]);
		//表示第i条边其对应的边
		next[i] = first[u[i]]; 
		//表示第u[i]个顶点对应的第一个边为第i条,即通过一个顶点值从first数组和next数组得到所有对应边,此为邻接表
		first[u[i]] = i;
	}

	//1号顶点入队
	que[tail] = 1; tail++;
	//标志1号顶点入队
	book[1] = 1;


	while (head < tail) {
		//当前需要处理的队首顶点
		k = first[que[head]];
		//扫面当前定点对应所有的边
		while (k != -1) {
			//判断松弛
			if (dis[v[k]] > dis[u[k]] + w[k]) {
				dis[v[k]] = dis[u[k]] + w[k];
				//判断是否在队列中
				if (book[v[k]] == 0) {
					//入队列
					que[tail] = v[k];
					tail++;
					//标记顶点v[k]已经入队
					book[v[k]] = 1;
				}
			}
			k = next[k];
		}
		//取消标记
		book[que[head]] = 0;
		//出队列
		head++;
	}
	//输出
	for (i = 1; i <= n; i++) {
		printf("%d ", dis[i]);

	}
	system("pause");
}

可以看出只有在每次循环所有边都要松弛的情况下时间复杂度才为O(NM)

猜你喜欢

转载自blog.csdn.net/weixin_38189842/article/details/81475231