C++ SPFA算法解析

news/2024/11/18 14:29:28/文章来源:https://www.cnblogs.com/Mono-Awen/p/18374186

前言

将了解 C++ 求最短路中 SPFA 的算法

SPFA

SPFA的一些说明

SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).!

引例:

输入格式

给出一个有向图,请输出从某一点出发所有点最短路径长度
三个整数 n, m, s,分别表示点的个数、有向边的个数、出发点的编号
接下来 m 行, 包含三个整数 u,v, w, u --> v长度为 w;

输出格式

输出一行 n 个整数,第 i 个表示 s 到第 i 个点的最短路径,若不能到达则输出 231 - 1;

输入样例 输出样例
4 6 1 0 2 4 3
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

此题的输入 数据范围70% 即可通过

我们需要一个 数组 来记录 一个点某个点 的最短距离, 我们可以定义一个 cost数组 来记录 权值

写好输入数据

我们的最大值可定义为 2e6 + 9 (即2000009), 不能到达则输出 231 - 1 (即2147483647)将它定义为 INF;

const int N = 2e6 + 9; // const常量,不可改变,数尽量大一些
const int INF = 2147483647;
int n, m, s, u, v, w;void AddEdge() {} // 连边函数signed main() {cin >> n >> m >> s;for (int i = 1; i <= m; ++ i) {cin >> u >> v >> w;AddEdge(u, v, w); // 待会进行的连边所使用的函数}
}

连边

我们需要定义一个 结构体 Edge 然后进行连边操作

int cnt = 0, head[N];struct Edge {int nxt, to, val; 
} edge[N];void AddEdge(int from, int to, int val) {cnt++; // 记录操作次数edge[cnt].nxt = head[from];edge[cnt].to = to;edge[cnt].val = val;head[from] = cnt;    
}

SPFA函数的编写

主程序 main(续写) 中 连边后执行 SPFA 函数

signed main() {……for (int i = 1; i <= m; ++ i) {cin >> u >> v >> w;AddEdge(u, v, w);}SPFA();
}

vis 记录节点是否 进/出 队列,cost 记录节点从 s某个节点 的总共权值

int vis[N], cost[N];

SPFA 函数中, 我们要进行 vis 数组的 清零, 需用到 memset 函数来执行,让 cost[n] 里的各数值为 INF,即作为无穷大的数,当不可到达时,可直接输出 cost[i] (i为各个点),可到达时,使用 min() 函数可求最短路径,将 cost[s] (即起点)的数值为 0, 因为它到它自己本身的距离为 0

void SPFA() {memset(vis, 0, sizeof(vis));for (int i = 1; i <= n; ++ i)cost[i] = INF;cost[s] = 0;
}

定义一个 队列 q,每次取 队头 执行 松弛操作,添加 起点

void SPFA() {……cost[s] = 0;queue <int> q;q.push(s);vis[s] = 1;// 让起点 s 标记为 1, 代表已进入队列
}

当队列不为 时, 进行循环

void SPFA() {……vis[s] = 1;while (!q.empty()) {int x = q.front(); // 获取队头数字q.pop(); // 弹出队头vis[x] = 0; // 队头已取出,此时 取出的队头 数字 的 vis[队头数字] 改为 0 代表 出队}
}

以下代码段为松弛操作(以便不懂的小伙伴)

if (cost[y] > cost[x] + edge[i].val) {cost[y] = cost[x] + edge[i].val;if (vis[y] == 0) {vis[y] = 1;q.push(y);}
}

队头已取出,此时 取出的队头 数字vis[队头数字] 改为 0 代表 出队

void SPFA() {……while (!q.empty()) {……vis[x] = 0;for (int i = head[x]; i; i = edge[i].nxt) {int y = edge[i].to; // 代表为 i 时的 edge的 下一个指向if (cost[y] > cost[x] + edge[i].val) { // 如果 指向(未更改 原本存储) 的权值 大于 x 的权值 + 指向的权值cost[y] = cost[x] + edge[i].val; //进行 权值 的 更改if (vis[y] == 0) { //判断是否曾进入队列vis[y] = 1;q.push(y);}}}}
}

最后在主程序中进行 权值 的输出

signed main() {……SPFA();for (int i = 1; i <= n; ++ i ) {cout << cost[i];}
}

最终代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 9; // const常量,不可改变
const int INF = 2147483647;
int n, m, s, u, v, w;
int cnt = 0, head[N], vis[N], cost[N];struct Edge {int nxt, to, val;
} edge[N];
void AddEdge(int from, int to, int val) {cnt++;edge[cnt].nxt = head[from];edge[cnt].to = to;edge[cnt].val = val;head[from] = cnt;
}
void SPFA() {memset(vis, 0, sizeof(0));for (int i = 1; i <= n; ++ i)cost[i] = INF;cost[s] = 0;queue<int> q;q.push(s);vis[s] = 1;while(!q.empty()) {int x = q.front();q.pop();vis[x] = 0;for (int i = head[x]; i; i = edge[i].nxt) {int y = edge[i].to;if (cost[y] > cost[x] + edge[i].val) {cost[y] = cost[x] + edge[i].val;if (vis[y] == 0) {vis[y] = 1;q.push(y);}}}}
}
signed main() {cin >> n >> m >> s;for (int i = 1; i <= m; ++ i) {cin >> u >> v >> w;AddEdge(u, v, w);}SPFA();for (int i = 1; i <= n; ++ i) {cout << cost[i] << " ";}
}

SPFA的负环判断

洛谷负环判断模板题目:传送门

我将上述代码的变量 cnt 循环次数 改为 tim,创建了一个 cnt数组, 用来储存 每个数的入队次数

存储的是入队次数而不是松弛次数

int cnt[N];

题目部分要求

若 u >= 0,则表示存在一条从 u 至υ边权为 w 的边,还存在一条从υ至u 边权为 w 的边。
若 u < 0,则只表示存在一条从 u 至υ边权为 w 的边。

主程序 main 中的部分代码段应改为

for (int i = 1; i <= m; ++ i) {cin >> u >> v >> w;AddEdge(u, v, w);if (w >= 0) AddEdge(v, u, w);
}

最终程序

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e6 + 9;
const int INF = 3e6 + 10;
int T;
int n, m, u, v, w, tim, head[N], vis[N], cost[N], cnt[N]; //cnt 换成 tim, 且创建 cnt 数组
struct Edge
{int nxt, to, val;
} edge[N];
// 清空函数: start
void Clear() {memset(edge, 0, sizeof(edge));tim = 0;memset(head, 0, sizeof(head));memset(vis, 0, sizeof(vis));memset(cost, INF, sizeof(cost));memset(cnt, 0, sizeof(cnt));
}
//清空函数: end
void AddEdge(int from, int to, int val) {tim++; //原本的cnt需改成tim,含义一致edge[tim].nxt = head[from];edge[tim].to = to;edge[tim].val = val;head[from] = tim;
}
bool SPFA() {memset(vis,0, sizeof(vis));for (int i = 1; i <= n; ++ i) cost[i] = INF;cost[1] = 0;queue<int> q;q.push(1); vis[1] = 1;while (!q.empty()) {int x = q.front();q.pop();vis[x] = false;for (int i = head[x]; i; i = edge[i].nxt) {int v = edge[i].to;if (cost[v] > cost[x] + edge[i].val) {cost[v] = cost[x] + edge[i].val;if (!vis[v]) {vis[v] = 1;q.push(v);cnt[v] ++; // 记录 edge[i].to 的次数if (cnt[v] >= n) return true; // 如果 入队次数 已经 大于等于 n}}}}return false;
}signed main() {ios::sync_with_stdio(false); cin.tie(0);cin >> T;while (T--) { //重复循环次数Clear(); //进行清空cin >> n >> m;for (int i = 1; i <= m; ++ i) {cin >> u >> v >> w;AddEdge(u, v, w);if (w >= 0) AddEdge(v, u, w); //更改的地方}cout << (SPFA() ? "YES" : "NO") << endl; //更改的地方}
}

因为进行多次 数组 的询问,我们要清空之前的数据

以上就是有关 SPFA 的知识点,感谢你能看到这里! Cheer !

相关练习:
洛谷 P3371:传送门

相关资料:
最短路相关算法复杂度比较

外部参考:
SPFA需要队列的原因
SPFA算法解析
链式前向星 相关知识_1
链式前向星 相关知识_2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/785467.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.Net8 部署到IIS 10 上需要注意的点

现在大部分项目都上云了,而且是linux的系统,这部分我下一篇再讲,这次讲一下如何部署到iis10,首先项目点击发布-》目标框架.net 8 部署模式是独立,目标运行时是win-x64, 你也可以选择部署模式为依赖框架,目标运行时选择可移植,但是这样的话要注意IIS的应用程序池选择启用…

单文件抽奖小工具(不放回抽)

单文件抽奖小工具(不放回抽) 创建时间:2024-08-12 一、HTML 部分 这段 HTML 代码构建了抽奖小工具的页面结构。引入了 jQuery 库用于后续的 JavaScript 操作,定义了两个音频元素用于播放抽奖相关音效。h1 标签显示“抽奖”标题,span 标签用于显示时间,wrapDiv 包含了抽奖…

【C#】.NET报错:所生成项目的处理器框架“MSIL”与引用“xxx”的处理器架构“AMD64”不匹配

一、现象所生成项目的处理器架构“MSIL”与引用“System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86”的处理器架构“AMD64”不匹配。这种不匹配可能会导致运行时失败。请考虑通过配置管理器更改您的项目的…

算法与数据结构——数据结构的分类

数据结构的分类 常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图,它们可以从“逻辑结构”和“物理结构”两个维度进行分类 逻辑结构:线性与非线性 逻辑结构揭示了数据元素之间的逻辑关系。在数组和链表中,数据按照一定顺序排列,体现了数据之间的线性关系;而在…

Python 实现批量数字二维码生成器

Python 实现批量数字二维码生成器 创建时间:2024-08-09 一、背景 手动逐个生成特定格式和内容的二维码是一项繁琐且耗时的任务。虽然有写二维码工具也可以制作,但是往往有一些限制,为了能够高效、批量生成自定义二维码的需求,开发了这个基于 Python 的数字二维码生成器应用…

【工程应用十一】基于PatchMatch算法的图像修复研究(inpaint)。

这个东西是个非常古老的算法了,大概是2008年的东西,参考资料也有很多,不过基本上都是重复的。最近受一个朋友的需求,前后大概用了二十多天时间去研究,也有所成果,在这里简单的予以记录。图像修复这个东西目前流行的基本都是用深度学习来弄了,而且深度学习的效果还是非常…

jmeter基本操作

发送一个post请求 1、创建一个线程2、新建一个http请求编辑http请求的内容接口断言: 响应参数:{"code":"200","msg":"登录成功!","model":{}}查看结果:保存,运行 a、保存:b、运行红色表示错误 绿色表示成功 查看请求后…

Blazor开发框架Known-V2.0.9

V2.0.9 Known是基于Blazor的企业级快速开发框架,低代码,跨平台,开箱即用,一处代码,多处运行。本次版本主要是修复一些BUG和表格页面功能增强。官网:http://known.pumantech.com Gitee: https://gitee.com/known/Known Github:https://github.com/known/Known概述基于C#…

antd5版本修改Table组件滚动条样式

因为项目需求,要将Table组件的样式修改为UI图所给的效果,但是怎么写都不生效 最后发现在 .ant-table的样式中设了scrollbar-color,只要把这里的样式设为scrollbar-color: auto; 然后再改.ant-table-body里面滚动条的样式,就可以了.ant-table-body{&::-webkit-scrollbar…

ThinkPHP6同步千万级流水数据

ThinkPHP6定时任务同步千万级流水数据 多数据源配置 自定义指令 定时同步单次1000条 <?php declare (strict_types = 1);namespace app\command\SyncDtaTask;use think\console\Command; use think\console\Input; use think\console\Output; use think\Exception; use thi…

AtCoder Beginner Contest 047

A - Fighting over Candies 简单排序。 #include <bits/stdc++.h>using namespace std; using i64 = long long;int main() {ios::sync_with_stdio(false), cin.tie(nullptr);vector<int> a(3);cin >> a[0] >> a[1] >> a[2];sort(a.begin(), a.e…

CRTP 和静态多态

c++古典面试问题之一:面向对象编程三大特性--封装,继承,多态 c++古典面试问题之二:如何实现多态-- 当基类指针指向派生类对象,并通过这个指针调用在派生类中被重写的虚函数基于上述知识点,今天我们讲下另一种多态实现方式:CRTP (curiously recurring template pattern)虚…