最短路问题spfa

题目链接:单源最短路
突然发现以前没写过spfa
dijstra传送门

SPFA算法核心思想

SPFA(Shortest Path Faster Algorithm) 是Bellman-Ford算法的队列优化版本,用于求解单源最短路径问题,特别适合处理有负权边但无负环的图。

算法步骤:

  1. 初始化

    • 所有节点距离初始化为无穷大(INT_MAX
    • 源点距离设为0,加入队列
  2. 队列处理

    • 从队列取出节点u
    • 遍历u的所有邻接边(u,v)
    • 如果dis[v] > dis[u] + w(u,v),则更新dis[v]
    • 如果v不在队列中且被更新了,将v加入队列
  3. 重复直到队列为空

核心代码:

void spfa(int x){
    for(int i=1;i<=n;i++) dis[i]=INT_MAX;  // 初始化距离
    ex[x]=1;ext[1]=x;dis[x]=0;int r=1,h=1; // 源点入队
    
    while(r<=h){  // 队列不为空
        int temp=head[ext[r]];  // 当前节点的第一条边
        ex[ext[r]]=0;  // 标记出队
        
        while(1){  // 遍历所有邻接边
            int v=to[temp];
            if(dis[v]>dis[ext[r]]+lon[temp]){  // 松弛操作
                dis[v]=dis[ext[r]]+lon[temp];
                if(!ex[v]){  // 如果不在队列中
                    ext[++h]=v;  // 入队
                    ex[v]=1;
                    num[v]++;  // 记录入队次数
                }
                if(num[v]>n) return;  // 检测负环
            }
            if(link[temp]) temp=link[temp];  // 下一条边
            else break;
        }
        r++;  // 处理下一个节点
    }
}

关键特点:

  1. 数据结构:使用链式前向星存图
  2. 负环检测:`num[v] > n 时认为存在负环
  3. 优化:只有被更新的节点才重新入队

突然发现2018年写的代码现在过不了洛谷评测了,原因是link不能做变量名了

这就是SPFA的精髓:通过队列避免不必要的松弛操作,比朴素Bellman-Ford效率更高!

2018版本

#include<bits/stdc++.h>
using namespace std;

int n,m,s;
int e=0,dis[110001],to[1500001],link[1500001],head[110001],lon[1500001];
void put(int x,int y,int z){
    to[++e]=y;
    lon[e]=z;
    link[e]=head[x];
    head[x]=e;
}
bool ex[10001];int ext[600001],num[10001];
void spfa(int x){
    for(int i=1;i<=n;i++) dis[i]=INT_MAX;
    ex[x]=1;ext[1]=x;dis[x]=0;int r=1,h=1;
    while(r<=h){
        int temp=head[ext[r]];
        ex[ext[r]]=0;
        while(1){
            int v=to[temp];
            if(dis[v]>dis[ext[r]]+lon[temp]){
                dis[v]=dis[ext[r]]+lon[temp];
                if(!ex[v]){
                ext[++h]=v;
                ex[v]=1;
                num[v]++;
            }
                if(num[v]>n) return;
            }
            if(link[temp]) temp=link[temp];
            else break;
        }
        r++;
    }
}

int main(){
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,l;
        cin>>u>>v>>l;
        put(u,v,l);
    }
    spfa(s);
    for(int i=1;i<=n;i++){
        cout<<dis[i]<<" ";
    }
    //cheak code
    //cout<<endl;
    //cout<<INT_MAX;
}

如图

2025版本

#include<bits/stdc++.h>
using namespace std;

int n,m,s;
int e=0,dis[110001],to[1500001],nxt[1500001],head[110001],lon[1500001]; // 将link改为nxt

void put(int x,int y,int z){
    to[++e]=y;
    lon[e]=z;
    nxt[e]=head[x]; // 改为nxt
    head[x]=e;
}

bool ex[110001]; // 大小改为110001
int ext[600001],num[110001]; // 大小改为110001

void spfa(int x){
    for(int i=1;i<=n;i++) dis[i]=INT_MAX;
    memset(ex,0,sizeof(ex));
    memset(num,0,sizeof(num));
    
    ex[x]=1;
    ext[1]=x;
    dis[x]=0;
    int r=1,h=1;
    
    while(r<=h){
        int u=ext[r];
        ex[u]=0;
        
        for(int temp=head[u];temp;temp=nxt[temp]){ // 改为nxt,使用for循环遍历
            int v=to[temp];
            if(dis[v]>dis[u]+lon[temp]){
                dis[v]=dis[u]+lon[temp];
                if(!ex[v]){
                    ext[++h]=v;
                    ex[v]=1;
                    num[v]++;
                    if(num[v]>n) return; // 通常判断负环用n而不是m
                }
            }
        }
        r++;
    }
}

int main(){
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,l;
        cin>>u>>v>>l;
        put(u,v,l);
    }
    spfa(s);
    for(int i=1;i<=n;i++){
        cout<<dis[i]<<" ";
    }
    return 0;
}

SPFA时间复杂度分析

理论时间复杂度

最坏情况O(VE),其中V是节点数,E是边数

  • 每个节点可能入队V次
  • 每次出队需要遍历所有邻接边

平均情况O(kE),其中k是常数,通常k≈2

  • 在实际应用中表现很好
  • 大多数节点只入队1-2次

最好情况O(E)

  • 类似BFS,每个节点只入队一次

代码具体分析

while(r<=h){                    // 外层循环:最多O(V)次
    int temp=head[ext[r]];
    ex[ext[r]]=0;
    
    while(1){                   // 内层循环:遍历所有邻接边,O(deg(v))
        // ...松弛操作...
        if(link[temp]) temp=link[temp];
        else break;
    }
    r++;
}

总复杂度:Σ(deg(v) × 入队次数) = O(VE)

与相关算法对比

算法时间复杂度适用场景
DijkstraO((V+E)logV)无负权边
Bellman-FordO(VE)有负权边
SPFA平均O(kE),最坏O(VE)有负权边,稀疏图
FloydO(V³)多源最短路径

实际性能特点

  1. 稀疏图表现优异:在边数E ≈ V的情况下接近O(V)
  2. 稠密图退化:当E ≈ V²时,退化为O(V³)
  3. 对负环敏感:存在负环时可能陷入死循环(你的代码有检测机制)
  4. 常数因子小:实际运行通常比理论分析要好

总结:SPFA在竞赛和实际应用中很受欢迎,因为对于随机图它的平均时间复杂度接近O(E),比Dijkstra的O(ElogV)常数更小,还能处理负权边。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值