20250819 强连通分量,边双总结

连通性定义

  • 连通分量:无向图中所有节点相互连通的子图。

  • 弱连通分量:将有向图所有边视为无向边后,所有节点相互连通的子图。

  • 强连通分量:有向图中任意两个节点互相连通的子图。

  • 点双连通分量:连通子图中删除任一节点后,其余节点仍保持连通。

  • 边双连通分量:连通子图中删除任一条边后,其余节点仍保持连通。

强连通分量

一张有向图里,如果一张子图,它包含的节点互相连通并且极大,那么他就是极大强连通子图,也就是强连通分量

举个栗子(强连通分量)

假设有以下图:

1
2
3
4
5
6
7

该图有三个强连通分量,分别为{1,2,3}、{4,5,6}、{7}。注意,不能把7归入到第二个强连通分量,因为7和4、5、6并不互相连通。

DFS生成树(搜索树)

在有向图的DFS生成树中存在四种边类型:

  1. 树边:从父节点指向未被访问的子节点
  2. 返祖边:指向祖先节点的边
  3. 前向边:指向子树中的边
  4. 横叉边:指向已访问过的非祖先非子树节点

需要注意的是,前向边和横叉边仅存在于有向图的DFS树中,无向图中只会有树边和返祖边。

关于DFS生成树与强连通分量的关系:

在一个强连通分量中,若点u是最先被搜索到的节点,则该分量中其余所有节点都必然位于以u为根的子树内。

证明? 反证法

假设强连通分量中存在节点v不在u的子树中。由于u和v互相可达,u到v的路径上必须存在离开子树的边(横叉边或返祖边),这意味着这些边指向的节点已被访问。

但若u和v同属一个强连通分量,这些更早被访问的节点本应能访问到u,这与u是最先被搜索到的假设矛盾,成立。

缩点

找到强连通分量后,可以考虑缩点,也就是把一整个强连通分量压缩成一个点,这样就能使原图变成有向无环图。

可以发现,有向无环图绝不连通,就是不会再有强连通分量。

缩点时要注意原图中的边和新图中的边的对应关系。

举个栗子(缩点)

假设有以下图:

1
2
3
4
5
6
7

该图有三个强连通分量,分别为{1,2,3}、{4,5,6}、{7}。

缩点之后长这样:

1,2,3
4,5,6
7

是不是看起来非常的简洁又明了呢?这就是缩点的益处。

实现

void tarjan(int x){//强连通分量
	dfn[x]=low[x]=++cnt;
	q.push(x);
	for(int i=0;i<E[x].size();i++){
		int v=E[x][i];
		if(dfn[v]==0){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}else if(!gp[v]){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]){
		gp[x]=++t;
		ts[t].push_back(x);
		while(q.top()!=x){
			gp[q.top()]=t;
			ts[t].push_back(q.top());
			q.pop();
		}
		q.pop();
	}
}
void tarjan(int x){//缩点
	dfn[x]=low[x]=++cnt;
	s.push(x);
	for(int i=0;i<E[x].size();i++){
		int v=E[x][i];
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}else if(!gp[v]){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]){
		k++;
		while(s.top()!=x){
			gps[k]+=D[s.top()];
			gp[s.top()]=k;
			s.pop();
		}
		gp[x]=k;
		dp[k]=gps[k]+=D[x];
		s.pop();			
	}		
}
int main(){
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			tarjan(i);
		} 
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<E[i].size();j++){
			int v=E[i][j];
			if(gp[i]!=gp[v]){
				e[gp[i]].push_back(gp[v]);
				rd[gp[v]]++;
			}
		}
	}
	return 0;
}

边双连通分量

将强连通分量中的有向边改为无向边,即可得到边双连通分量

这是因为在强连通分量中,任意两点u和v都是相互可达的。当我们将有向边替换为无向边后,任意两点之间必然存在至少两条不同的路径。

需要注意的是,不要将重边合并,否则上述结论将不再成立。

实现

void dfs(int x,int fa){
	dfn[x]=low[x]=++cnt;
	s.push(x);
	for(auto v:E[x]){
		if(v.second==(fa^1))continue;
		if(!dfn[v.first]){
			dfs(v.first,v.second);
			low[x]=min(low[x],low[v.first]);
		}else{
			low[x]=min(low[x],dfn[v.first]);
		}
	}
	if(low[x]==dfn[x]){
		num++;
		while(s.top()!=x){
			ans[num].push_back(s.top());
			s.pop();					
		}
		ans[num].push_back(x);
		s.pop();
	}
}
int main(){
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			dfs(i,0);
		}
	}
	cout<<num<<endl;
	for(int i=1;i<=num;i++){
		cout<<ans[i].size();
		for(int j=0;j<ans[i].size();j++){
			cout<<" "<<ans[i][j];
		}
		cout<<endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值