一、定义
图的割点 一个无向连接图中,如果删除某个顶点后,图不再连同(即任意两点之间不能互相到达) ,称这样的顶点为割点 或:某个点是割点当且仅当删除该点和与该点相关联的边后图变得不连通。
图的割边/桥: 一个无向连通图中,如果删除某条边后,图不再连通,这条边就为割边。 或:某条边是割边当且仅当删除该边后图变的不连通。
二.
求割点,桥 使用dfs(深搜)来求割点和桥。先明确一下几点: 1、 图的dfs相当于是对相应的dfs树的遍历。
2、 无向图的dfs树,无论以哪个点为根都可以遍历完所有的点。
3、 无向图的dfs树,没有横叉边(连接两个子树的边)。
对原图进行深度优先搜索,会生成一颗深度优先搜索生成树。定义dfs[u]为u在深度 优先搜索生成树中被遍历到的序号,low[u]为u或者他的子树中可以通过非父子边追 溯到的最早结点。
①一个顶点是割点,满足下列条件之一:
1).u是树根,u有两个或两个以上的分支/u有多于一个子树;
2).u不是树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),(u,v)是树边且low[v]>=dfn[u]。 low(u)=min{dfn(u),low(v) if(u,v)是树枝边/父子边,dfn(v) if(u,v)是后向边/返祖边}
②桥无向边(u,v),当且仅当(u,v)为树枝边,且满足dfn[u]<low[v]。 注:1、为方便程序编写,我们都采用low[v]来判断u。(理论上low[u]也可以判断割点)
三、编程
准备两个数组low和dfn。 Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的Dfn值(很绕嘴,往下看你就会明白)。 dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图(无需回溯)和对栈的操作,我们就可以得到该有向图的强连通分量。
1.数组的初始化:当首次搜索到点p时,dfn与low数组的值都为到该点的时间。
2.堆栈:每搜索到一个点,将它压入栈顶。 3.当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中(是父子边),p的low值为min(low(p),dfn(p’) )。
4.当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中(是后向边),p的low值为min(low(p),low(p’))。
5.每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
6.继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
由于每个顶点只访问过一次,每条边也只访问过一次,我们就可以在O(n+m)的时间内求出有向图的强连通分量。
四、应用 1、一个表示通信网络的图的连通度越高,其系统越可靠,无论是哪一个站点出现故
障或遭到外界破坏,都不影响系统的正常工作; 2、一个航空网若是重连通的,则当某条航线因天气等某种原因关闭时,旅客仍可从别的航线绕道而行;
3、若将大规模的集成电路的关键线路设计成重连通的话,则在某些元件失效的情况下,整个片子的功能不受影响,反之,在战争中,若要摧毁敌方的运输线,仅需破坏其运输网中的关节点即可。
割点模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
using namespace std;
const int N=1000005;
int t,cnt,head[N],son[N],dfn[N],low[N],n,ans,m,root;
bool is_ok[N];
struct Node{
int u,v,next;
}edge[N];
void push(int u,int v){
++cnt;
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void tarjan(int v,int u){
low[v]=dfn[v]=++t;
for(int i=head[v];i!=-1;i=edge[i].next){
++son[v];
int v1=edge[i].v;
if(dfn[v1]==-1){
tarjan(v1,v);
low[v]=min(low[v],low[v1]);
if(v==root&&son[v]>=2){
is_ok[v]=true;
}
else if(v!=root&&low[v1]>=dfn[v]){
is_ok[v]=true;
}
}
else if(v1!=u){
low[v]=min(low[v],dfn[v1]);
}
}
}
int main(){
memset(head,-1,sizeof(head));
memset(dfn,-1,sizeof(dfn));
memset(is_ok,false,sizeof(is_ok));
int u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
push(u,v);
push(v,u);
}
root=u;
tarjan(u,0);
for(int i=1;i<=n;i++){
if(is_ok[i]){
++ans;
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++){
if(is_ok[i]){
printf("%d ",i);
}
}
return 0;
}
桥模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
const int N=10005;
using namespace std;
vector<int>G[N];
int n,m,low[N],dfn[N];
int father[N];
int tim;
void Tarjan(int i,int Father){
father[i]=Father;
dfn[i]=low[i]=tim++;
for(int j=0;j<G[i].size();++j){
int k=G[i][j];
if(dfn[k]==-1){
Tarjan(k,i);
low[i]=min(low[i],low[k]);
}
else if(Father!=k){
low[i]=min(low[i],dfn[k]);
}
}
}
int main(){
int a,b;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
memset(dfn,-1,sizeof(dfn));
memset(father,0,sizeof(father));
memset(low,-1,sizeof(low));
Tarjan(1,0);
for(int i=1;i<=n;++i){
int v=father[i];
if(v>0&&low[i]>dfn[v])
printf("%d %d\n",v,i);
}
return 0;
}