一、前言

在之前的学习中,我们学过了Dijkstra算法和Bellman_Ford算法求最短路问题(Dijkstra求最短路)(Bellman_Ford求有限制的最短路),而今天我们要继续图论的学习。名义上,Spfa算法是Bellman_Ford的优化算法,但是…它的代码长得和Dijkstra算法真的好像啊,真的好像啊。

二、概念介绍

算法思路: 在Bellman_Ford算法中,我们需要依次遍历每一条边(a----->b,长为w)来更新我们的dis[b]。但是我们发现,如果dis[a]在循环中如果一直没有出现变化,那么公式(dis[b]=min(dis[b],te[a]+w))就一直不会更新我们的dis[b],做了很多的无用操作,对程序的运行速度造成了比较大的影响。那我们需要这样去想:我们可不可以用一个方法记录一下a的状态,只有当dis[a]发生了变化时,我们才去更新所有以a为起点的点呢?这,就是Spfa算法的思路。而在时间复杂度上,虽然spfa算法的时间复杂度有退化的可能性,但基本上优于Bellman_Ford。

实现思路: 在代码中,我们会使用一个队列q去存下所有dis发生了改变的值(首先把起点放进队列),然后依次遍历与它相连的点,如果字节点通过比较后发生了更新而且队列中没有这个子节点,那我们就把子节点也放进队列q。在遍历完一个点的所有子节点后,我们把这个点移出队列。然后只要队列不空,我们就一直循环。如果这里没有看懂,在之后的代码中我会解释。

负权边的处理问题: 在这里我们要讲一讲图中负权边对算法的影响,由于spfa仍然求的是没有次数限制的最短路问题,所以如果出现一个负权边形成的环,那么队列是会陷入一个死循环从而出错。为了避免这种情况,我们可以用一个数组记录下每一个点进入队列q的次数,如果进入的次数达到了一个比较大的明显不正常的值,我们就认为存在负权环,从而退出算法函数。

三、代码实现基础Spfa

例题链接:Acwing spfa算法求最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。数据保证不存在负权回路。输入格式
第一行包含整数 n 和 m。接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。如果路径不存在,则输出 impossible。数据范围
1≤n,m≤1e5,
图中涉及边长绝对值均不超过 10000。输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2

在题目中明确提出了是没有负权回路的,所以我们可以放心的使用spfa算法了

看懂下面的代码你需要提前知道以下知识点:
1.链式前向星存图(链式前向星)
2.作者重写的memset函数(“#define mem(a,b) memset(a,b,sizeof(a))”)
3.BFS宽度优先搜索的基本思想
4.C++STL中queue队列数据结构的基本用法

//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef  pair<int, int> PII;
const int N = 1e5 + 7;int e[N], ne[N], w[N], h[N], id = 1;  //链式前向星
int n, m;
int dis[N];   //dis[i]代表从起点到i的距离
bool ch[N];   //用来判断一个点是否在队列中
void add(int a, int b, int c)   //加边函数
{e[id] = b;w[id] = c;ne[id] = h[a];h[a] = id;id++;
}int spfa()
{mem(dis, 0x3f);   //距离的初始化dis[1] = 0;   //起点到自己的距离为0queue<int>q;q.push(1);   //先把起点放进去ch[1] = true;   //记录一下起点放进去了while (!q.empty())   //循环条件:队列非空{int f = q.front();   //取出队首元素q.pop();   //把队首踢出去ch[f] = false;   //记录一下队首已经被踢出去了for (int i = h[f]; i != -1; i = ne[i])   //链式前向星遍历所有以f为起点的边{int j = e[i];if (dis[j] > dis[f] + w[i])   //如果点j发生了更新{dis[j] = dis[f] + w[i];   //更新一下if (!ch[j])   //如果点j没在队列中{ch[j] = true;   //把点j放进队列q.push(j);}}}}return dis[n];   //输出最后的结果
}void solve()
{mem(h, -1);   //初始化链式前向星cin >> n >> m;for (int i = 0; i < m; i++){int a, b, c;cin >> a >> b >> c;add(a, b, c);}int t = spfa();if (t == INF)   //如果是INF代表到不了cout << "impossible" << endl;elsecout << t << endl;
}int main()
{//std::ios::sync_with_stdio(false);//cin.tie(0), cout.tie(0);solve();return 0;
}

四、代码实现Spfa对负权环的判断

例题链接:Acwing spfa判断负环

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。请你判断图中是否存在负权回路。输入格式
第一行包含整数 n 和 m。接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。输出格式
如果图中存在负权回路,则输出 Yes,否则输出 No。数据范围
1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes

分析:这道题中并没有要求我们求出最短路的距离,所以dis数组可以不初始化为0x3f3f3f3f,初始化为0就可以达到我们想要的效果了。因为0大于负数,而遇到负权边的时候dis会进行更新。除此之外,这道题并没有说明起点,所以我们需要在最开始把所有的点push进队列q。

//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef  pair<int, int> PII;
const int N = 1e6 + 7;int e[N], ne[N], w[N], h[N], id = 1;  //链式前向星
int n, m;
int dis[N];   //dis[i]代表从起点到i的距离
bool ch[N];   //用来判断一个点是否在队列中
int num[N];  //num[i]代表走到i经过了多少条边 void add(int a, int b, int c)   //加边函数
{e[id] = b;w[id] = c;ne[id] = h[a];h[a] = id;id++;
}int spfa()
{/*本题不是求距离,可以不用初始化dis*/queue<int>q;for (int i = 1; i <= n; i++){q.push(i);ch[i] = true;}while (!q.empty())   //循环条件:队列非空{int f = q.front();   //取出队首元素q.pop();   //把队首踢出去ch[f] = false;   //记录一下队首已经被踢出去了for (int i = h[f]; i != -1; i = ne[i])   //链式前向星遍历所有以f为起点的边{int j = e[i];if (dis[j] > dis[f] + w[i])   //如果点j发生了更新{dis[j] = dis[f] + w[i];   //更新一下num[j] = num[f] + 1;if (num[j] >= n)  //经过的边的数量如果大于了n-1,那就说明至少重复走了一个点,有负权环(只有存在负权环,才可能在求最短路的时候两次经过一个点)return -1;if (!ch[j])   //如果点j没在队列中{ch[j] = true;   //把点j放进队列q.push(j);}}}}return 1;   //输出最后的结果
}void solve()
{mem(h, -1);   //初始化链式前向星cin >> n >> m;for (int i = 0; i < m; i++){int a, b, c;cin >> a >> b >> c;add(a, b, c);}int t = spfa();if (t == -1)  cout << "Yes" << endl;elsecout << "No" << endl;
}int main()
{//std::ios::sync_with_stdio(false);//cin.tie(0), cout.tie(0);solve();return 0;
}

作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》

【图论】Spfa算法求最短路(长得像Dijkstra的,Bellman_Ford的优化算法)相关推荐

  1. 2019中山纪念中学夏令营-Day14 图论初步【dijkstra算法求最短路】

    Dijkstra是我学会的第一个最短路算法,为什么不先去学SPFA呢?因为我在luogu上翻到了一张比较神奇的图: 关于SPFA -它死了 以及网上还有各位大佬的经验告诉我:SPFA这玩意很容易被卡. ...

  2. SPFA算法求最短路

    AcWing 851. spfa求最短路 题目 https://www.acwing.com/problem/content/submission/853/ 给定一个 n 个点 m 条边的有向图,图中 ...

  3. POJ 3255(迪杰斯特拉算法求次短路)

    POJ3255,问题是求节点1到n的次短路. 在dijkstra求最短路算法的基础上进行变形,用两个数组分别记录源点到各节点最短路径和次短路径: 每次更新时,都将最短路的节点及可能成为次短路的节点pu ...

  4. Floyd算法求最短路

    Floyd算法(基于动态规划):用于求多源汇最短路 初始化:用邻接矩阵d[i,j]存储中所有的边,floyd算法就是三重循环 for(k = 1;k <= n;k ++){ for(i = 1; ...

  5. MATLAB 求函数极值的内置函数一览表(实则优化算法函数汇总)

    MATLAB 求函数极值的内置函数一览表 收集了几乎所有的 MATLAB 内置的优化函数,可收藏,需要时查阅. 文章目录 MATLAB 求函数极值的内置函数一览表 简介 概览 求函数极值(或最大值最小 ...

  6. 【智能优化算法】基于分段权重和变异反向学习的蝴蝶优化算法求解单目标优化问题附matlab代码

    1 简介 针对原始蝴蝶优化算法容易陷入局部最优解,收敛速度慢及寻优精度低等问题,提出分段权重和变异反向学习的蝴蝶优化算法.通过飞行引领策略来矫正邻域内蝴蝶的自身飞行,降低盲目飞行,增强算法跳出局部最优 ...

  7. skew算法_一种新型Skew Tent映射的混沌混合优化算法

    一种新型 Skew Tent 映射的混沌混合优化算法 江善和 ; 王其申 ; 江巨浪 [期刊名称] <控制理论与应用> [年 ( 卷 ), 期] 2007(024)002 [摘要] 针对已 ...

  8. 【单源最短路】Dijkstra算法求最短路

    题目描述 给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值. 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1. 输入格式 第一 ...

  9. Dijkstra算法求最短路

    一.算法描述 Dijkstra算法的流程如下: 1.初始化dist[1] = 0,其余节点的dist值为无穷大. 2.找出一个未被标记的.dist[x]最小的节点x,然后标记节点x. 3.扫描节点x的 ...

  10. 优化算法|MOAVOA:一种新的多目标人工秃鹰优化算法(Matlab代码实现)

最新文章

  1. C++知识:__stdcall、__cdcel和__fastcall三者的区别
  2. jquery对ajax的支持
  3. 最小生成树(prime算法、kruskal算法) 和 最短路径算法(floyd、dijkstra)
  4. c#实现显式的用户自定义类型转换(关键字explicit operator)
  5. Python多任务(多线程执行带有参数的任务,利用threading创建线程时传入参数--args参数和kwargs参数)
  6. 实现一个悬浮在软键盘上的输入栏
  7. flex与flash元件交互
  8. 基于FPGA实现AD7609接口
  9. C#中的三层前馈神经网络,带有图形显示
  10. Activity初级:startActivityForResult、重写onActivityResult、setResult回传数据、requestCode请求码...
  11. JT/T808校验码计算(按字节异或求和)
  12. outlook邮箱签名设置
  13. PHP手册-use关键字
  14. AndroidStudio利用android-support-multidex解决65536问题64k问题
  15. c++ 常用总结(三)
  16. android 浏览器隐藏地址,移动端隐藏手机浏览器的地址栏一下底部的菜单栏
  17. 什么是GPU加速,如何使用GPU加速,GPU加速的缺点
  18. 嵌入式设备NFS挂载目录(基于iTop 4412)
  19. java获取汉字拼音_Java获取汉字对应的拼音(全拼或首字母)
  20. 湖北5G继续加码!今年投资64亿元,新建5G基站5万个

热门文章

  1. 小红书创始人瞿芳回应裁员风波:战略部署清晰 人员翻倍
  2. js jquery select 操作 获取值,选中选项,增加,修改,删除
  3. Microsoft Exchange 2010 and Outlook 2010
  4. 源码大招:不服来战!撸这些完整项目,你不牛逼都难! 1
  5. 使用runtime跳转界面
  6. openstack nova后端使用ceph rbd(增加在线迁移live_migrate和快照snapshot功能)
  7. 输入一个整数,计算它各位上数字的和。(注意:是任意位的整数)
  8. Lucene4:创建一个简单查询
  9. ALV 行、列、单元格颜色设置
  10. SEO学习笔记-PR值