强连通分量

news2024/10/5 22:12:36

强连通分量

强连通定义

有向图 G G G 的强连通是指 G G G 中任意两个节点都可以直接或间接到达。

下方两幅图都是强连通。一个特殊一点,任意两点都可以直接到达;一个则是最常见的强连通图。

  • 特殊强连通图,任意两点都可以直接到达

  • 常见的强连通图,即一个环

强连通分量

强连通分量,简称 S C C SCC SCC,是在一个有向图中最大的强连通子图。

  • 图中加粗的点组成的子图即为此图的强连通分量

dfs 生成树的边

dfs 遍历过程就不多说了,这是图论基本能力。

dfs 深搜后,会出现四种不同情况的边,如下:

  • 树边:由 dfs 自然搜索到的边,组成一棵树(不一定是最小/大生成树)。
  • 返祖边(回边):由一个节点指向前面已经遍历过的祖先节点的边。
  • 横叉边:指向了一个访问过但不是当前节点的祖先的边。
  • 前向边:指向了目前未遍历到的节点,但以后会遍历到的节点的边。

例如,下图即为一张图 G G G

不难看出,dfs 生成树长这样:

对比一下,如下:

黑边为树边,是正常深搜而来的。

红边即为返祖边,因为它指向了当前节点 7 7 7 的祖先。

蓝边即为横叉边,因为它指向了当前生成树的另一个节点,但不是当前节点 9 9 9 的祖先。

绿边即为前向边,指向了还未加入生成树的节点。

Tarjan 算法

Tarjan 算法是用来求解 S C C SCC SCC 的著名算法,可以在线性时间复杂度完成统计 S C C SCC SCC 的任务。

思路

若节点 u u u S C C SCC SCC 在搜索树中访问到的第一个节点,那么 S C C SCC SCC 就肯定是一个以 u u u 为根节点的子树,我们称 u u u 为这个 S C C SCC SCC 的根。

Tarjan 算法基本思路为把每个 S C C SCC SCC 都看作搜索树的一个子树,将其节点一个个保存。

对于两个节点 u u u v v v,若 u u u v v v 的祖先,且 v v v 有一条返祖边能指向 u u u,则 u u u v v v 形成了环,属于一个 S C C SCC SCC。从 u u u v v v 一路上遇到的所有点也属于这一 S C C SCC SCC 中的点,边也为 S C C SCC SCC 中边。

步骤

每次遍历到一个节点 u u u,需要统计一下信息:

  • dfn[u],即 u u u 的时间戳(第几个被访问到的)。
  • low[u] u u u 属于的那个 S C C SCC SCCdfn 最小的时间戳。

初始化时,dfn[u]=low[u]=++tottot 为时间戳。

既然 dfs 是一种递归的算法,不妨用栈来存节点信息。

每次搜到一个节点都将其入栈,有出度则沿着出度遍历。

上文说到,dfs 搜索会搜到 4 4 4 种边,那么我们该如何解决 4 4 4 种边呢?

  • 树边:正常搜
  • 返祖边:更新当前的 low
  • 横叉边:无视,没用
  • 前向边:无视,没用

每次搜完子树都需要更新 u u ulow 值,若 low[u]==dfn[u],则 u u u 为这个 S C C SCC SCC 的根节点,因为没有比他时间戳更小的了(回溯完之后)。

例题:福州一中OJ P2110 求有向图的强连通分量

AC Code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,MAXM=8e5+5;
struct EDGE{
	int to,pre;
}edge[MAXM<<1];
int head[MAXN],cnt_edge,tot,t;
int n,m,op;
//链式前向星存图 
void add(int from,int to)
{
	edge[++cnt_edge].to=to;
	edge[cnt_edge].pre=head[from];
	head[from]=cnt_edge;
	return;
}
int dfn[MAXN],low[MAXN];
stack<int> st;
bool vis[MAXN];
int cnt_ans,cnt_t,maxn;
void dfs(int u)//目标是统计maxn 
{
	dfn[u]=low[u]=++tot;//时间戳和子树最小时间戳 
	st.push(u);
	vis[u]=true;
	for(int i=head[u];i;i=edge[i].pre)
	{
		if(!dfn[edge[i].to])
		{
			dfs(edge[i].to);
			low[u]=min(low[u],low[edge[i].to]);//更新 
		}
		else
			if(vis[edge[i].to])//返祖边,注意是vis,不是dfn(有可能是横叉边) 
				low[u]=min(low[u],dfn[edge[i].to]);
	}
	if(low[u]==dfn[u])//是SCC的根节点 
	{
		cnt_t=0;//统计SCC节点个数 
		do{//记得是先做在判断 
			vis[t=st.top()]=false;//比u后入栈的都是SCC的子节点 
			st.pop();
			cnt_t++;
		}while(u!=t);
		maxn=max(maxn,cnt_t);
	}
	return;
}
void dfs2(int u)//与dfs大同小异,目标是计算有多少个强连通子图 
{
	dfn[u]=low[u]=++tot;
	st.push(u);
	vis[u]=true;
	for(int i=head[u];i;i=edge[i].pre)
	{
		if(!dfn[edge[i].to])
		{
			dfs2(edge[i].to);
			low[u]=min(low[u],low[edge[i].to]);
		}
		else
			if(vis[edge[i].to])
				low[u]=min(low[u],dfn[edge[i].to]);
	}
	if(low[u]==dfn[u])
	{
		cnt_t=0;
		do{
			vis[t=st.top()]=false;
			st.pop();
			cnt_t++;
		}while(u!=t);
		if(cnt_t==maxn)
			cnt_ans++;
	}
	return;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&op);
		while(op--)
		{
			scanf("%d",&t);
			add(i,t);
		}
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			dfs(i);
	//初始化 
	while(!st.empty())
		st.pop();
	for(int i=1;i<=n;i++)
	{
		dfn[i]=0;
		low[i]=0;
		vis[i]=false;
	}
	//初始化 
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			dfs2(i);
	printf("%d %d\n",maxn,cnt_ans);
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

7.7、指针和函数

代码 #include <iostream> using namespace std;//实现两个数字进行交换 void swap01(int a, int b) {int temp a;a b;b temp;cout << "swap01a " << a << endl;cout << "swap01b " << b << endl; }void sw…

Docker精华篇 - 常用命令大全,入门到精通!

大家好,我是CodeQi! 我们都知道 Docker 的重要性,以及 Docker 如何在软件开发生命周期中发挥重要作用 。 说实话,学习 Docker 很有趣,至少在我看来是这样。 一旦掌握了基础知识,这并不难。 困难的是记住所有这些命令。 因此,在这篇文章中,我收集了所有命令,或者更…

一图胜千言|用Python搞定统计结果展示!

分享一份原创Python可视化教程&#xff1a;530张图形8000行代码&#xff0c;轻松搞定统计结果展示&#xff0c;部分如下&#xff0c; 每类图表包含详细代码详细代码注释&#xff0c;多达8000行代码&#xff0c;例如&#xff0c; 如何加入学习&#xff1f; &#x1f447;&#…

视频监控汇聚和融合平台的特点、功能、接入方式、应用场景

目录 一、产品概述 二、主要特点 1、多协议支持 2、高度集成与兼容性 3、高性能与可扩展性 4、智能化分析 5、安全可靠 三、功能概述 1. 视频接入与汇聚 2. 视频存储与回放 3. 实时监控与预警 4. 信息共享与联动 5. 远程管理与控制 四、接入方式 1、直接接入 2…

QStringListModel 绑定到QListView

1.QStringListModel 绑定到listView&#xff0c;从而实现MV模型视图 2.通过QStringListModel的新增、删除、插入、上下移动&#xff0c;listView来展示出来 3.下移动一行&#xff0c;传入curRow2 的个人理解 布局 .h声明 private:QStringList m_strList;QStringListModel *m_m…

Sping源码(九)—— Bean的初始化(非懒加载)—mergeBeanDefinitionPostProcessor

序言 前几篇文章详细介绍了Spring中实例化Bean的各种方式&#xff0c;其中包括采用FactoryBean的方式创建对象、使用反射创建对象、自定义BeanFactoryPostProcessor以及构造器方式创建对象。 创建对象 这里再来简单回顾一下对象的创建&#xff0c;不知道大家有没有这样一个疑…

【C语言】顺序表经典算法

本文介绍的是两道顺序表经典算法题目。 移除元素 &#xff08;来源&#xff1a;LeetCode&#xff09; 题目 分析 我们很容易想到的办法是去申请一个新的数组&#xff0c;遍历原数组不等于val就把它拿到新数组里。但是题目的要求是不使用额外空间&#xff0c;所以这种方法我们…

Vue进阶(四十五)Jest集成指南

文章目录 一、前言二、环境检测三、集成问题汇总四、拓展阅读 一、前言 在前期博文《Vue进阶&#xff08;八十八&#xff09;Jest》中&#xff0c;讲解了Jest基本用法及应用示例。一切顺利的话&#xff0c;按照文档集成应用即可&#xff0c;但是集成过程中遇到的问题可能五花八…

【微机原理及接口技术】中断控制器8259A

【微机原理及接口技术】中断控制器8259A 文章目录 【微机原理及接口技术】中断控制器8259A前言一、介绍二、8259A的内部结构和引脚三、8259A的中断工作过程四、8259A的工作方式五、8259A的编程六、外部中断服务程序总结 前言 本篇文章将就8259芯片展开介绍&#xff0c;8259A的…

Chapter10 高级纹理——Shader入门精要学习笔记

Chapter10 高级纹理 一、立方体纹理1.基本概念①组成②采样 2.天空盒子 Sky Box3.环境映射三种方法①特殊布局的纹理创建②手动创建Cubemap——老方法③脚本生成 4.反射5.折射6.菲涅尔反射 二、渲染1.镜子效果2.玻璃效果3.渲染纹理 vs GrabPass 三、程序纹理1.简单程序纹理2.Un…

mov文件怎么转换成mp4格式?这四种转换方法超级好用!

mov文件怎么转换成mp4格式&#xff1f;在数字娱乐的世界中&#xff0c;你是否曾遇到过MOV格式的视频&#xff1f;也许&#xff0c;对于许多人来说&#xff0c;这并不是一个常见的格式&#xff0c;但这并非偶然&#xff0c;首先&#xff0c;我们来谈谈MOV的兼容性问题&#xff0…

读书笔记-《Spring技术内幕》(三)MVC与Web环境

前面我们学习了 Spring 最核心的 IoC 与 AOP 模块&#xff08;读书笔记-《Spring技术内幕》&#xff08;一&#xff09;IoC容器的实现、读书笔记-《Spring技术内幕》&#xff08;二&#xff09;AOP的实现&#xff09;&#xff0c;接下来继续学习 MVC&#xff0c;其同样也是经典…

Linux动态库的制作

Linux操作系统支持的函数库分为&#xff1a; 静态库&#xff0c;libxxx.a&#xff0c;在编译时就将库编译进可执行程序中。 优点&#xff1a;程序的运行环境中不需要外部的函数库。 缺点&#xff1a;可执行程序大 动态库&#xff0c;又称共享库&#xff0c;libxxx.so&#…

Linux之进程控制(下)

目录 进程替换的概念 进程替换的函数 execl​编辑 execlp execle execv execvp execve 上期&#xff0c;我们学习了进程创建&#xff0c;进程终止和进程等待&#xff0c;今天我们要学习的是进程控制中相对重要的板块------进程替换。 进程替换的概念 在进程创建时&…

中国经济昆虫志(55卷)

中国经济昆虫志&#xff0c;共55卷&#xff0c;内容包括概述、形态特征、分类等。各级分类单元均编有检索表&#xff0c;每个种有特征描述、地理分布&#xff0c;有的还记载有生活习性和防治方法。为便于鉴定&#xff0c;绘制有特征图和彩色图。 包括鞘翅目天牛科、半翅目蝽科、…

C - Tile Distance 2

分析&#xff1a;每穿过一行就会加一 先纵向走&#xff0c;再横向走 统一用砖头的左半部分计算 #include<bits/stdc.h> using namespace std; typedef long long ll; int main(){ ll sx,sy,tx,ty;cin>>sx>>sy>>tx>>ty; if((sxsy)%2!0)…

使用CubeIDE调试项目现stm32 no source available for “main() at 0x800337c:

使用CubeIDE调试项目现stm32 no source available for "main() at 0x800337c&#xff1a; 问题描述 使用CubeIDE编译工程代码和下载都没有任何问题&#xff0c;点击Debug调试工程时&#xff0c;出现stm32 no source available for "main() at 0x800337c 原因分析&a…

【C++】#1

关键字&#xff1a; 基本框架、多个main执行、快捷键、cout规则 基本框架&#xff1a; #include <iostream> using namespace std;int main() {//具体内容return 0; } 多个main函数可执行&#xff1a; 常用快捷键&#xff1a; cout规则&#xff1a;

小米MIX Fold 4折叠屏手机背面渲染图曝光

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 7 月 3 日消息&#xff0c;消息源 Evan Blass 今天在 X 平台发布推文&#xff0c;分享了小米 MIX Fold 4 折叠屏手机的高清渲染图&#xff08;图片有加工成分在&#xff0c;最终零售版本可能会存在差异…

Cypress测试:7个快速解决问题的调试技巧!

以下为作者观点&#xff1a; 快速编写代码是一项宝贵的技能&#xff0c;但能够有效调试和解决错误和bug&#xff0c;更是一个软件开发人员具有熟练技能的标志。调试是开发过程中的一个关键环节&#xff0c;可以确保软件按预期运行并满足用户需求。 Cypress 调试简介 Cypress …