每周一算法:负环判断

2024-04-19 16:52
文章标签 算法 判断 每周 负环

本文主要是介绍每周一算法:负环判断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目链接

负环

题目描述

给定一个 n n n 个点的有向图,请求出图中是否存在从顶点 1 1 1 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

输入格式

本题单测试点有多组测试数据

输入的第一行是一个整数 T T T,表示测试数据的组数。对于每组数据的格式如下:

第一行有两个整数,分别表示图的点数 n n n 和接下来给出边信息的条数 m m m

接下来 m m m 行,每行三个整数 u , v , w u, v, w u,v,w

  • w ≥ 0 w \geq 0 w0,则表示存在一条从 u u u v v v 边权为 w w w 的边,还存在一条从 v v v u u u 边权为 w w w 的边。
  • w < 0 w < 0 w<0,则只表示存在一条从 u u u v v v 边权为 w w w 的边。

输出格式

对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

样例 #1

样例输入 #1

2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8

样例输出 #1

NO
YES

提示

数据规模与约定

对于全部的测试点,保证:

  • 1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1n2×103 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1m3×103
  • 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1u,vn − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 104w104
  • 1 ≤ T ≤ 10 1 \leq T \leq 10 1T10
提示

请注意, m m m 不是图的边数。

算法思想

判断图中是否存在负环,需要先了解下面关于最短路的几个性质:

  • 对于边权为正的图,任意两个节点之间的最短路,不会经过重复的节点。
  • 对于边权为正的图,任意两个节点之间的最短路,不会经过重复的边。
  • 对于边权为正的图,任意两个节点之间的最短路,任意一条的节点数不会超过 n n n,边数不会超过 n − 1 n-1 n1

Bellman–Ford 算法

Bellman–Ford 算法是一种基于松弛(relax)操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断。大名鼎鼎的「SPFA」,就是 Bellman–Ford算法的一种实现。

基本思想

Bellman–Ford算法所做的,就是不断尝试对图上每一条边进行松弛。每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。

对于边 ( u , v ) (u,v) (u,v),Bellman–Ford算法中松弛操作对应下面的式子: d i s ( v ) = min ⁡ ( d i s ( v ) , d i s ( u ) + w ( u , v ) ) dis(v) = \min(dis(v), dis(u) + w(u, v)) dis(v)=min(dis(v),dis(u)+w(u,v))。尝试用 S → u → v S \to u \to v Suv(其中 S → u S \to u Su 的路径取最短路)这条路径去更新 v v v 点最短路的长度,如果这条路径更优,就进行更新。

每次循环的时间复杂度是 O ( m ) O(m) O(m),那么最多会循环多少次呢?

在最短路存在的情况下,由于一次松弛操作会使最短路的边数至少 + 1 +1 +1,而最短路的边数最多为 n − 1 n-1 n1,因此整个算法最多执行 n − 1 n-1 n1 轮松弛操作。故总时间复杂度为 O ( n m ) O(nm) O(nm)

但还有一种情况,如果从 S S S 点出发,抵达一个负环时,松弛操作会无休止地进行下去。对于最短路存在的图,松弛操作最多只会执行 n − 1 n-1 n1 轮,因此如果第 n n n 轮循环时仍然存在能松弛的边,说明从 S S S 点出发,能够抵达一个负环。

代码实现

struct Edge {int u, v, w;
};vector<Edge> edge;int dis[MAXN], u, v, w;
const int INF = 0x3f3f3f3f;
//节点数n,起点s
bool bellmanford(int n, int s) {memset(dis, 0x3f, sizeof(dis));dis[s] = 0;bool flag = false;  // 判断一轮循环过程中是否发生松弛操作for (int i = 1; i <= n; i++) {flag = false;for (int j = 0; j < edge.size(); j++) {u = edge[j].u, v = edge[j].v, w = edge[j].w;if (dis[u] == INF) continue;// 无穷大与常数加减仍然为无穷大// 因此最短路长度为 INF 的点引出的边不可能发生松弛操作if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;flag = true;}}// 没有可以松弛的边时就停止算法if (!flag) {break;}}// 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环return flag;
}

队列优化的Bellman–Ford

SPFA即 Shortest Path Faster Algorithm,即队列优化的Bellman–Ford。很多时候Bellman–Ford算法并不需要那么多无用的松弛操作, 只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。那么可以用队列来维护哪些结点可能会引起松弛操作,就能只访问必要的边了。

SPFA也可以用于判断 s s s点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少 n n n条边时,说明 s s s点可以抵达一个负环。

代码实现

struct edge {int v, w;
};vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;bool spfa(int n, int s) {memset(dis, 0x3f, sizeof(dis));dis[s] = 0, vis[s] = 1;q.push(s);while (!q.empty()) {int u = q.front();q.pop(), vis[u] = 0;for (auto ed : e[u]) {int v = ed.v, w = ed.w;if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数if (cnt[v] >= n) return false;// 在不经过负环的情况下,最短路至多经过 n - 1 条边// 因此如果经过了多于 n 条边,一定说明经过了负环if (!vis[v]) q.push(v), vis[v] = 1;}}}return true;
}

虽然在大多数情况下SPFA跑得很快,但其最坏情况下的时间复杂度为 O ( n m ) O(nm) O(nm),将其卡到这个复杂度也是不难的,所以考试时要谨慎使用。在没有负权边时最好使用Dijkstra算法,在有负权边且题目中的图没有特殊性质时,若SPFA是标算的一部分,题目不应当给出Bellman–Ford算法无法通过的数据范围。

这篇关于每周一算法:负环判断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/918110

相关文章

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11