强连通分量

求强连通分量


相关概念

强连通:在有向图 \(D\) 中,\(D\) 的任意两个节点连通

强连通分量:一张平凡图上的极大强连通子图

DFS 生成树:以图上某节点通过 DFS 访问的方式,与可达节点形成的树结构,依据树结构图上的边主要分为四类

树边:在 DFS 生成树上连接两个节点的边,在图上为每次搜索找到一个还没有访问过的结点时通过的一条边

返祖边:图上的一条边,他的始点在 DFS 生成树上的祖先是这条边的终点

横叉边:图上的一条边,他的始点在 DFS 生成树上的祖先不是这条边的终点,但终点在之前已经被访问过

前向边:图上的一条边,他的始点在 DFS 生成树上的儿子是这条边的终点

返祖边与树边一定能构成环,横叉边与树边可能构成环

强连通分量的根:某强连通分量在 DFS 生成树中遇到的第一个节点,该强连通分量的其余节点一定也在这个节点为根的子树中

Tarjan算法

对每个节点 \(x\) 都维护出它在 DFS 搜索树上的时间戳 \(dfn_x\)(DFS 过程中被搜索到的次序),以及 \(x\) 能访问到的最早时间戳 \(low_x\) (追溯值)

vector<int> e[N];
vector<int> stk;//模拟栈
int dfn[N],low[N],tot;
int scc[N],siz[N],cnt;
bool instk[N];

void tarjan(int x){
	dfn[x]=low[x]=++tot;
	stk.push_back(x),instk[x]=1;
	for (auto v:e[x]){
		if (!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}else if (instk[v])
			low[x]=min(low[x],dfn[v]);
	}
	
	if (low[x]==dfn[x]){
		int t;
		cnt++;
		do{
			t=stk.back();
			stk.pop_back();
			instk[t]=0;
			scc[t]=cnt;
			siz[cnt]++;
		}while (t!=x);
	}
}

每进入一个节点时,初始化节点的 \(dfn_x,low_x\) 为当前的次序号 \(tot\),并将这个节点放进栈中,标记节点已入栈

枚举当前节点的邻边,根据不同类型进行操作

for (auto v:e[x]){
	if (!dfn[v]){//如果邻点还没有被访问到,则该边为树边
		tarjan(v);//dfs访问v
		low[x]=min(low[x],low[v]);//返回更新当前x的追溯值
	}else if (instk[v])//如果v被访问,且在栈内,则该边为横叉边或返祖边
		low[x]=min(low[x],dfn[v]);//返回更新当前x的追溯值
}

如果一个节点在遍历完它的所有儿子后,\(dfn_x\) 没有变小那么说明他就是一个强连通分量的根(在他可连通的节点中不存在能够到达之前访问过的节点),依次取出栈内元素,直到栈顶元素为 \(x\)

if (low[x]==dfn[x]){
	int t;
	cnt++;//强连通分量数+1
	do{
		t=stk[top--];//取出栈顶元素
		instk[t]=0;//标记出栈
		scc[t]=cnt;//更新栈顶元素所在的强连通分量
		siz[cnt]++;//记录该强连通分量拥有的节点数
	}while (t!=x);
}

核心思想是通过 \(low_x\) 判断是否能回到 \(DFS\) 路径的起点,从而切割出 \(scc\)

Kosaraju算法

第一遍 DFS 处理出每个节点的时间戳,回溯时将时间戳较大的节点放进栈内(强连通分量的根一定是最后完成的)

根据这样一个结论:将一个强连通分量中所有边反向后,形成的仍然为一个强连通分量

从栈顶依次取元素,如果该点未处于一个强连通分量中,那对该节点进行第二次 DFS,在逆图中枚举他能够到达的所有点,这些点集构成一个极大强连通分量

每次取出的元素,都作为他所在强连通分量的根

int n,m;
vector<int> e[N],re[N];
vector<int> stk;
bool vis[N];
int scc[N],siz[N],cnt;

void dfs1(int x){
	if (vis[x]) return;
	vis[x]=1;
	for (auto v:e[x])
		dfs1(v);
	stk.push_back(x);
}

void dfs2(int x){
	if (scc[x]) return;
	scc[x]=cnt;
	siz[cnt]++;
	for (auto v:re[x])
		dfs2(v);
}

void kosaraju(){
	for (int i=1;i<=n;i++)
		if (!vis[i])
			dfs1(i);
	for (int i=stk.size()-1;i>=0;i--){
		if (!scc[stk[i]]){
			++cnt;
			dfs2(stk[i]);
		}
	}
}

核心思想在于划分一个强连通分量前,封锁他与外界连通的道路

posted @ 2025-05-21 19:23  才瓯  阅读(20)  评论(0)    收藏  举报