有向图的强连通性
一些定义
1. 强连通图
强连通图是指一个图内任意两点 uuu 和 vvv 能够相互到达的图。若 GGG 中任意两点都可以互相到达,则称图 GGG 是强连通图。
2. 强连通分量
若一个图 GGG 不是强连通图,那么就可以把它分成一些子图,使得这些子图是强连通图,且这些子图不能继续扩大,即不能与其他节点强连通,则称这个子图是 GGG 的一个强连通分量(StronglyConnectedComponentStrongly Connected ComponentStronglyConnectedComponent,SCCSCCSCC)。
求强连通分量的算法
算法一:Kosaraju
为什么这么做我就不讲了(主要讲讲 tarjantarjantarjan),直接写解题步骤吧(逃
- 储存原图 GGG 和反图 rGrGrG;
- 在 GGG 中 dfsdfsdfs 一下,dfsdfsdfs 顺序无关,记录 dfsdfsdfs 回溯的顺序,回溯的顺序代表优先级,优先级递增;
- 根据优先级,从高到低在反图 rGrGrG 中 dfs2dfs2dfs2,每次会得到一个 SCCSCCSCC,记录每个节点属于 SCCSCCSCC 的编号和 SCCSCCSCC 的个数。
代码
代码请参考 oi-wiki
手动狗头
回归正题!
算法二:Tarjan
DFS 生成树
在说 TarjanTarjanTarjan 之前,必须先说说 DFSDFSDFS 生成树。
DFSDFSDFS 生成树有一下几种边:
- 树边 (tree edge):每次搜索访问到一个还没被搜索过的点时就形成了一条树边;
- 反祖边 (back edge):即指向祖先节点的边;
- 横叉边 (cross edge):即一个节点 uuu 在搜索时搜索到一个已经被遍历过的节点 vvv,但该节点 vvv 并非 uuu 的祖先;
- 前向边(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;
}
1356

被折叠的 条评论
为什么被折叠?



