最短路算法总结
最短路算法总结 2023.3.15
最短路的概念
在一个图中有 n个点、m条边。边有权值,权值可正可负。边可能是有向的,也可能是无向的。给定两个点,起点是s,终点是t,在所有能连接s和t的路径中寻找边的权值之“和” 最小的路径,这就是最短路径问题。如下图:求v1->v6的最短距离。
最短路类型
单源最短路:从单个节点出发,到所有节点的最短路
多源最短路:整个图中所有点到其他点的最短路
对于无权图的最短路的求法,是一个经典的搜索结构。最简单的方式是通过BFS逐个节点进行遍历。
对于有权图最短路的求法
对于有权的一个图,需要注意边权正负或是否存在负权回路,根据题目不同选择合适的处理办法。
一、Floyd最短路算法
1)基本思想:对于任意一条s->t的路径,必定存在一个点k处于该路径上(k可能与s,t相等)。原型dist[k][i][j]表示找i和j之间通过编号不超过k(k从1到n)的节点的最短路径。推导公式为:dist[k][i][j] = min(dist[k-1][i][j] , dist[k-1][i][k]+dist[k-1][k][j]),最终优化为:dist[i][j]=min(dist[i][k]+dist[k][j],dist[i][j]),可以用三角形判定法理解。
2)在Floyd算法枚举ki的时候,已经得到了前 k-1 个点的最短路径,这 k-1 个点不包括点 k,并且他们的最短路径中也不包括 k 点
3)三层for循环即可实现,需要注意的是,最外层一定是枚举k,即要先求出任意两点i,j通过点k得到的最短距离。
4)floyd是一个多源最短路径算法,即经过一次floyd后能求出任意两点间的最短路
5)可以适用于没有负权回路的图,复杂度为:O(m3)
floyd模板
#include<bits/stdc++.h> using namespace std; int g[205][205]; int main(){ memset(g,0x3f,sizeof g); int n,m,k;cin>>n>>m>>k; while(m--){ int x,y,z;cin>>x>>y>>z; g[x][y]=z; } //最短路径的处理,floyd 佛洛依德最短路办法 for(int p=1;p<=n;p++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ g[i][j]=min(g[i][p]+g[p][j],g[i][j]); } } } while(k--){ int x,y;cin>>x>>y; if(g[x][y]==0x3f3f3f3f) cout<<"impossible"<<endl; else cout<<g[x][y]<<endl; } return 0; }
二、SPFA最短路算法
1)基本思想:初始化起点s的距离为0,其他点的距离为正无穷,将所有与s相连的ki点入队,,如果点ki的距离发生变化,更新点ki的距离,并将点ki重新入队。直到所有点距离不再更新,此时求得最短路。
2)如果存在一个负权环,那环上的点会被多次入队。利用这个特性做判定,如果某个入队次数大于等于n,那必然存在负权环。(对于某个点,最多有n-1个点使它距离变小,每次距离变小便入队一次,所以最多入队n-1次)
3)存在负权边或负权回路依然可用。因为存在一个点多次入队的情况,所以最坏的情况复杂度为O(n*m),最好的情况为O(m),容易被卡。
spfa模板
//SPFA算法 #include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,m,s; int head[N];//当前点指向的边 int ne[N<<1];//当前边的上一条边 int ver[N<<1];//当前边的重点 int w[N<<1];//当前边的权值 int tot;//边的个数 void add(int x,int y,int z){//邻接表存边,也是图的通常存储应用 ver[++tot]=y,w[tot] =z; ne[tot]=head[x],head[x]=tot; } queue<int> q; int dis[N]; void spfa(){ memset(dis,0x3f,sizeof dis); dis[1]=0; q.push(1); while(!q.empty()){ int x=q.front();q.pop(); for(int i=head[x];i;i=ne[i]){ int y=ver[i]; if(dis[y]>dis[x]+w[i]){ dis[y]=dis[x]+w[i]; q.push(y); } } } } int main(){ cin>>n>>m; while(m--){ int x,y,z;cin>>x>>y>>z; add(x,y,z); } spfa(); cout<<dis[n]; return 0; }
SPFA判断负环
思路:在spfa的基础上新建要给cnt数组,统计各点入队次数。若入队次数>=n则一定存在负权换。
#include<bits/stdc++.h> using namespace std; const int N=6e3+5; int n,m,s; int head[N],ne[N],ver[N],w[N],tot; void add(int x,int y,int z){//邻接表存边,也是图的通常存储应用 ver[++tot]=y,w[tot] =z; ne[tot]=head[x],head[x]=tot; } int dis[N];//记录点i到起点的最短距离 int cnt[N];//记录点i的入队次数 queue<int> q; bool spfa(){ memset(dis,0x3f,sizeof dis);//初始化其他点为正无穷 dis[1]=0;q.push(1); while(!q.empty()){ int x=q.front();q.pop(); for(int i=head[x];i;i=ne[i]){ int y=ver[i]; if(dis[y]>dis[x]+w[i]){//注意dis[x]+w[i]在有些题目当中出现爆int情况 cnt[y]=cnt[x]+1; if(cnt[y]>=n) return true;//入队超过n-1次,存在负权环 dis[y]=dis[x]+w[i]; q.push(y); } } } return false; } void solve(){ for(int i=0;i<=6000;i++){//多测清空 head[i]=ne[i]=ver[i]=w[i]=cnt[i]=tot=0; queue<int> empty; swap(empty,q); } cin>>n>>m; while(m--){ int x,y,z;cin>>x>>y>>z; add(x,y,z); if(z>=0) add(y,x,z); } if(spfa()) cout<<"YES"<<endl; else cout<<"NO"<<endl; } int main(){ int T;cin>>T; while(T--) solve(); return 0; }
三、dijkstra最短路算法
1)spfa算法复杂度不可控在于每个点可能多次入队出队,若能保证每个点只出队一次,那么算法将会更加稳定。dijkstra则是这样的思想对spfa进行优化。
2)若存在一个全为非负边权的有向图,我们初始化起点dis[s]=0,其他点为正无穷。按照各点到起点的距离从小到大排序依次出队,当某点x要执行出队操作时,此时dis[x]在队列中一定最小,即不存在一个路径s->k->x,使得dis[k]+w[k,x]<dis[x]。因为dis[k]>dis[x],那么只要k到x的边权为正,则dis[x]就一定为最短路径。这也是dijkstra不能处理负边权的原因。
3)通常有优先队列priority_queue进行处理。每个点出队一次,加上优先队列内部排序,总体复杂度约为:O(m*logn)
dijkstra处理最短路
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,m,s; int head[N],ne[N<<1],ver[N<<1],w[N<<1],tot; void add(int x,int y,int z){//邻接表存边,也是图的通常存储应用 ver[++tot]=y,w[tot] =z; ne[tot]=head[x],head[x]=tot; } struct node{ int id,d; bool operator < (const node &p) const{//按到起点的距离从小到大排序 return d>p.d; } }; bool vis[N];//记录点i的入队情况 int dis[N];//记录到起点的最短距离 priority_queue<node> q; void dijkstra(){ memset(dis,0x3f,sizeof dis); q.push({s,0});dis[s]=0;//初始化起点距离为0,其他点距离为正无穷 while(!q.empty()){ node tmp=q.top();q.pop(); int x=tmp.id; if(vis[x]) continue;//标记出队,出队时当前距离一定最短 vis[x]=true; for(int i=head[x];i;i=ne[i]){ int y=ver[i]; dis[y]=min(dis[y],dis[x]+w[i]); q.push({y,dis[y]}); } } } int main(){ cin>>n>>m>>s; while(m--){ int x,y,z;cin>>x>>y>>z; add(x,y,z); } dijkstra(); for(int i=1;i<=n;i++) cout<<dis[i]<<" "; return 0; }
四、bellman-ford算法
1)基本思想:跟spfa和dijsktra一样,每个顶点进行距离更新时都是由某一条边决定。即dis[y] > dis[x]+w时进行更新(通过x->y这条边更新y到顶点得距离)。我们初始化起点距离为0,其他点为正无穷。显然,我们遍历m条边,通过对每条边进行松弛操作(通过该边更新y的距离),一次完整得遍历至少会有一个点的距离产生更新。那么对于n个顶点,由于起点已经有了最小值,只需要进行n-1次遍历边进行松弛即可。复杂度为O(n*m)
2)优化:若在一次遍历边的过程中,若没有产生新的距离更新,则此时所有点的最短距离已经固定,可以提前结束。
3)判断负环:执行完n-1次遍历边的操作后,此时已经得出答案。如果在第n次再次执行遍历边操作依然产生了新的更新,说明存在负环。
4)常见应用:对于某条路径s->t,执行一次松弛操作等价于最多为路径添加一条边。所以bellman-ford可以处理有边数限制的最短路。
Bellman_ford核心代码
struct node{ int x,y,w; } a[N]; int dis[N]; bool bellman_ford(){//核心代码 memset(dis,0x3f,sizeof dis);//其他边距离初始化为正无穷 dis[1]=0;//起点为0 for(int k=1;k<n;k++){//n-1轮遍历边的操作 bool flag=false; for(int i=1;i<=m;i++){//逐条边遍历 int x=a[i].x,y=a[i].y,w=a[i].w; if(dis[y]>dis[x]+w && dis[x]!=0x3f3f3f3f){//正无穷过来的边不用更新 dis[y]=dis[x]+w; flag=true; } } if(!flag) break;//没有再产生更新,提前结束。 } for(int i=1;i<=m;i++){//第n次松弛,若还存在更新,则说明存在负环 int x=a[i].x,y=a[i].y,w=a[i].w; if(dis[y]>dis[x]+w && dis[x]!=0x3f3f3f3f) return true; } return false; }
最短路问题扩展技巧
1.最短路径输出方案 例题:T317647
开一个 pre
数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。
比如 Floyd 就要记录 pre[i][j] = k;
SPFA 和 Dijkstra 一般记录 pre[v] = u
。
2.传递闭包(连通性的判断)例题:T319398
利用floyd算法原理,将i,j两点的连通性通过中间点k进行转移。如果 (i,k) && (k,j) ,那么(i,j)就连通。另外不要忽略(i,j)原本就联通的情况。
3.有边数限制的最短路 例题:T317715
bellman_ford通过对边进行松弛,松弛k次的时候,此时最短路径所用的边数一定是小于等于k,利用这个性质可以进行问题的求解。
算法总结对比
算法/对比 | 主要使用方向 | 时间复杂度 | 处理负边权 | 处理负权回路 |
Floyd | 带权图的多源最短路径 | O(m3) | YES | NO |
SPFA | 带权图的单源最短路径 | 最坏O(n*m) | YES | YES |
dijkstra | 带权图的单源最短路径 | 约为O(m*logn) | NO | NO |
Bell-man Ford | 带权图中指定边数的单源最短路 | O(n*m) | YES | YES |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具