贝尔曼算法队列优化(SPFA)

最短路问题

想必大家接触图论的时候,结束了DFS和BFS,接下来就是求最短路了吧!在学SPFA之前,你也许已经接触过弗洛伊德算法迪杰斯特拉算法,甚至还学过贝尔曼算法,然而,不管你以前学过什么,今天的SPFA都可以向你保证——零基础学会!
言归正传,话回正题。
什么是最短路问题呢?就是求两点之间的最短路,当然是一个带权图(没权值的图还最短?)。然而SPFA是由贝尔曼改良过来的,所以这也是单源最短路的算法(别在意这专业的叫法,真的在意就比较一下弗洛伊德算法和迪杰斯特拉算法的区别),可是真正的SPFA是贝尔曼的优化但不是我现在所讲的贝尔曼队列优化。但是我会习惯地称为SPFA…不要在意这奇怪的癖好。因为叫着简单。

SPFA需要用到的工具

老规矩,我们用如下代码存图。

int p,s;
struct e{
	int u,v,dis,next;
}edge[maxm];
int head[maxm],js;
int a,b,c;

不懂邻接表的朋友们看看这个:邻接表存图法这个邻接表这里面讲的很通俗,想看不懂是有点难度的哦。
p是总共的点数,s是总共的边数。js用于计数,dis代表u,v两点的距离。a,b,c是临时储存点a到点b距离为c的临时变量。所以我会这么读入:

for(int i=1;i=<s;i++){
		cin>>a>>b>>c;
		addedge(a,b,c);
}

顺便给你一个存图的代码:

void addedge(int u,int v,int dis){
	edge[++js].u=u;
	edge[js].v=v;
	edge[js].dis=dis;
	edge[js].next=head[u];
	head[u]=js;
	return;
}

我们需要一个起始点作为源点,我们就用一个变量start来储存源点吧,我们在这里把点1设为源点。所以start=1。哦,图还没给你们呢。
我来上一个图:
在这里插入图片描述
我们需要一个dis数组来存源点——下面就说点1吧,来储存点1到其他所有点的距离,初始化的距离当然是inf,除了源点自己是0以外。在这里我使用十六进制宏定义,就是定义一个很大的数(其实跟const是一样的),如图:

#define inf 0x3f3f3f

0x3f3f3f就是一个16进制的很大的数,等于1061109567。0x表示16进制。3f开始才是这个数字。
所以我给出的所有的输入就是酱紫:

cin>>p>>s;
for(int i=1;i=<s;i++){
	cin>>a>>b>>c;
	addedge(a,b,c);
}
memset(dis,inf,sizeof(dis));  //初始化dis数组  
cin>>start;
dis[start]=0;
样例输入
5 7
1 2 2
1 5 10
2 5 7
2 3 3
5 3 6
4 5 5
3 4 4
1

队列优化的话是少不了队列的。所以我们上个队列que,队首begin队尾tail。
同样,我们添加了一个vis数组,防止已经加入队列的点重复加入。
最后,我们的预处理代码,预处理就是正式处理之前的定义什么的啦~就是酱紫:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f
const int maxm=10001;
int p,s;
struct e{
	int u,v,dis,next;
}edge[maxm];
int head[maxm],js;
int a,b,c;
int que[maxm],begin,tail;
int dis[maxm],start;
int vis[maxm];

记住,memset在cstring库里边。

样例模拟

图论问题最重要的是模拟,我们以上图为例来模拟一次。
模拟之前我们看看我们的核心SPFA代码。

void spfa(int x){
	begin=end=1;
	que[end++]=x;
	while(end-begin!=0){  //队列不为空执行循环 
		for(int i=head[que[begin]];i;i=edge[i].next){
			if(dis[edge[i].v]>dis[que[begin]]+edge[i].dis){
				dis[edge[i].v]=dis[que[begin]]+edge[i].dis;
				if(!vis[edge[i].v]){
					que[end++]=edge[i].v;
					vis[edge[i].v]=1;
				}
			}
		} 
		begin++;
	}
	return;
}

逻辑上还是很简单的,我现在给你讲讲。
初始化就是酱紫:
在这里插入图片描述
然后我们读入的x是1,队首元素就是1了。于是我从点1开始遍历,发现了点2可以走,所以我们就到达了这个判断: i f ( d i s [ e d g e [ i ] . v ] > d i s [ q u e [ b e g i n ] ] + e d g e [ i ] . d i s ) ,就是当前的队首元素的dis值加上队首元素的点到当前搜到的点的权值。然后发现比dis[2]小,所以执行判断内部的语句了。
换句话说,就是满足了d i s [ 2] > d i s [ 1 ] 加上 点1到点2的距离。
那么我们就让 d i s [ 2] = d i s [ 1 ] 加上 点1到点2的距离 ,所以dis[2]就从偌大的inf变到了2,再把点2入队,就是执行 q u e [ e n d + + ] = e d g e [ i ] . v 这句到时候再从点2开始找有没有能进一步缩小的。同理,dis[5]也从偌大的inf变成了10,再把点5入队。
当然,点2和点5都要在vis里面记录已经入队了。
点1只能搜到2和5,那么点1的使命到此为止了,我们就把点1出队,就是begin++。
所以第一轮结束的结果就是酱紫,begin指向2,tail指向4:
在这里插入图片描述
当然,队列不为空吧。所以继续。
只是我们从点2开始了。
点2就找到了点3,进入判断i f ( d i s [ e d g e [ i ] . v ] > d i s [ q u e [ b e g i n ] ] + e d g e [ i ] . d i s ) ,发现比dis[3]小,于是就让 d i s [ 3] = d i s [ 2 ] 加上 点2到点3的距离。然后让点3入队。当然,点2还找到了点5,哇,又是点5!一样的判断,我们却发现d i s [ 5] 居然大于 d i s [ 2 ] 加上 点2到点5的距离!没法子,那么我dis[5]就要变了!当然是变小!当然,入队还是一样的入队…但是点5已在队列中,所以不需要再一次入队。
所以这一轮结束,就是酱紫:
在这里插入图片描述
我们从点5开始继续新一轮搜索,然后搜到了点3,但是d i s [ 3] < d i s [ 5 ] 加上 点5到点3的距离,所以不更新,也就不存在入队什么的了。
然后点3开始搜索,他搜到了点4,所以dis[4]=9
最后,就会是酱紫的结果,点4搜完后,begin和tail的差就变成0了,SPFA算法结束:
在这里插入图片描述
最后我们输出dis数组,就可以查看点1到所有点的最短了。
最后给出样例输出:
在这里插入图片描述
完整代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f
const int maxm=10001;
int p,s;
struct e{
	int u,v,dis,next;
}edge[maxm];
int head[maxm],js;
int a,b,c;
int que[maxm],begin,end;
int dis[maxm],start;
int vis[maxm];

void addedge(int u,int v,int dis){
	edge[++js].u=u;
	edge[js].v=v;
	edge[js].dis=dis;
	edge[js].next=head[u];
	head[u]=js;
	return;
}

void spfa(int x){
	begin=end=1;
	que[end++]=x;
	while(end-begin!=0){  //队列不为空执行循环 
		for(int i=head[que[begin]];i;i=edge[i].next){
			if(dis[edge[i].v]>dis[que[begin]]+edge[i].dis){
				dis[edge[i].v]=dis[que[begin]]+edge[i].dis;
				if(!vis[edge[i].v]){
					que[end++]=edge[i].v;
					vis[edge[i].v]=1;
				}
			}
		} 
		begin++;
	}
	return;
}

int main(){
	cin>>p>>s;
	for(int i=1;i<=s;i++){
		cin>>a>>b>>c;
		addedge(a,b,c);
	}
	memset(dis,inf,sizeof(dis));  //初始化dis数组  
	cin>>start;
	dis[start]=0;
	
	spfa(start);
	
	for(int i=1;i<=p;i++) cout<<dis[i]<<endl;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值