无向图连通性相关算法及概念
written on 2022-08-03
之前第一次讲的时候没有掌握得很好,这里再写一篇博客总结一下。
(注:以下均为通俗不专业定义,具体定义可以百度百科)
Part 1
先来讨论一下无向图的割点与桥。
割点:在一张无向图中,若对于点
桥:又称割边。在一张无向图中,若对于边
伟大的计算机科学学者 Tarjan 提出了割边判定法则与割点判定法则。 下面来介绍一下相关算法。
- 搜索树
对于一个无向连通图,从其中任选一个点作为根节点出发对整张图进行深度优先遍历,每个点只访问一次,此时所有发生递归的边构成了一棵树,那么称这棵树为原无向连通图的搜索树。对于一般的无向图,它将会构成一片搜索森林。
很明显搜索树不止一种形态。代码实现可以用
- 追溯值(
数组)
在图的连通性若干问题中,这是区别与图论其他分支的一个最特殊的地方。
追溯值
-
的子树中的节点。 -
通过恰好一条非树边能够到达
的子树中的节点。
代码实现中
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else low[x]=min(low[x],dfn[y]);
}
}
- 割边判定法则/割点判定法则
无向边
是桥,当且仅当搜索树上存在 的一个子节点 ,满足 。 若
不是搜索树的根节点,则 是割点当且仅当搜索树上存在 的一个子节点 ,满足 ;反之若 是搜索树的根节点,则 是割点当且仅当搜索树上存在至少两个节点 满足上述条件。
完全是可以感性理解的。另外,桥一定是树边。
- 判定割边时需要注意的成对变换问题
求割边时,需要判断
void tarjan(int x,int in_edge)
{
...
for(...)
{
if(!dfn[y]) ...
else if((i^1)!=in_edge) low[x]=min(low[x],dfn[y]);
}
}
由于割点的判定是取等的,所以不用成对变换也没关系。
- 边双/点双连通分量
先给出定义:
边双连通分量:对于一个无向图,若其去掉任意一条边都不会改变此图的连通性,即不存在桥,则称为边双连通图。一个无向图中的每一个极大边双连通子图称作此无向图的边双连通分量。
点双连通分量:若一个无向图中的去掉任意一个节点都不会改变此图的连通性,即不存在割点,则称作点双连通图。一个无向图中的每一个极大点双连通子图称作此无向图的点双连通分量。
(节选自百度百科,这个定义还是比较好的。)
三点注意:
-
连接某两个边双连通分量的边即是桥。
-
一个割点可能存在于多个点双连通分量。
-
一个点双连通分量内可能存在任意多个割点(也可能没有)。
然后介绍一下二者的缩点:
对于边双的缩点,很显然可以直接找出所有的桥,然后用
void dfs(int x)
{
dcc[x]=dc;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(dcc[y]||bri[i]) continue;
dfs(y);
}
}
int main()
{
...
for(int i=1;i<=n;i++) if(!dcc[i]) ++dc,dfs(i);
}
点双的缩点就并不如边双那样直接了。由于一个割点可能存在于多个点双内,所以我们需要换一种方法,将割点单独视为一个点。这里直接给出代码,证明略(其实是因为我太菜了) 。
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
sta[++top]=x;
if(head[x]==0)
{
dcc[x]=++dc,v[dc].push_back(x);
return ;
}
int cnt=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
if(low[y]>=dfn[x])
{
++cnt;
if(x!=rt||cnt>=2) cut[x]=1;
++dc;
while(1)
{
int z=sta[top--];
dcc[z]=dc,v[dc].push_back(z);
if(z==y) break;
}
dcc[x]=dc,v[dc].push_back(x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int main()
{
...
int cur=dc;
for(int i=1;i<=n;i++) if(cut[i]) dcc[i]=++cur;
for(int i=1;i<=dc;i++)
{
for(int j=0;j<(int)v[i].size();i++)
{
int x=v[i][j];
if(cut[x]) add_E(dcc[x],i),add_E(i,dcc[x]);
}
}
for(int i=1;i<=m;i++)
{
int x=E[i].x,y=E[i].y;
if(dcc[x]==dcc[y]) continue;
add_E(dcc[x],dcc[y]),add_E(dcc[y],dcc[x]);
}
}
性质:边双与点双缩完点后均会形成一棵树。
有时题目为了提升难度往往会在缩完点后的这棵树上做文章。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!