有向图的强连通性

有向图的强连通性

一些定义

1. 强连通图

强连通图是指一个图内任意两点 uuuvvv 能够相互到达的图。若 GGG 中任意两点都可以互相到达,则称图 GGG 是强连通图。

2. 强连通分量

若一个图 GGG 不是强连通图,那么就可以把它分成一些子图,使得这些子图是强连通图,且这些子图不能继续扩大,即不能与其他节点强连通,则称这个子图是 GGG 的一个强连通分量(StronglyConnectedComponentStrongly Connected ComponentStronglyConnectedComponentSCCSCCSCC)。

求强连通分量的算法

算法一:Kosaraju

为什么这么做我就不讲了(主要讲讲 tarjantarjantarjan),直接写解题步骤吧(逃

  1. 储存原图 GGG 和反图 rGrGrG;
  2. GGGdfsdfsdfs 一下,dfsdfsdfs 顺序无关,记录 dfsdfsdfs 回溯的顺序,回溯的顺序代表优先级,优先级递增;
  3. 根据优先级,从高到低在反图 rGrGrGdfs2dfs2dfs2,每次会得到一个 SCCSCCSCC,记录每个节点属于 SCCSCCSCC 的编号和 SCCSCCSCC 的个数。

代码

代码请参考 oi-wiki

手动狗头

回归正题!

算法二:Tarjan

DFS 生成树

在说 TarjanTarjanTarjan 之前,必须先说说 DFSDFSDFS 生成树。

DFSDFSDFS 生成树有一下几种边:

  1. 树边 (tree edge):每次搜索访问到一个还没被搜索过的点时就形成了一条树边;
  2. 反祖边 (back edge):即指向祖先节点的边;
  3. 横叉边 (cross edge):即一个节点 uuu 在搜索时搜索到一个已经被遍历过的节点 vvv,但该节点 vvv 并非 uuu 的祖先;
  4. 前向边(forward edge):是搜索时遇到该节点的自述中的一个节点形成的。
DFS 生成树与强连通分量的关系

如果结点 uuu 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 uuu 为根的子树中。结点 uuu 被称为这个强连通分量的根。

算法概述

Tarjan 算法是由 Robert Tarjan 在 1972 年提出的基于深度优先搜索的算法,主要用于求解有向图的强连通分量 (Strongly Connected Components, SCC)。该算法在线性时间O(V+E)O(V+E)O(V+E)内解决问题,是图论中的经典算法。

Tarjan 算法是一种基于深度优先搜索的线性时间算法,用于寻找有向图的强连通分量。

几个比较重要的量
  • dfnidfn_idfni 记录在 DFS 中 iii 号节点遍历的顺序(dfn 序)
  • lowilow_ilowi 记录 iii 号节点最早能够回溯到的最早的栈中节点的时间戳
  • stackstackstack 在本节点的搜索路径上的节点
  • in_stackiin\_stack_iin_stacki 标记节点 iii 是否在栈中
具体代码实现(SCC计数)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int h[N],e[N],ne[N],idx=1;
int dfn[N],low[N],sccno[N],timestamp;
int stk[N],top;
int n,m,scc_cnt;
void connec(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++timestamp;
	stk[top++]=x;
	for(int i=h[x]; i; i=ne[i])
	{
		int j=e[i];
		if(!dfn[j])
		{
			tarjan(j);
			low[x]=min(low[x],low[j]);
		}
		else if(!sccno[j])
		{
			low[x]=min(low[x],dfn[j]);
		}
	}
	if(dfn[x]==low[x])
	{
		scc_cnt++;
		while(1)
		{
			int tem=stk[--top];
			sccno[tem]=scc_cnt;
			if(x==tem) break;
		}
	}
}
int main(){
	cin>>n>>m;
	timestamp=scc_cnt=top=0;
	idx=1;
	memset(h,0,sizeof(h));
	memset(e,0,sizeof(e));
	memset(ne,0,sizeof(ne));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(sccno,0,sizeof(sccno));
	for(int i=1; i<=m; i++)
	{
		int x,y;
		cin>>x>>y;
		connec(x,y);
	}
	for(int i=1; i<=n; i++)
	{
		if(!dfn[i])
		{
			tarjan(i);
		}
	}
	cout<<scc_cnt;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值