SPFA算法详解
前置知识: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算法详解的更多相关文章
- 图的最短路径-----------SPFA算法详解(TjuOj2831_Wormholes)
这次整理了一下SPFA算法,首先相比Dijkstra算法,SPFA可以处理带有负权变的图.(个人认为原因是SPFA在进行松弛操作时可以对某一条边重复进行松弛,如果存在负权边,在多次松弛某边时可以更新该 ...
- SPFA 算法详解
适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便 派上用场了. 我们约定有向加权图G不存在负权回路,即最短路径 ...
- SPFA 算法详解( 强大图解,不会都难!) (转)
适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便 派上用场了. 我们约定有向加权图G不存在负权回路,即最短路径 ...
- Bellman-Ford算法与SPFA算法详解
PS:如果您只需要Bellman-Ford/SPFA/判负环模板,请到相应的模板部分 上一篇中简单讲解了用于多源最短路的Floyd算法.本篇要介绍的则是用与单源最短路的Bellman-Ford算法和它 ...
- Bellman-Ford&&SPFA算法详解
Dijkstra在正权图上运行速度很快,但是它不能解决有负权的最短路,如下图: Dijkstra运行的结果是(以1为原点):0 2 12 6 14: 但手算的结果,dist[4]的结果显然是5,为什么 ...
- 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?
简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...
- BM算法 Boyer-Moore高质量实现代码详解与算法详解
Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...
- kmp算法详解
转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector ...
随机推荐
- 从键盘输入一个字符串(长度不超过30),统计字符串中非数字的个数,并将统计的结果显示在屏幕上,用EXE格式实现。
问题 从键盘输入一个字符串(长度不超过30),统计字符串中非数字的个数,并将统计的结果显示在屏幕上,用EXE格式实现. 源程序 data segment hintinput db "plea ...
- PHP 中的字符串变量
PHP 字符串变量 字符串变量用于存储并处理文本. PHP 中的字符串变量 字符串变量用于包含有字符的值. 在创建字符串之后,我们就可以对它进行操作了.您可以直接在函数中使用字符串,或者把它存储在变量 ...
- Python time altzone()方法
描述 Python time altzone() 函数返回格林威治西部的夏令时地区的偏移秒数.高佣联盟 www.cgewang.com 如果该地区在格林威治东部会返回负值(如西欧,包括英国).对夏令时 ...
- PHP fileinode() 函数
定义和用法 fileinode() 函数返回指定文件的 inode 编号. 如果成功,该函数返回指定文件的 inode 编号.如果失败,则返回 FALSE. 语法 fileinode(filename ...
- PHP is_writable() 函数
定义和用法 is_writable() 函数检查指定的文件是否可写. 如果文件可写,该函数返回 TRUE. 语法 is_writable(file) 参数 描述 file 必需.规定要检查的文件. 提 ...
- PDO::errorInfo
PDO::errorCode — 返回最后一次操作数据库的错误信息(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 public array PDO::e ...
- 牛客练习赛63 牛牛的树行棋 差分 树上博弈 sg函数
LINK:牛牛的树行棋 本来是不打算写题解的. 不过具体思考 还是有一段时间的. 看完题 一直想转换到阶梯NIM的模型上 转换失败. 考虑SG函数. 容易发现 SG函数\(sg_x=max{sg_{t ...
- luogu P1526 [NOI2003]智破连环阵 搜索+最大匹配+剪枝
LINK:智破连环阵 考试的时候 题意没理解清楚 题目是指一个炸弹爆炸时间结束后再放另一个炸弹 而放完一个炸弹紧接另一个炸弹.题目中存在然后二字. 这样我们可以发现某个炸弹只会炸连续的一段. 但是 由 ...
- electron开发 - mac关闭和隐藏窗口
针对mac平台的app let willQuitApp = false; // 控制退出方式 mainWindow.on('close', (e) => { if (willQuitApp) { ...
- 一、elasticsearch部署
Elasticsearch官网: https://www.elastic.co/products/elasticsearch 一.Linux单节点部署 1. 解压elasticsearch-5.6.1 ...