SPFA算法

最短路径问题

从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径。

解决方法:

SPFA算法

算法特点:用于解决单源最短路径问题,并且具有判断负圈的功能。(本来Bellman-Ford算法是用数组解决的,而我偷懒用了vector来方便我加边)SPFA利用队列对Bellman-Ford进行了优化,舍去了一些无效的操作,效率要高很多。

算法思路:回顾一下Bellman-Ford的算法,仅针对相邻节点计算最短近距离虽然对于FLoyd已经舍去了很多的不必要计算,但仍然存在很多无效计算。而SPFA的优化在于,计算节点u之后,下一步只计算和调整它的邻居,这样能加快收敛的过程。

变化在与增设一个队列vector<edge>e[num],用于存放以节点i为起点的边。

struct edge{
	int u, v, w;
	edge(int a, int b, int c){u=a;v=b;w=c;}
}; 
vector<edge>e[num];

for(int i=0; i<e[u].size(); i++){
	int v = e[u][i].v, w = e[u][i].w;
	if(dis[v] > dis[u] + w){
		……
	}
}

 其他的思路和步骤都是与Bellman-Ford雷同的,不再赘述。

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int num = 10001;
const int inf = 0x3f3f3f3f;

int n, m;
int dis[num];
int pre[num];
struct edge{
	int u, v, w;
	edge(int a, int b, int c){u=a;v=b;w=c;}
}; 
vector<edge>e[num];

void print_path(int u, int v){
	if(u==v){	printf("%d", u);return;}
	print_path(u, pre[v]);
	printf("->%d", v);
}

void spfa(){
	int neg[num];//判断负圈
	int inq[num];//判断是否在队列中
	for(int i=1; i<=n; i++){
		dis[i] = inf;
		neg[i] = inq[i] = 0;
	} 
	
	int s = 1;//定义起点为1
	queue<int>q;
	q.push(s);
	inq[s] = 1;//s进队
	
	while(!q.empty()){
		int u = q.front();
		q.pop();
		inq[u] = 0;//u出队 
		
		for(int i=0; i<e[u].size(); i++){
			int v = e[u][i].v, w = e[u][i].w;
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				pre[v] = u;
				
				if(!inq[v]){
					q.push(v);
					inq[v] = 1;
					neg[v]++;
					if(neg[v]>n){printf("有负圈\n");return ;}
				}
			}
		}
	} 
}

int main(){
	while(~scanf("%d%d", &n, &m) &&n &&m){
		for(int i=1; i<=n; i++)	e[i].clear();
		while(m--){
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			e[a].push_back(edge(a, b, c));
			e[b].push_back(edge(b, a, c));
		}
		
		spfa();
	}
	return 0;
}

这个基于邻接表的代码已经很好了,但是在极端情况下,图特别大,用邻接表会超出空间限制,此时就需要用链式前向星来存图。主要的区别在于:不再用vector来记录同起点的边,而是直接在结构体下定义数组,增设一个函数来加边。

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int inf = INT_MAX/10;
const int num = 1000005;
 
int n, m, cnt;
int head[num];
int dis[num];
int pre[num];
int inq[num];
int neg[num];
struct edge{	int to, w, next;}e[num];
 
void print_path(int u, int v){
	if(u==v)	printf("%d", u);
	print_path(u, pre[v]);
	printf("->%d", v);
}
 
void init(){
	for(int i=0; i<num; i++)
		head[i] = e[i].next = -1;
	cnt = 0;
}
 
void addedge(int u, int v, int w){
	e[cnt].to = v;
	e[cnt].w  = w;
	e[cnt].next = head[u];//指向上一条同起点的边
	head[u] = cnt++;//存放以u为起点的某条边edge的下标 
}
 
void spfa(){
	for(int i=1; i<=n; ++i){
		neg[i] = inq[i] = 0;
		dis[i] = inf;
	}
	int s = 1;//设起点为1 
	neg[s] = 1;
	dis[s] = 0;
	
	queue<int>q;
	q.push(s);
	inq[s] = 1;//入队 
	
	while(!q.empty()){
		int u = q.front();
		q.pop();
		inq[u] = 0;//出队
										//加入的边是从后往前遍历的 
		for(int i=head[u]; ~i; i=e[i].next){//~i 等同于 i!=-1 
			int v = e[i].to,  w = e[i].w;
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				pre[v] = u;
				
				if(!inq[v]){
					inq[v] = 1;
					q.push(v);
					neg[v]++;
					if(neg[v] > n) {	printf("有负圈\n"); return ;}
				}
			}
		} 
	}
}
 
int main(){
	while(~scanf("%d%d", &n, &m) &&n &&m){
		init();
		while(m--){
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			addedge(a, b, c);
			addedge(b, a, c);
		}
		spfa();
		printf("%d\n", dis[n]);
	}
	return 0;
}

 

 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值