SPFA算法

本文深入讲解了SPFA算法,一种高效的最短路径算法,适用于含有负权边的有向图。文章通过两个实例,详细解释了如何使用SPFA算法求解最短路径问题和判断图中是否存在负权回路。

判断图中是否有负环可用spfa算法,比Bellman-Ford算法效率高。

d[v]=min(d[v],d[u]+w),只有d[u]变小,d[v]才变小,spfa基于这一点进行优化。 只要d[u]变小,则将其压入队列中,并更新以u为起点的所有路径。

例题:spfa求最短路

给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出impossible。

数据保证不存在负权回路。

输入格式

第一行包含整数n和m。

接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。

输出格式

输出一个整数,表示1号点到n号点的最短距离。

如果路径不存在,则输出”impossible”。

数据范围

1≤n,m≤10^5
图中涉及边长绝对值均不超过10000。

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

 

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N = 100010, M = 100010, INF = 0x3f3f3f3f;
int dist[N];
bool visited[N];
int head[N], ver[M], edge[M], ne[M];
int idx, n, m;
void add(int x, int y, int z){
	ver[idx] = y, edge[idx] = z, ne[idx] = head[x], head[x] = idx ++;
}
int spfa(){
	memset(dist, 0x3f, sizeof(dist));
	queue<int>Q;
	Q.push(1);
	dist[1] = 0;
//visited[]存储是否在当前点是否在队列中
	visited[1] = true;
	while(!Q.empty()){
		int x = Q.front();
		Q.pop();
		visited[x] = false;
		for(int i = head[x]; i != -1; i = ne[i]){
			int y = ver[i], z = edge[i];
			if(dist[y] > dist[x] + z){
				dist[y] = dist[x] + z;
				if(!visited[y]){
					visited[y] = true;
					Q.push(y);
				}
			}
		}
	} 
	if(dist[n] == INF) return -1;
	return dist[n];
}
int main(){
	scanf("%d%d", &n, &m);
	while(m --){
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
	}
	int res = spfa();
	if(res == -1) puts("impossible");
	else printf("%d", res);
	return 0; 
}

 

 

给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路。

输入格式

第一行包含整数n和m。

接下来m行每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。

输出格式

如果图中存在负权回路,则输出“Yes”,否则输出“No”。

数据范围

1≤n≤2000,1≤m≤10000,图中涉及边长绝对值均不超过10000。

输入样例:

3 3
1 2 -1
2 3 4
3 1 -4

输出样例:

Yes

不需要初始化d数组,判断是否有负环,并不是判断是否只有从1开始的负环,所以要把所有的点都压入队列中。
原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点(n条边),由抽屉原理一定有两个点相同,所以存在环,而且一定是负环。

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const int N = 2010, M = 10010, INF = 0x3f3f3f3f;
int head[N], ver[M], edge[M], ne[M];
int cnt[N]; 
/*
cnt[]存储1到x的最短路中经过的点数
visited[]存储每个点是否在队列中
*/
bool visited[N];
int dist[N];
int idx, n, m;
void add(int x, int y, int z){
	ver[idx] = y, edge[idx] = z, ne[idx] = head[x], head[x] = idx ++;
}
bool spfa(){
	queue<int>Q;
	for(int i = 1; i <= n; i ++){
		Q.push(i);
		visited[i] = true;
	}
	while(!Q.empty()){
		int x = Q.front();
		Q.pop();

		visited[x] = false;
		for(int i = head[x]; i != -1; i = ne[i]){
			int y = ver[i], z = edge[i];
			if(dist[y] > dist[x] + z){
				dist[y] = dist[x] + z;
				cnt[y] = cnt[x] + 1;
// 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
				if(cnt[y] >= n) return true;
				if(!visited[y]){
					visited[y] = true;
					Q.push(y);
				}
			}
		}
	}
	return false;
}
int main(){
	memset(head, -1, sizeof(head));
	cin >> n >> m;
	while(m --){
		int x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
	}
	if(spfa()) cout << "Yes" << endl;
	else cout << "No" << endl;
	return 0;
} 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值