首页 > 其他分享 >负环问题

负环问题

时间:2023-08-11 21:33:43浏览次数:25  
标签:问题 int 负环 访问 edge 邻接 顶点

负环问题

负环问题(Negative Cycle Problem)是图论中的一个经典问题,指的是在一个有向图中是否存在一条从某个顶点出发,经过若干条边后回到起点的路径。如果存在这样的路径,那么就称这个图存在负环;否则,称这个图不存在负环。

负环问题的解决方法有很多种,其中最常用的是深度优先搜索(DFS)算法。下面我们将详细介绍负环问题的原理及算法流程,并给出相关的C++代码实现。

原理

负环问题的核心思想是:在遍历图的过程中,记录已经访问过的顶点和路径上的边。当遍历到某个顶点时,如果发现该顶点已经被访问过且路径上存在一条从当前顶点出发经过已访问过的顶点的边,那么就可以判断出存在负环。

为了实现这个思路,我们需要使用一个栈来存储待访问的顶点,以及一个集合来存储已经访问过的顶点。在遍历过程中,我们首先将当前顶点压入栈中,然后将其加入已访问顶点的集合。接下来,我们遍历当前顶点的所有邻接顶点,对于每个邻接顶点,如果它尚未被访问过且可以通过当前顶点到达,那么我们就将它压入栈中。最后,当我们再次遇到已访问过的顶点时,就可以判断出存在负环。

算法流程

  1. 初始化一个空栈stack和一个空集合visited。
  2. 将起始顶点v压入栈stack中。
  3. 将起始顶点v加入已访问顶点的集合visited中。
  4. 当stack非空时,执行以下操作:
    a. 弹出栈顶元素u。
    b. 如果u已经在visited集合中,说明找到了负环,结束算法。
    c. 否则,将u的所有未访问过的邻居顶点压入栈stack中,并将它们加入visited集合中。
  5. 如果算法执行到这里,说明没有找到负环。

下面给出负环问题C++代码实现:

#include<bits/stdc++.h> // 包含常用的C++库
#define reg register // 定义一个宏,表示使用寄存器变量
using namespace std; // 使用标准命名空间

// 读取输入的整数
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1; // 如果输入的是负号,将f设为-1
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48); // 将输入的数字转换为十进制
		ch=getchar();
	}
	return x*f; // 返回结果,如果输入的是负数,返回负值
}

// 输出整数
void write(int x){
	if(x<0){
		putchar('-');
		x=-x; // 如果输入的是负数,先输出负号,再取反
	}
	if(x>9) write(x/10); // 如果输入的数字大于9,递归调用write函数处理十位数和个位数
	putchar(x%10+'0'); // 输出个位数
	return ;
}

const int MAXN=2003,MAXM=3003,inf=INT_MAX; // 定义常量
struct e{ // 定义结构体,表示图的边
	int to,w,nextt; // 边的终点、权重和下一个节点在链表中的位置
};
vector<e > edge; // 存储图的边的向量
int t,n,m,head[MAXN],dis[MAXN],cnt[MAXN]; // 定义图的顶点数、边数、头指针数组、距离数组和计数数组
bool vis[MAXN]; // 定义标记数组,表示是否访问过某个节点

// 初始化图的数据结构
void init(){
	edge.clear(); // 清空边向量
	memset(head,-1,sizeof(head)); // 将头指针数组的所有元素设为-1
	fill(dis+1,dis+n+1,inf); // 将距离数组的所有元素设为无穷大
	memset(vis,0,sizeof(vis)); // 将标记数组的所有元素设为0
	memset(cnt,0,sizeof(cnt)); // 将计数数组的所有元素设为0
	return ;
}

// 在图中添加一条边
void add_edge(int u, int v, int w){
	edge.push_back((e){v,w,head[u]}); // 在边向量中添加一条新的边,起点为u,终点为v,权重为w,下一个节点在头指针数组中的下标为head[u]
	head[u]=edge.size()-1; // 将头指针数组中u对应的元素设为新添加的边的下标
}

// 实现最短路径算法SPFA(Shortest Path Faster Algorithm)
bool spfa(){
	dis[1]=0,vis[1]=1; // 将起点的距离设为0,标记为已访问
	queue<int > q; // 创建一个队列q用于BFS遍历
	q.push(1); // 将起点加入队列q中
	while(!q.empty()){ // 当队列q不为空时循环执行以下操作
		int x=q.front(); // 从队列q的前面取出一个元素作为当前节点x
		q.pop(); // 将当前节点x从队列q中移除
		vis[x]=0; // 将当前节点x的标记设为未访问过的状态
		for(reg int i=head[x];i!=-1;i=edge[i].nextt){ // 遍历当前节点x的所有邻接点y和它们的边z和权重w
			int y=edge[i].to,z=edge[i].w; // 将邻接点y和边的权重w分别赋值给y和w
			if(dis[y]>dis[x]+z){ // 如果从起点到邻接点y的路径长度比从起点到邻接点x的路径长度加上边的权重w更长
				dis[y]=dis[x]+z; // 则更新从起点到邻接点y的路径长度为从起点到邻接点x的路径长度加上边的权重w
				cnt[y]=cnt[x]+1; // 并将从起点到邻接点y的路径上经过的边的数量加一
				if(cnt[y]>=n) return 1; // 如果从起点到邻接点y的路径上经过的边的数量等于n(即所有的顶点都已经访问过),则返回true表示存在负权回路
				if(!vis[y]){ // 如果邻接点y还没有被访问过
					q.push(y); // 则将邻接点y加入队列q中等待访问
					vis[y]=1; // 并将邻接点的标记设为已访问过的状态
				}
			}
		}
	}
	return 0; // 如果没有找到负权回路,则返回false表示不存在负权回路
}

int main(){ // 主要函数开始执行的地方,程序的入口函数
	scanf("%d",&t); // 从标准输入读取一个整数t作为测试用例的数量
	edge.push_back((e){0,0,0});
	while(t--){
		scanf("%d%d",&n,&m);
		init();
		for(reg int i=1;i<=m;i++){
//			int u=read(),v=read(),w=read();
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			add_edge(u,v,w);
			if(w>=0)	add_edge(v,u,w);
		}
		if(spfa())	printf("YES\n");
		else	printf("NO\n");
	}
	return 0;
}

例题及题解

例题:给定一个有向图的邻接表表示,判断该图是否存在负环。

标签:问题,int,负环,访问,edge,邻接,顶点
From: https://www.cnblogs.com/Nebulary/p/17623979.html

相关文章

  • 19.17RU安装问题汇总
    问题概述19.17RU安装问题汇总一、lib库被其他用户使用二、CRS-1159:Theclustercannotbesettorollingpatchmode三、NoreadorwritepermissiontoORACLE_HOME/.patch_storage四、Datapatch:couldn'topenencmapgbk.enc五、CRS-6706:OracleClusterwareReleasepatch......
  • 关于C语言输入输出的逗号问题(小细节)
    C语言的输入输出必须要遵循scanf和printf的格式,就是你是什么格式你就要输入什么。一、输入问题#include<stdio.h>intmain(){ inta,b;scanf("%d,%d",&a,&b); printf("a+b=%d",a+b);return0;}编辑 这个程序我们可以看到它运行的结果是错误的!为什么呢,因为我们在s......
  • 如何解决物流投诉问题,拥有更多的回头客?
    在电商物流中,客户投诉比较多的一块通常是配送延迟或派送问题。以下是一些可能导致此类问题的原因以及解决方法:配送员数量不足或调度不合理:电商企业可能面临配送员不足的情况,导致派送时间延长或出现派送失败等问题。解决方法可以是增加配送员数量,与第三方物流合作,或者使用智能调度系......
  • 关于FFmpeg释放 AVFormatContext*解码上下文的一些问题
    关于FFmpeg释放AVFormatContext*解码上下文的一些问题FFmpeg的一些常用函数用途结构体释放解码上下文FFmpeg的一些常用函数用途av_register_all()注册所有组件。avformat_open_input()打开输入视频文件。avformat_find_stream_info()获取视频文件信息。avcodec_find_d......
  • ffmpeg使用avformat_close_input()函数释放结构体时崩溃的问题
    先看一下我调试时,发现程序崩溃的代码位置  //这是我的程序释放流上下文时的操作 if(m_pAvFormatContext) { //释放视频解码器上下文 if(m_iVideoStreamIndex>=0) avcodec_free_context(&m_pVideoDecodeContext);//此处是发生崩溃......
  • python3 定时处理任务的问题?
    作者:27RRRR链接:https://www.zhihu.com/question/30944800/answer/2317117095来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。如果你想在Linux服务器上周期性地执行某个Python脚本,最出名的选择应该是Crontab脚本,但是Crontab具有以下缺点:1.不......
  • 有关成员函数const修饰,对传入的成员属性影响以及返回指针引用的bug问题
    boolcontains(_T&data,bn_ptrt)const 此时传入的成员参数是带有const属性的,但是data是不带const的,通过影响成员参数访问权限,而达到不能修改的目的;BinarySearchTree<_T>&BinarySearchTree<_T>::operator=(constbst_refbst){ if(this!=&bst) { makeEmpty(); ......
  • 汉诺塔问题(递归)
    博客地址:https://www.cnblogs.com/zylyehuo/#_*_coding:utf-8_*_defhanoi(n,a,b,c):ifn>0:hanoi(n-1,a,c,b)print("movingfrom%sto%s"%(a,c))hanoi(n-1,b,a,c)hanoi(5,'A','B�......
  • Docker 问题
    GPGerror:Atleastoneinvalidsignaturewasencountered相关问题及解决方法#挨个试试dockersystemprune--forcedockervolumeprune--forcedockerimageprune-fdockerimageprune-adockercontainerprune-adockersystemprunedockersystemdf......
  • 百度人脸识别授权序列号-设备时间复原问题
    Q:为什么单设备授权序列号失效?A:以下情况都有可能导致序列号失效,请您进行一-排查1.测试序列号过期,请在百度智能云管理后台申请更多测试序列号2.CPU、网卡等硬件损坏导致硬件指纹变更3.已经激活的设备硬件变更4.SDK升级:安卓1.0&2.0版本升级至3.0&4.0&5.0版本会导......