SPFA算法详解

SPFA算法详解SPFA算法~~它死了~~

大家好,又见面了,我是你们的朋友全栈君。

前置知识:Bellman-Ford算法

前排提示:SPFA算法非常容易被卡出翔。所以如果不是图中有负权边,尽量使用Dijkstra!(Dijkstra算法不能能处理负权边,但SPFA能)

前排提示*2:一定要先学Bellman-Ford!

0.引子

在Bellman-Ford算法中,每条边都要松弛\(n-1\)轮,还不一定能松弛成功,这实在是太浪费了。能不能想办法改进呢?

非常幸运,SPFA算法能做到这点。(SPFA又名“Bellman-Ford的队列优化”,就是这个原因。)

1.基本思想

先说一个结论:只有一个点在上一轮被松弛成功时,这一轮从这个点连出的点才有可能被成功松弛。

为什么?显而易见

好吧其实我当初也花了不少时间理解这玩意

松弛的本质其实是通过一个点中转来缩短距离(如果你看了前置很容易理解)。所以,如果起点到一个点的距离因为某种原因变小了,从起点到这个距离变小的点连出的点的距离也有可能变小(因为可以通过变小的点中转)。(通读三遍再往下看)

所以,可以在下一轮只用这一轮松弛成功的点进行松弛,这就是SPFA的基本思想。

2.用队列实现

我们知道了在下一轮只用这一轮松弛成功的点进行松弛,就可以把这一轮松弛成功的点放进队列里,下一轮只用从队列里取出的点进行松弛。

为什么是队列而不是其他的玄学数据结构?因为队列具有“先进先出,后进后出”的特点,可以保证这一轮松弛的点不会在这一轮结束之前取出。

干说可能不太理解,所以还是举个栗子吧。

<span role="heading" aria-level="2">SPFA算法详解

这又是之前的有向图,但是这次我们要用SPFA跑。

最开始,我们要把\(1\)号点放进队列(为什么要这样?先往下看)。\(dis\)数组和队列是这个亚子的:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(\infty\)
\(3\) \(\infty\)
\(4\) \(\infty\)
queue 1

\(1\)号点进行松弛(就是\(1\)号到\(1\)号再到目标点):

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(\infty\)
queue 2 3

\(2,3\)号点被松弛成功了,把它们加入到队列里。

\(1\)号点被用过了,把它扔掉。(工具点石锤)

\(2\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(3\)
queue 3 4

\(4\)号点被松弛成功了,把它们加入到队列里。

\(2\)号点被用过了,把它扔掉。

\(3\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(3\)
queue 4

没有点被松弛成功。

\(3\)号点被用过了,把它扔掉。

\(4\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(4\)
\(4\) \(3\)
queue 3

\(3\)号点被松弛成功了,把它们加入到队列里。

\(4\)号点被用过了,把它扔掉。

\(3\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(4\)
\(4\) \(3\)
queue

没有点被松弛成功。

\(3\)号点被用过了,把它扔掉。

现在队列为空(也就是能松弛的都松弛了),算法结束。

3.Code

SPFA的具体实现,推荐结合上面的栗子食用。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 10005
#define INF 0x7fffffff
int n,m,s,dis[MAXN];
vector<pair<int,int> > g[MAXN];//用vector存图,但是据说链式前向星更快
void spfa(){
    queue<int> q;
    q.push(s);//把初始点加入队列
    fill(dis+1,dis+1+n,INF);//因为一开始所有点都到不了,所以初始化为INF
    dis[s]=0;//自己到自己肯定距离为0
    while(!q.empty()){
        int u=q.front();
        q.pop();//从队列里取出第一个元素
        for(int i=0;i<g[u].size();i++){
            int v=g[u][i].first,w=g[u][i].second;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push(v);
            }
            //如果能松弛成功,那么松弛,把松弛成功的目标点放入队列
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        g[u].push_back(make_pair(v,w));
    }//输入,建图
    spfa();
    for(int i=1;i<=n;i++){
        printf("%d ",dis[i]);
    }//输出
    return 0;
}

这个代码能够ACP3371,但是我还是推荐你自己码一遍。

4.特点

  • 能够处理有负权边的图,但是隔壁Dijkstra不行。
  • 在有负环的情况下,不存在最短路,因为不停在负环上绕就能把最短路刷到任意低。但是SPFA能够判断图中是否存在负环,具体方法为统计每个点的入队次数,如果有一个点入队次数$ \ge n $,那么图上存在负环,也就不存在最短路了。
  • 什么?你不知道什么叫负环?下面的就是。

<span role="heading" aria-level="2">SPFA算法详解

就是一个环,边权和是负。一般用一个名为菜鸡算法的算法判断

——Ynoi

  • SPFA的时间复杂度是\(O(km)\)\(k\)是每一个节点的平均入队次数,经过实践,\(k\)一般为\(4\),所以SPFA通常情况下非常高效。但是SPFA非常容易被卡出翔,最坏情况下会变成\(O(nm)\) 所以如果能用隔壁Dijkstra尽量不要用SPFA。至于具体怎么卡,据说是这样的:

<span role="heading" aria-level="2">SPFA算法详解

(这种图据说叫菊花图,能欺骗SPFA多次让点进入队列,所以\(k\)会变得非常大(上限为\(n\))。)

5.你都看到这了就不点一个赞吗?

这个最重要了qwq

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154921.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • seekg()/seekp()与tellg()/tellp()的用法详解

    seekg()/seekp()与tellg()/tellp()的用法详解对输入流操作:seekg()与tellg()对输出流操作:seekp()与tellp()下面以输入流函数为例介绍用法:seekg()是对输入文件定位,它有两个参数:第一个参数是偏移量,第二个参数是基地址。对于第一个参数,可以是正负数值,正的表示向后偏移,负的表示向前偏移。而第二个参数可以是:ios::beg:表示输入流的开始位置ios::cur:表示输入流的当前位置io

  • 怎么关闭磁盘共享(电脑如何关闭默认共享)

         Windows2000/XP/2003版本的操作系统提供了默认共享功能,这些默认的共享都有“$”标志,意为隐含的,包括所有的逻辑盘(C$,D$,E$……)和系统目录Winnt或Windows(admin$)。   带来的问题:   微软的初衷是便于网管进行远程管理,这虽然方便了局域网用户,但对我们个人用户来说这样的设置是不安全的。如果电脑联网,网络上

  • java读取数据库_jsp怎么显示数据库数据

    java读取数据库_jsp怎么显示数据库数据importlmdbimportos,sysdefinitialize():env=lmdb.open(“lmdb_dir”)#如果没有就创建lmdb_dir目录returnenvdefinsert(env,sid,name):txn=env.begin(write=True)txn.put(str(sid).encode(),name.encode())txn.commit()defdelete(env…

  • SP网站SelectSql和searchSql的区别

    SP网站SelectSql和searchSql的区别新入行小白,如有不足请多指教。SP网站进行二次开发,需要对VS开发的页面内的自定义列表行进行删除,需求是用户或者项目经理只能查到并且删除自己的列表。最终发现将源码searchSql改为SelectSql,页面成功显示。此代码中searchSql代表显示所有信息,SelectSql显示特定信息。修改别人的代码真头大。

  • PHP怎么调用其他类的方法

    PHP怎么调用其他类的方法

    2021年10月25日
  • 二进制与十进制,八进制,十六进制转换_十进制转十六进制算法

    二进制与十进制,八进制,十六进制转换_十进制转十六进制算法进制转换:二进制、八进制、十六进制、十进制之间的转换不同进制之间的转换在编程中经常会用到,尤其是C语言。将二进制、八进制、十六进制转换为十进制二进制、八进制和十六进制向十进制转换都非常容易,就是“按权相加”。所谓“权”,也即“位权”。假设当前数字是N进制,那么:对于整数部分,从右往左看,第i位的位权等于Ni-1对于小数部分,恰好相反,要从左往右看,第j位的位权为N-j。…

    2022年10月18日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号