图论算法之最短路问题

本文深入探讨了图论中的最短路问题,详细介绍了Bellman-Ford、SPFA和Floyd三种算法的思路、特点及代码实现。Bellman-Ford适用于含负权边的单源最短路,SPFA是Bellman-Ford的队列优化,Floyd则解决了多源最短路的问题。通过实例解析,帮助读者理解这些算法在解决实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图论算法之最短路问题

Bellman-Ford算法

思路

所有的边进行n-1轮松弛,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。换句话说,第1轮在对所有的边进行松弛操作后,得到从1号顶点只能经过一条边到达其余各定点的最短路径长度,第2轮在对所有的边进行松弛操作后,得到从1号顶点只能经过两条边到达其余各定点的最短路径长度,…

特点

单源最短路算法,可求含有负权边的最短路,Bellman-Ford算法可以检测一个图是否含有负权回路:如果经过n-1轮松弛后任然存在dist[e[i]]>dist[s[i]]+w[i],可求经过x条边的最短路距离

例题

acwing853有边数限制的最短路

代码

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 10010;

struct Edge {
    int a;
    int b;
    int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边

int bellman_ford() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i++) {//k次循环
        memcpy(back, dist, sizeof dist);
        for (int j = 0; j < m; j++) {//遍历所有边
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);
            //使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来
        }
    }
    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    else return dist[n];

}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        e[i] = {a, b, w};
    }
    int res = bellman_ford();
    if (res == -1) puts("impossible");
    else cout << res;

    return 0;
}

spfa算法

特点

SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford队列优化,它是一种十分高效的最短路算法。SPFA算法可以判断图中是否有负权环,即一个点的入队次数超过N

思路

建立一个队列,初始时队列里只有起始点s,在建立一个数组记录起始点s到所有点的最短路径(初始值都要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里的点去刷新起始点s到所有点的距离的距离,如果刷新成功且刷新的点不在队列中,则把该点加入到队列,重复执行直到队列为空。

例题

在这里插入图片描述

代码

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10,M=2*N;
int n,m;
// 邻接表的数据结构 
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c) {
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx++;
}
int dist[N];
int cnt[N];
// 存储某一个点是否在队列中,存重复的点没有意义 
bool st[N];
bool spfa() {
	// 只判断是否存在负环时不需要进行初始化 
//	memset(dist,0x3f,sizeof dist);
//	dist[1]=0;
	
	queue<int> q;
//	q.push(1);
//	st[1]=true;

	// 把所有点全部放到队列中,因为负环可能从任何点伸出
	for(int i=1;i<=n;i++) {
		st[i]=true;
		q.push(i);
	} 
	while(q.size()) {
		int t = q.front();
		q.pop();
		st[t]=false;
		for(int i=h[t];i!=-1;i=ne[i]) {
			int j=e[i];
			if(dist[j]>dist[t]+w[i]) {
				dist[j]=dist[t]+w[i];
				cnt[j]=cnt[t]+1;
				if(cnt[j]>=n) return true; 
				if(!st[j]) {
					q.push(j);
					st[j]=true;
				}
			}
		}
	} 
//	只判断是否存在负环时不需要以下这句 
//	if(dist[n] == 0x3f3f3f3f) return -1;
	return false; 
}
int main() {
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	for(int i=0;i<m;i++) {
		int a,b,c;	cin>>a>>b>>c;
		add(a,b,c);
	}
	if(spfa()) puts("Yes");
	else puts("No");
	return 0;
} 

Floyd算法

特点

多源最短路算法

思路

三重循环,最外层枚举每个点最为中间节点,内两层枚举每种点对并以k点为中间节点进行距离的更新。实际上是利用了DP的思想

例题

在这里插入图片描述

代码

#include<iostream>
using namespace std;
const int N=210,INF=0x3f3f3f3f;
int n,m,Q;
int d[N][N];
void floyd() {
	for(int k=1;k<=n;k++) {
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=n;j++) {
				d[i][j]=min(d[i][j], d[i][k]+d[k][j]);
			}
		}
	}
}
int main() {
	cin>>n>>m>>Q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i==j) d[i][j]=0;
			else d[i][j]=INF;
	for(int i=0;i<m;i++) {
		int a,b,w;	cin>>a>>b>>w;
		// 处理重边 
		d[a][b]=min(d[a][b],w);
	}
	floyd(); 
	while(Q --) {
		int a,b;	cin>>a>>b;
		if(d[a][b]>INF/2) puts("impossible");
		else cout<<d[a][b]<<endl;
	}
	return 0;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值