前置知识:Bellman-Ford算法

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

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

0.引子

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

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

1.基本思想

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

为什么?显而易见

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

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

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

2.用队列实现

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

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

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

这又是之前的有向图,但是这次我们要用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 $,那么图上存在负环,也就不存在最短路了。
  • 什么?你不知道什么叫负环?下面的就是。

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

——Ynoi

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

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

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

这个最重要了qwq

SPFA算法详解的更多相关文章

  1. 图的最短路径-----------SPFA算法详解(TjuOj2831_Wormholes)

    这次整理了一下SPFA算法,首先相比Dijkstra算法,SPFA可以处理带有负权变的图.(个人认为原因是SPFA在进行松弛操作时可以对某一条边重复进行松弛,如果存在负权边,在多次松弛某边时可以更新该 ...

  2. SPFA 算法详解

    适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便 派上用场了. 我们约定有向加权图G不存在负权回路,即最短路径 ...

  3. SPFA 算法详解( 强大图解,不会都难!) (转)

    适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便 派上用场了. 我们约定有向加权图G不存在负权回路,即最短路径 ...

  4. Bellman-Ford算法与SPFA算法详解

    PS:如果您只需要Bellman-Ford/SPFA/判负环模板,请到相应的模板部分 上一篇中简单讲解了用于多源最短路的Floyd算法.本篇要介绍的则是用与单源最短路的Bellman-Ford算法和它 ...

  5. Bellman-Ford&&SPFA算法详解

    Dijkstra在正权图上运行速度很快,但是它不能解决有负权的最短路,如下图: Dijkstra运行的结果是(以1为原点):0 2 12 6 14: 但手算的结果,dist[4]的结果显然是5,为什么 ...

  6. 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?

    简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...

  7. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  8. kmp算法详解

    转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...

  9. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

随机推荐

  1. 如何在 UltraEdit 删除空行(含空格,制表符)

    如何在 UltraEdit 删除空行(含空格,制表符) 打开UltraEdit,ctrl+r弹出替换对话框,点选启用正则表达式方法1:在查找框输入 ^p^p:在替换框输入 ^p执行全部替换:这种方法是 ...

  2. layer.js : n.on is not a function

    当时使用的jQuery为1.4.x的版本.换成高版本就好了. 参考 https://blog.csdn.net/marswill/article/details/69316003

  3. github 错误

    Push failed: Unable to access 'https://github.com/infoo/Neo4j.git/': The requested URL returned erro ...

  4. 基于AspectCore打造属性注入

    前言 源自于晓晨在成都.net社区群的一篇文章 <晓晨的ASP.NET Core 奇淫技巧之伪属性注入> 他的思路是 Ioc容器替换 ControllerActivator,因为只能在控制 ...

  5. 修改当前项目maven仓库地址

    pom.xml中修改 <repositories> <repository> <id>nexus-aliyun</id> <name>Nex ...

  6. python7.4邮件发送

  7. 017_go语言中的指针

    代码演示 package main import "fmt" func zeroval(ival int) { ival = 0 } func zeroptr(iptr *int) ...

  8. Android Studio--家庭记账本(四)

    今天,实现了在数据库中的删除功能,但是无法实现对表单的删除与自动更新.需要重新启动虚拟机重新从数据库中读取数据才可以实现表单的更新.List表单中的remove功能不太会用.

  9. 99%的人都会遇到的Python “用户环境”问题

  10. 2020-05-14:实现分布式Session的过程是怎样的?

    福哥答案2020-05-14: 此答案来自群员:分布式session ,最开始起源是双机热备,当时php1和php2为了共享session,一开始采用samba/nfs,后来php有了插件以后就扔当时 ...