【C++】Ford算法和SPFA算法

算法简介

B e l l m a n − F o r d Bellman-Ford BellmanFord(以下简称 F o r d Ford Ford)算法是一种单源最短路径算法,他由 R i c h a r d   B e l l m a n Richard\ Bellman Richard Bellman L e s t e r   F o r d Lester\ Ford Lester Ford提出并因此得名,其中 R i c h a r d   B e l l m a n Richard\ Bellman Richard Bellman是动态规划的提出中。 F o r d Ford Ford算法可以用于求特定点到任意点的最短路径,相比于 D i j k s t r a Dijkstra Dijkstra算法, F o r d Ford Ford算法可以求带负权边的图,适用面更广。但是 F o r d Ford Ford复杂度高,达到了 O ( V E ) O(VE) O(VE)

假设采取邻接表存储, d i s t [ i ] dist[i] dist[i]表示源点到 i i i号顶点的最短距离, f r o m [ k ] , t o [ k ] , v a l [ k ] from[k],to[k],val[k] from[k],to[k],val[k]存储的是第 k k k条边的起点、终点和权值。

核心代码

for(int i=1;i<n;i++){//循环n-1次
	for(int j=1;j<=m;j++){//遍历所有的边
		int u=from[j],v=to[j],w=val[j];
		dist[v]=min(dist[v],dist[u]+w);//进行松弛操作
	}
}

我们可以发现 F o r d Ford Ford算法的松弛操作和迪杰斯特拉算法类似,但是 F o r d Ford Ford算法用边做松弛,迪杰斯特拉算法用点做松弛。至于为什么是循环 n − 1 n-1 n1次,这是因为两个点之间的最短距离最多经过 n − 1 n-1 n1条边,超过这个数量还可以松弛就说明肯定有负环存在。所以 F o r d Ford Ford算法不仅可以求解最短路径,还可以判断图是否有负环。

如果循环 n − 1 n-1 n1次之后,仍然可以松弛,那么说明存在负环。

for(int i=1;i<=m;i++){
	int u=from[j],v=to[j],w=val[j];
	if(dist[u]+w<dist[v]){
		return false;//n-1次之后还能松弛说明有负环
	}
}
return true;//否则返回true

优化

提前退出循环

在实际当中,普遍情况是并不需要 n − 1 n-1 n1条边就能得到最短路,所以 n − 1 n-1 n1次循环实际上可以提前退出,避免过大的循环次数。

优化点在于,如果某一轮没有成功松弛,那么就可以退出循环。

bool Ford(int x){
	memset(dist,INF,sizeof(dist));//初始化x到任意点距离为无穷大
	dist[x]=0;//x到自己距离为0 
	for(int i=1;i<n;i++){//循环n-1次
		bool flag=true;
		for(int j=1;j<=m;j++){//遍历所有的边
			int u=from[j],v=to[j],w=val[j];
			if(dist[u]+w<dist[v]){
				dist[v]=min(dist[v],dist[u]+w);//进行松弛操作
				flag=false;//能够松弛就不用结束 
			}
		}
		if(flag) return true;//不能松弛,提前结束
	}
	for(int i=1;i<=m;i++){
		int u=from[i],v=to[i],w=val[i];
		if(dist[u]+w<dist[v]){
			return false;//n-1次之后还能松弛说明有负环
		}
	}
	return true;//计算完成
}

队列优化— S P F A SPFA SPFA算法

  • F o r d Ford Ford算法有许多冗余松弛,我们可以看到 F o r d Ford Ford算法需要遍历每一条边。实际上,只有那些路径变小了的点才可能使得与该点连通的点路径减小。

算法思想

我们需要维护一个队列 Q Q Q,首先只存储起点 x x x,记 d i s t [ i ] dist[i] dist[i]数组存 x − > i x->i x>i的距离, v i s i t [ i ] visit[i] visit[i]表示顶点 i i i是否被松弛过。

  1. 初始化 d i s t [ ] = ∞ dist[]=\infin dist[]= d i s t [ x ] = 0 dist[x]=0 dist[x]=0 v i s i t [ 1 ∼ n ] = 0 visit[1\sim n]=0 visit[1n]=0,分别表示起点任意点距离无穷大,到自己距离为 0 0 0,所有点未被访问。
  2. 将起点编号 x x x入队 Q Q Q v i s i t [ x ] visit[x] visit[x]标记为 1 1 1表示已被访问。
  3. Q Q Q中取出队头顶点编号 k k k,记录 k k k未被访问。
  4. 松弛所有以 k k k为起点的边对应的终点。入队松弛成功且未被访问的顶点编号 v v v,并标记为已访问。
  5. v v v的松弛次数加 1 1 1,如果超过顶点数则有负环,否则重复第 2 2 2步直到队列为空。

初始化

  • 因为 S P F A SPFA SPFA的初始化有点长,所以这里把初始化剥离出来了。
  • 初始化到任意点的距离为 ∞ \infin ,到起点距离 0 0 0,所有顶点未被访问,起点被访问,以及所有点松弛 0 0 0次。
void init(int x){
	memset(dist,INF,sizeof(dist));//初始化x到任意点距离为无穷大
	dist[x]=0;//x到自己距离为0 
	memset(visit,0,sizeof(visit));//初始化未访问 
	visit[x]=1;//初始化自己被访问 
	memset(in,0,sizeof(in));//初始化所有点未被松弛 
	SPFA(x);//调用SPFA
}

S P F A SPFA SPFA

  • S P F A SPFA SPFA算法主要以记忆为主,不是特别容易理解,所以直接贴代码加注释来解释了。
bool SPFA(int x){
	queue<int> Q;
	Q.push(x);//入队x 
	while(!Q.empty()){
		int i=Q.front();Q.pop();//出队 
		visit[i]=0;//标记为未访问 
		for(int j=head[i];j;j=last[j]){
			int u=from[j],v=to[j],w=val[j];
			if(dist[u]+w<dist[v]){
				dist[v]=dist[u]+w;//进行松弛操作
				if(!visit[v]){//如果未被访问 
					Q.push(v);//加入队列
					visit[v]=1;//标记为已被访问 
					if(++in[v]>n) return false;//如果松弛超过n次说明有负环 
				}
			}
		}
	}
	return true;//计算完成
}

总结

本文所用算法都是 b o o l bool bool类型,这样可以扩展到判定负环的问题上, S P F A SPFA SPFA的最坏复杂度和 F o r d Ford Ford一样,都高达 O ( V E ) O(VE) O(VE),所以实际上能用 D i j k s t r a Dijkstra Dijkstra算法的一般还是采用 D i j k s t r a Dijkstra Dijkstra实现, F o r d Ford Ford S P F A SPFA SPFA更适用于带负权的图。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cout0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值