Bellman-Ford(贝尔曼-福特)(单源最短路径,比迪杰斯特拉增加了能解决负边权)

Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法可能就会失效,求出的最短路径就可能是错的。这时候,就需要使用Bellman-Ford算法来求解最短路径,Bellman-Ford算法的流程如下:
给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,

1.数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;

2.以下操作循环执行至多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进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

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

可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).
介绍一下松弛操作
在这里插入图片描述Distant[B]>Distant[A]+w(A,B),松弛完成,权值变为2
那如果是这种情况,如下图:
在这里插入图片描述
Distant[B]<Distant[A]+w(A,B),那就不需要松弛了

Bellman-Ford算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:d(v) > d (u) + w(u,v),存在则返回false,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。

第一次遍历后,Distant[B]=5,Distant[C]=8,但注意接下来这个负权值的边,导致Distant[A]=-2
在这里插入图片描述
第二次遍历后,Distant[B]=3,Distant[C]=6,Distant[A]=-4,开始出现矛盾循环了,正是因为图中存在一条负权值回路,导致每次遍历,各点值都在减少

所以我们就可以在第三部分检查是否存在可松弛机会(即d(v) > d (u) + w(u,v)),如果存在无法收敛的情况,则肯定可以检查出来

所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问题。

上代码,方便以后查阅

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 1005

const int inf = 0x3f3f3f3f;

using namespace std;

int n, m;
int dis[MAXN], u[MAXN], v[MAXN], w[MAXN];

void bellford(int start)
{
	for (int i = 1; i <= n; i++)
		dis[i] = inf;
	dis[start] = 0;
	for (int k = 1; k <= n - 1; k++)//外部循环n-1次
	{
		int flag = 0;
		for (int i = 1; i <= m; i++)//按边松弛
		{
			if (dis[u[i]] < inf && dis[v[i]] > dis[u[i]] + w[i])
			{
				dis[v[i]] = dis[u[i]] + w[i];
				flag = 1;
			}
		}
		if (!flag)	break;//本轮dis数组没有更新,直接退出结束算法。
	}
}

int judge()
{
	int flag = 0;
	for (int i = 1; i <= m; i++)
	{
		if (dis[v[i]] > dis[u[i]] + w[i])
			flag = 1;
		break;
	}
	if (flag)
	{
		printf("此图含有负权回路\n");
		return 0;
	}
	else
		return 1;
}

void print()
{
	for (int i = 1; i <= n; i++)
		printf("%d ", dis[i]);//输出每个点到单源起点的最短距离
	printf("\n");
}
int main()
{
	while (scanf_s("%d%d", &n, &m) != EOF && (n || m))
	{
		//读入边
		for (int i = 1; i <= m; i++)
			scanf_s("%d%d%d", &u[i], &v[i], &w[i]);
		int start;//读入起点
		scanf_s("%d", &start);
		bellford(start);
		if (judge())
			print();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值