SPFA算法:AcWing 851. spfa求最短路

本文详细介绍了SPFA算法,它是队列优化的Bellman-Ford算法,通过维护待扩展节点的队列,避免冗余扫描,适用于随机图,平均时间复杂度为O(m)。特别指出在特殊情况下可能退化为O(nm),并讨论了如何优化为堆优先队列在非负权图上的O(mlogn)效率。

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

SPFA算法分析:

SPFA算法实际上是“队列优化的Bellman-Ford 算法”。

SPFA 算法的流程如下:

  • 1.建立一个队列,最初队列中只含有起点1
  • 2.取出队头节点x,扫描它的所有出边(x,y,z),若dist[y] > dist[x]+z,则使用dist[x]+z更新 dist[y]。同时,若y不在队列中,则把y入队。
  • 3.重复上述步骤,直到队列为空。

在任意时刻,该算法的队列都保存了待扩展的节点。每次入队相当于完成一次 dist数组的更新操作,使其满足三角形不等式。

一个节点可能会入队、出队多次。最终,图中节点收敛到全部满足三角形不等式的状态。

这个队列 避免了Bellman-Ford算法中对不需要扩展的节点的冗余扫描,在随机图上运行效率为 O(km)级别,其中 k 是一个较小的常数

注意,在特殊构造的图上,该算法很可能退化为O(nm),必须谨慎使用。(这就是“关于SPFA,它已经死了”的由来)
————————————————————————

ps:

即使图中存在负权边,Bellman-Ford和 SPFA算法也能够正常工作(这很强大)。

另外,如果图中不存在长度为负数的边,那么类似于优先队列 BFS,我们也可以使用priority_queue对SPFA算法进行优化,用堆代替一般的队列,用于保存待扩展的节点,每次取出“当前距离最小”的节点(堆顶)进行扩展,节点第一次从堆中被取出时,就得到了该点的最短路。这与堆优化 Dijkstra算法的流程完全一致。

堆优化的 Dijkstra算法” 和 “优先队列优化的SPFA 算法”两种思想殊途同归,都得到了 非负权图时间复杂度为O(mlogn)单源最短路径算法。

代码片段:

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则输出“impossible”
void spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int> q;
    q.push(1);
    st[1]=true;//st数组存储当前这个点是否在队列当中,防止队列当中存储重复的点
    while(!q.empty())
    {
        int t=q.front();
        q.pop();//队头t弹出了,置为false,表示不在队列中
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int b=e[i];
            if(dist[b]>dist[t]+w[i])
            {
                dist[b]=dist[t]+w[i];
                if(!st[b])//如果更新的点b不在队列中,才把它加到队列中去,如果队列中已存在b,则不需要将b重复插入
                {
                    q.push(b);
                    st[b]=true;
                }
            }
        }
    }
    if(dist[n]==inf) cout<<"impossible"<<endl;
    else cout<<dist[n]<<endl;
}

例题:AcWing 851. spfa求最短路
在这里插入图片描述
在这里插入图片描述
时间复杂度:

平均情况下 O(m)最坏情况下 O(nm),
n 表示点数,m 表示边数)

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 150000+10;
#define inf 0x3f3f3f3f
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool st[N];
int x,y,z,n,m;
void add(int x,int y,int z)
{
    e[idx]=y,ne[idx]=h[x],h[x]=idx,w[idx]=z,idx++;
}
void spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int> q;
    q.push(1);
    st[1]=true;//st数组存储当前这个点是否在队列当中,防止队列当中存储重复的点
    while(!q.empty())
    {
        int t=q.front();
        q.pop();//队头t弹出了,置为false,表示不在队列中
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int b=e[i];
            if(dist[b]>dist[t]+w[i])
            {
                dist[b]=dist[t]+w[i];
                if(!st[b])//如果更新的点b不在队列中,才把它加到队列中去
                {
                    q.push(b);
                    st[b]=true;
                }
            }
        }
    }
    if(dist[n]==inf) cout<<"impossible"<<endl;
    else cout<<dist[n]<<endl;
}
int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    memset(h,-1,sizeof h);
    cin>>n>>m;
    while (m -- )
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    spfa();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值