最短路径专题
最短路问题可以分类成两种:
1、单源最短路:给定源点 ss,试求 ss到图中任一顶点 vv的最短路长度。
2、多源最短路:求出任意一对顶点 (u, v)(u,v)之间的最短路距离。
通常情况下,我们需要根据问题的不同需要选择不同的算法:
1、根据问题模型分类,求解单源最短路需要使用 Dijkstra或 SPFA算法,求解多源最短路需要使用 Floyd算法。
2、根据边权分类,如果图中不存在负边权,则可以使用 Dijkstra算法,否则只能使用 Floyd 或 SPFA算法。
Floyd
最短路求解
Floyd算法实质上属于动态规划的思想。 Floyd算法用于求解多源最短路问题。它的效率较低,但是模型的可拓展性很高。通常情况下,除了需要灵感的题目,其余的难题均需要使用 Floyd算法实现。同时, Floyd算法的代码实现很简单。
Floyd算法的主要思想是: 设 dp[i][j]表示从顶点 i 到顶点 j 的最短路距离。另有一顶点 k, i 到 k、 k到 j均存在一条边。则 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])。
从 i到 j的最短路有两种形式:
1、直接从 i 到 j。
2、从 i 经过某个中转点 k,再从 k到达 j。
我们枚举每一个中转点 k,从 i到 j的距离有两种选择:经过中转点 k, dp[i][j] = dp[i][k] + dp[k][j];原来的最短路, dp[i][j]。取最小值即为从 i到 j的最短路。
Floyd算法的时间复杂度是 O(n^3),空间复杂度是 O(n^2)。
Floyd模板
#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int n,m,x,y,a,b,w,f[maxn][maxn];
int main(){
cin>>n>>m>>x>>y;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=m;i++)cin>>a>>b>>w,f[a][b]=w,f[b][a]=w;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
cout<<f[x][y];
}
SPFA
SPFA是一种类似于 bfs的算法。它在大部分情况下速度会优于其他的最短路算法。“关于 SPFA,它死了”,由于其过于优秀,导致出题人通常会特意卡 SPFA。故而除非图中可能出现负环或负权边,否则不使用 SPFA。
SPFA的主要思想是:使用队列保存可以松弛的顶点。每次取出队首的顶点,并松弛与其相邻的顶点。如果某一顶点 v被松弛,且其未被加入松弛队列,则将其加入松弛队列。重复以上操作,直到队列为空。通常情况下,我们使用 bool 数组表示顶点是否在队列中。
SPFA不能处理有负环的图,但是它可以判断图中是否存在负环。判断负环的方法有两种,其中第二种方法通常会更为高效:
1、如果图中有 n个顶点,且某个顶点的入队次数达到 n次,说明图中存在负环。
2、如果图中有 n个顶点,且某个顶点的最短路经过了超过 n个顶点,说明图中存在负环。
Spfa模板
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m,x,y,a,b,w,tot,head[maxn],dis[maxn],vis[maxn];
queue<int>q;
struct edge{
int to,next,w;
}e[maxn];
void add(int a,int b,int w){
e[++tot].next=head[a];
e[tot].w=w;
e[tot].to=b;
head[a]=tot;
}
void Spfa(){
memset(dis,0x3f,sizeof(dis));
dis[x]=0;
q.push(x);
vis[x]=1;
while(!q.empty()){
int tmp=q.front();
q.pop();
vis[tmp]=0;
for(int i=head[tmp];i;i=e[i].next){
if(dis[e[i].to]>dis[tmp]+e[i].w){
dis[e[i].to]=dis[tmp]+e[i].w;
if(!vis[e[i].to]){
q.push(e[i].to);
vis[e[i].to]=1;
//cou[e[i].to]++; //判负回,入队次数大于节点数
//if(cou[e[i].to]>n){cout<<"-1";exit(0);};
}
}
}
}
}
int main(){
cin>>n>>m>>x>>y;
for(int i=1;i<=m;i++)cin>>a>>b>>w,add(a,b,w);//add(b,a,w);
Spfa();
//for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
cout<<dis[y];
return 0;
}
Dijkstra
Dijkstra算法是一种贪心算法。它用于求解单源最短路问题。它可以用于处理没有负边权的图,但是不能处理负边权,也不能用于求解最长路问题。通常情况下,脑洞题思维题都会用到 Dijkstra算法。
Dijkstra算法的主要思想是:我们每次取出一个最短路已经被更新的、最短路最短的顶点 u,此时从1(x)点到u点的距离已经确定,再也不能更改(这就是贪心的真理! ! !),然后用 u更新与其相邻的顶点 v,这步操作称为松弛。如果 v的最短路长度大于从源点 s到达 u,再从 u到达 v的路径长度,那么就将 v的最短路长度更新为最小值。
因为图中不存在负边权,所以每次我们取出的顶点 u的最短路长度一定不大于从图中另外一个未被松弛的顶点 v到达 u的最短路长度。故而 Dijkstra算法在不存在负边权的情况下正确。
因为我们需要维护最短路最小的顶点(维护最值),所以 Dijkstra算法可以使用堆优化。使用堆优化后, Dijkstra算法的时间复杂度是 O(nlogn + m),其中 n为点数, m为边数。堆优化的实现可以使用 set,也可以使用优先队列,推荐使用后者。
Dijkstra模板
#include<bits/stdc++.h>
using namespace std;
const int maxn=4*1e5+5;
const int maxm=1e5+5;
int n,m,head[maxn],a,b,w,tot,dis[maxn],vis[maxn];
struct edge{
int to,next,w;
}g[maxn];
struct node{
int i,dis;
bool operator < (const node tmp)const{
return tmp.dis<dis;
}
};
void add(int a,int b,int w){
g[++tot].to=b;
g[tot].next=head[a];
g[tot].w=w;
head[a]=tot;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>a>>b>>w,add(a,b,w);
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
priority_queue<node>q;
q.push((node){1,0});
while(!q.empty()){
node temp=q.top();
q.pop();
//cout<<temp.i<<" "<<temp.dis<<endl;
int now=temp.i;
if(vis[now]==1) continue;
vis[now]=1;
for(int i=head[now];i;i=g[i].next){
int next=g[i].to;
if(dis[next]>dis[now]+g[i].w){
dis[next]=dis[now]+g[i].w;
q.push((node){next,dis[next]});
}
}
}
cout<<dis[n];
//for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
}
次短路模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
using namespace std;
const int M=200000+10;
int head[M],cnt;
struct node
{
int v,w,nxt;
}edge[M];
struct edg{
int i,dis;
bool operator < (const edg tmp)const{
return tmp.dis<dis;
}
};
int dis1[5005];
int dis2[5005];
void add(int x,int y,int w)
{
edge[++cnt].nxt=head[x];
edge[cnt].v=y;
edge[cnt].w=w;
head[x]=cnt;
}
void Dijkstra(int x)
{
memset(dis1,INF,sizeof(dis1));
memset(dis2,INF,sizeof(dis2));
priority_queue<edg>qu;
dis1[x]=0;//最短路初始值为0,次短路无穷大
qu.push((edg){x,0});
while(!qu.empty( ))
{
int w=qu.top( ).dis;//弹出最小值,或许是最短路,或许是次短路
int u=qu.top( ).i;
qu.pop( );
if(dis2[u]<w)//弹出来的值比当前的次短路大,就可以跳过这个
{
continue;
}
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
int cost=w+edge[i].w;//u到v的花费
if(dis1[v]>cost)//花费大于原来的最小值,更新最短路
{
swap(dis1[v],cost);//交换值
qu.push((edg){v,dis1[v]});//压入队列
}
if(dis2[v]>cost&&cost>dis1[v])//交换次短路
{
swap(dis2[v],cost);
qu.push((edg){v,dis2[v]});//压入队列,之所以次短路要压入队列是因为后面更新需要。
//例子:dis[2] = 10, dis2[2] = 20 有一条边 2 到 6 的边权值为 5
//如果不把 dis2 入队,那么之后的算法中 dis[6] = 15, dis2[6] = INF
//只有当队列里有 20 这个值,才能 20+5 得出 25,然后更新 dis2[6] = 25
}
}
}
}
int main( ){
int n,r;
scanf("%d%d",&n,&r);
int x,y,w;
for(int i=1;i<=r;i++){
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
add(y,x,w);
}
Dijkstra(1);
printf("%d",dis2[n]);
return 0;
}
本文来自博客园,作者:蒟蒻orz,转载请注明原文链接:https://www.cnblogs.com/orzz/p/18122234
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人