AcWing算法基础课 最短路
最短路问题
一、单源最短路(一个点到所有点的最短距离)
1、所有边的权重都为正
n个点,m个边
(1)朴素dijkstra O(n^2)
适合稠密图,O(n^2)=O(m)
算法步骤:
①初始化距离 dis[1]=0,dis[i]=+∞,集合s为已经找到的点的集合
② for i=1:n
找到点t,t不在s中且距离最近
将s加入t
用t更新其他所有点的距离,dis[t]+t到其他点的边的距离
稠密图适合用邻接矩阵存储!
例题:849. Dijkstra求最短路 I

#include<iostream> #include<cstring> using namespace std; const int N=510; int A_M[N][N]; int n,m; int dis[N]; bool st[N]; int dijkstra() { memset(dis,0x3f,sizeof(dis)); dis[1]=0;//dis初始化 for(int i=0;i<n;i++) { int t=-1; for(int j=1;j<=n;j++) { if(!st[j]&&(t==-1||dis[j]<dis[t])) t=j; } if(t!=-1) { st[t]=true; for(int j=1;j<=n;j++) { dis[j]=min(dis[t]+A_M[t][j],dis[j]); } } } return dis[n]==0x3f3f3f3f?-1:dis[n]; } int main() { memset(A_M,0x3f,sizeof(A_M)); cin>>n>>m; while (m -- ) { int x,y,z; cin>>x>>y>>z; A_M[x][y]=min(A_M[x][y],z); } for(int i=0;i<N;i++) A_M[i][i]=0; cout<<dijkstra(); return 0; }
(2)堆优化版的dijkstra O(mlogn)
适合稀疏图,O(n)=O(m),稀疏图使用邻接表存储,h[],e[],ne[],w[],idx,分别存储邻接表头,节点编号,节点的下一个节点,节点的权重(距离),可插入节点
对(1)②中,将全部点按距离加入最小堆中,则找到点t的复杂度为O(1),用t更新其他全部点,复杂度是mlogn(全部边的数量乘在堆中修改的复杂度)
堆可以手写或用priority_queue,由于priority_queue不支持修改,可以仅不断插入,则对于一个点,后插入的距离一定小于先插入,复杂度变为O(mlogm)=O(mlogn)
例题:850. Dijkstra求最短路 II

#include<iostream> #include <queue> #include<cstring> using namespace std; const int N=150010; int dis[N]; bool vis[N]; typedef pair<int,int> PII; int h[N],e[N],ne[N],w[N],idx=0; int n,m; void insert(int x,int y,int z) { idx++; e[idx]=y; ne[idx]=h[x]; w[idx]=z; h[x]=idx; } int dijkstra() { memset(dis,0x3f,sizeof dis); dis[1]=0; priority_queue<PII,vector<PII>,greater<PII>> heap; heap.push({0,1}); while(heap.size()) { auto [dist,node]=heap.top(); //cout<<node<<endl; heap.pop(); if(vis[node]) continue; vis[node]=true; for(int i=h[node];i!=0;i=ne[i]) { int j=e[i]; //cout<<j<<endl; if(dis[j]>dist+w[i]) { dis[j]=(dist+w[i]); heap.push({dis[j],j}); } } } return dis[n]==0x3f3f3f3f?-1:dis[n]; } int main() { cin>>n>>m; while (m -- ) { int x,y,z; cin>>x>>y>>z; insert(x,y,z); } cout<<dijkstra(); return 0; }
2、存在负权重边
若存在负权回路,则最小距离可能是-∞
(1)Bellman-Ford O(nm)
可以求限制步数k的最短路
算法可以不用存储图,而是将所有边(a->b=w)用数组存储
①初始化dis[],dis[1]=0,dis[i]=∞
②for n //当前迭代次数k,代表着从当前点,经过不超过k步到达每个点的最短路径
dis_fore=dis;//防止一次循环迭代多步,记录上一步的dis
for 所有边 a, b, w
dis[b]=min(dis_fore[b],dis_fore[a]+w);//类似dijkstra,每次对所有边进行更新,而不是对最小距离点的边进行更新
853. 有边数限制的最短路

1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 8 int n,m,k; 9 int dis1[510]; 10 int dis2[510]; 11 vector<pair<pair<int,int>,int>> edge; 12 13 int main() 14 { 15 cin>>n>>m>>k; 16 while (m -- ) 17 { 18 int x,y,z; 19 cin>>x>>y>>z; 20 edge.push_back({{x,y},z}); 21 } 22 memset(dis1,0x3f,sizeof dis1); 23 dis1[1]=0; 24 memset(dis2,0x3f,sizeof dis2); 25 dis2[1]=0; 26 int *ptr1=dis1; 27 int *ptr2=dis2; 28 while(k--) 29 { 30 auto tmp=ptr2; 31 ptr2=ptr1; 32 ptr1=tmp; 33 for(auto [node,dist]:edge) 34 { 35 ptr1[node.second]=min(ptr1[node.second],ptr2[node.first]+dist); 36 } 37 } 38 if(ptr1[n]>0x3f3f3f3f/2) 39 cout<<"impossible"; 40 else 41 cout<<ptr1[n]; 42 return 0; 43 }
(2)SPFA 一般:O(m),最坏:O(nm)
可以解决不含负权,也可以解决含负权边,需要不限制步数且图中不含负环(也可以判断是否有负环)
是对Bellman-Ford算法的优化
BF算法的②中,只有点a的距离被减小了,其后继点的距离才会减小
①初始化,queue,将第一个点入队
② while(que不空)
取出点t=que.front;
更新t的所有出边 t→b
将b加入队列que(判断b是否未在队列中)//只要b在队列中,就可以利用本次更新距离的影响
其余和BF都一样
851. spfa求最短路

1 #include<iostream> 2 #include <queue> 3 #include<cstring> 4 using namespace std; 5 6 const int N=100010; 7 int n,m; 8 int h[N], e[N], w[N], ne[N], idx=0; 9 int dis[N]; 10 bool in_que[N]; 11 12 void Insert(int x,int y,int z) 13 { 14 idx++; 15 e[idx]=y; 16 ne[idx]=h[x]; 17 w[idx]=z; 18 h[x]=idx; 19 } 20 21 bool SPFA() 22 { 23 queue<int> que; 24 memset(dis,0x3f,sizeof dis); 25 que.push(1); 26 dis[1]=0; 27 in_que[1]=true; 28 while(que.size()) 29 { 30 int node=que.front(); 31 que.pop(); 32 in_que[node]=false; 33 for(int i=h[node];i!=0;i=ne[i]) 34 { 35 int j=e[i]; 36 if(dis[j]>dis[node]+w[i]) 37 { 38 dis[j]=dis[node]+w[i]; 39 if(!in_que[j]) 40 { 41 in_que[j]=true; 42 que.push(j); 43 } 44 } 45 } 46 } 47 if(dis[n]>0x3f3f3f3f/2) 48 cout<<"impossible"; 49 else 50 cout<<dis[n]; 51 52 } 53 int main() 54 { 55 cin>>n>>m; 56 while(m--) 57 { 58 int x,y,z; 59 cin>>x>>y>>z; 60 Insert(x,y,z); 61 } 62 SPFA(); 63 return 0; 64 }
当判断是否存在负环时,需要记录cnt[i],表示第i个点的当前最短路径有几步。
每次更新,cnt[j]=cnt[node]+1;
当cnt[j]>=n时,则存在负环(最短路径上存在重复点,重复点处有负环)
判断负环时,初始化时队列中需要放入全部点,cnt[i]、dis[i]初始化为全0,dis[i]表示任意点到当前点i的最小距离
在循环时,负边会令dis[i]逐渐减小
二、多源汇最短路(多个点到多个点的最短路)
Floyd算法,O(n^3)
可以存在负权边,但是不能存在负环
for k 1:n
for i 1:n
for j 1:n
d[i,j]=min(d[i,j],d[i,k],d[k,j]); 用k更新i和j的距离
注意,k在最外层
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人