C++图论基础Bellman-Ford与spfa算法如何判断负环

本编标红字段均是值得纳为己用的经验条~


引言:

此前,我们已经学习了Bellman-Ford算法与spfa算法,这两个算法都是可以处理边权有负数的情况,此外,他俩还有个妙用,就是判断图中是否存在负环。

本篇就是介绍他俩怎么判断负环的。

判断负环的代码与他本身的代码都是大差不差的,下文细细品鉴~~


Bellman-Ford算法判断负环:

BF算法是暴力过 n - 1 轮、单趟全遍历边进行的松弛操作,他的最差情况就是至多会进行 n - 1 轮松弛操作,到第 n 轮肯定是松弛不了的,能松弛的就一定存在负环。所以我们就可以用标记变量 bool flag 来特判经过第 n 轮之后的状态,当然这个flag也可以是当作优化的,提前结束,省去不必要的轮。

这个BF算法其实只关心边,所以也可以用一个结构体存边的信息,就不用存图了,这里我们也来练习一下~


OJ题来源:洛谷

OJ题名:【模板】负环

OJ题归属:图论基础【单源最短路】

解题算法:结构体存边信息 + Bellman-Ford算法判断负环

cpp 复制代码
#include<iostream>
#include<cstring>

using namespace std;

const int N = 2e3 + 10, M = 3e3 + 10;

int n, m;
int pos;
struct node
{
	int x, y, z;
}e[M * 2];
int dist[N];

bool bf()
{
	// 初始化
	memset(dist, 0x3f3f3f3f, sizeof dist);
	dist[1] = 0;

	bool flag;
	// 执行 n 轮松弛操作,看最后一轮结束后的结果
	for (int i = 1; i <= n; i++)
	{
		flag = false;
		for (int j = 1; j <= pos; j++)
		{
			int u = e[j].x, v = e[j].y, w = e[j].z;
			if (dist[u] == 0x3f3f3f3f) continue;

			if (dist[u] + w < dist[v])
			{
				dist[v] = dist[u] + w;
				flag = true;
			}
		}

		// 这步是做的优化
		if (flag == false) return flag;
	}

	return flag;
}

int main()
{
	int T; cin >> T;
	while (T--)
	{
		cin >> n >> m;
		pos = 0;
		for (int i = 1; i <= m; i++)
		{
			int u, v, w; cin >> u >> v >> w;
			pos++;
			e[pos].x = u, e[pos].y = v, e[pos].z = w;
			if (w >= 0)
			{
				pos++;
				e[pos].x = v, e[pos].y = u, e[pos].z = w;
			}
		}

		if (bf()) cout << "YES" << endl;
		else cout << "NO" << endl;
	}

	return 0;
}

spfa 算法判断负环:

spfa 算法是用队列优化的BF算法。

存在负环时,while循环会死循环,我们可以计数的方式,既可以判断负环,也可以跳出死循环。

计数的方式--->小动态规划:每个节点配一个 cnt 数组;cnti 表示从起点到 i 位置经过多少条边;cnti > n - 1 即存在负环,cnti <= n - 1 有最短路,没有负环。

状态转移方程:起点 ......................u------v 此时我们来算cntv,它等于刚刚经过的一条边加上在这条边之前所经过的边个数,这个在cntu里面存着,即 cntv = cntu + 1。


cpp 复制代码
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>

using namespace std;

typedef pair<int, int> PII;

const int N = 2e3 + 10;

int n, m;
vector<PII> edges[N];
int dist[N];
bool st[N]; // 标记哪些点在队列里面
int cnt[N];

bool spfa()
{
	// 初始化 --- 既是初始化也是清理数据
	memset(dist, 0x3f, sizeof dist);
	memset(st, 0, sizeof st);
	memset(cnt, 0, sizeof cnt);

	queue<int> q;
	q.push(1);
	dist[1] = 0;
	st[1] = true;
	//cnt[1] = 0;

	while (q.size())
	{
		auto u = q.front(); q.pop();
		st[u] = false;

		for (auto& e : edges[u])
		{
			int v = e.first, w = e.second;
			if (dist[u] + w < dist[v])
			{
				dist[v] = dist[u] + w;
				dist[v] = dist[u] + 1;
				if (dist[v] > n - 1) return true;

				if (!st[v])
				{
					q.push(v);
					st[v] = true;
				}
			}
		}
	}

	return false;
}

int main()
{
	int t; cin >> t;
	while (t--)
	{
		// 清空数据
		for (int i = 1; i <= n; i++) edges[i].clear();

		cin >> n >> m;
		for (int i = 1; i <= m; i++)
		{
			int u, v, w; cin >> u >> v >> w;
			edges[u].push_back({ v, w });
			if (w >= 0) edges[v].push_back({ u, w });
		}

		if (spfa()) cout << "YES" << endl;
		else cout << "NO" << endl;
	}

	return 0;
}
相关推荐
破茧蝴蝶3 小时前
Python分布式爬虫完整教程
开发语言·python
狗凯之家源码网4 小时前
全新 PHP 轻量客服聊天室源码深度评测
开发语言·php·聊天室系统
远离UE44 小时前
UE5 Substrate Thin Film 源码拆析
开发语言·javascript·ue5
keyipatience4 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
hanlin034 小时前
刷题笔记:力扣第32题-最长有效括号
笔记·算法·leetcode
一拳一个呆瓜4 小时前
【STL】文件系统导航
c++·stl
All for pursuit.5 小时前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划
栈帧之外5 小时前
【C++】STL之set
开发语言·c++
catchadmin6 小时前
免费可商用 PHP 管理后台 CatchAdmin V5.4.1 发布,新增 MCP 支持
开发语言·php
xian_wwq6 小时前
【学习笔记】Milvus 如何完成一次向量相似度检索,Milvus 索引到底应该怎么选?-15
笔记·学习·milvus