1 无向图的连通性
如果无向图G=<E,V>中任意两点u、v间存在通路,即u和v连通,则称G为连通图。
容易发现,无向图G顶点间的连通关系构成了一个V上的等价关系。我们可以依照这一等价关系对图G进行划分。不妨设U是V关于顶点间连通关系构成的一个等价类,称导出子图G[U]为G的一个连通分支。
我们可以利用dfs或者并查集来判断连通分支。
1.1 割点
对于无向图G,在多数情况下,我们可以通过删掉一部分点使得原本连通的部分变得不连通。设U是V的子集,如果删掉U可以使G的连通分支数增加,并且仅删去U的任何子集都不能使G的连通分支数增加,则称U为G的一个点割集。特别的,若U={u},我们称u为G的一个割点。
接下来讨论如何求出无向图的割点。
很容易想出一种暴力的做法,即枚举无向图G中的每个点u,删去u以及与u相邻的边,然后用dfs判断G的连通分支数是否增多。效率O(n*(n+m))。
如何更高效地解决这一问题?下面介绍效率为O(n+m)的Tarjan算法。
容易发现,当我们对无向图G中的一个连通分量做dfs时,按照遍历的次序,我们可以得到一棵dfs树(当然,如果G不连通,得到的会是dfs森林)。按照这棵dfs树,我们可以将图G中的边分为两类:树边和非树边。同时我们也能注意到,这些非树边一定是从子结点连向它的祖先的,称之为返祖边。观察这棵dfs树,我们还可以发现,一个点可能称为割点,只可能是一下两种情况:
·这个点是根,并且它有至少两棵子树。
·这个点存在至少一棵不能通过非树边回到它的祖先的子树。
因此我们引入时间戳,即访问某个节点的时间,我们用dft数组来维护这个时间戳。此外,我们在维护一个low数组,用来维护每个点能回到的祖先的最小时间戳。如果一个点的儿子的low不小于它的时间戳,这就说明这个儿子所在的子树无法通过非树边回到它的祖先,这个点就是割点(当然,根要特判)。
1 void tarjan(int u,int fa){ 2 int son=0; 3 dft[u]=low[u]=++tot; 4 for(int i=f[u];i;i=e[i].nxt){ 5 int v=e[i].v; 6 if(!dft[v]){ 7 son++; 8 dfs(v,u); 9 low[u]=min(low[u],low[v]); 10 if(low[v]>=dft[u]) flag[u]=1; 11 } 12 else if(v!=fa) low[u]=min(low[u],dft[v]); 13 } 14 if(u==root&&son==1) flag[u]=0; 15 }//tarjan求割点
1.2 割边
仿照点割集和割点的定义,我们可以定义无向图G的边割集和割边。
类似的,我们也可以用tarjan算法求解无向图G的割边。
容易发现,割边一定是dfs上的一条树边,并且它所连接的子结点无法通过其子树中的边和非树边回到其祖先,换言之,它所连接的子结点u满足low[u]==dft[u]。
1.3 无向图的双连通分量
对于无向图G上两点,如果它们之间存在至少两条点不重复的路径,我们称它们为点双连通的。G的点双连通的极大子图称为G的点双连通分量。
对于无向图G上两点,如果它们之间存在至少两条边不重复的路径,我们称它们为边双连通的。G的边双连通的极大子图称为G的边双连通分量。
下面讨论如何求解无向图G的边双连通分量。
回忆上文提到的割边,我们很容易发现这样一个性质:两个结点是边双连通的,当且仅当它们之间存在通路,并且通路上没有割边。因此,割边可以被用作区分不同点双连通分量的标志。于是,我们只用对求割边的tarjan算法稍作修改,便能求解出无向图的边双连通分量。我们在dfs的同时维护一个栈,将G中的结点按照dfs的顺序放入栈中。当我们找到一条割边时,便将栈中的结点弹出,知道这条割边的所连接的子结点被弹出为止。
1 void tarjan(int u,int fa){ 2 dft[u]=low[u]=++tot;q[++top]=u; 3 for(int i=f[u];i;i=e[i].nxt){ 4 int v=e[i].v; 5 if(v==fa) continue; 6 if(!dft[v]){ 7 tarjan(v,u); 8 low[u]=min(low[u],low[v]); 9 } 10 else if(vis[v]==-1) low[u]=min(low[u],dft[v]); 11 } 12 if(dft[u]==low[u]){//找到割边 13 int x;cnt++; 14 do{ 15 x=q[top];top--;vis[x]=cnt; 16 }while(x!=u); 17 } 18 }//tarjan求边双连通分量
用类似的方法,我们也可以求解无向图的点双连通分量。
2 有向图的连通性
2.1 有向图的强连通分量
如果有向图G的基图是连通图,我们称G为弱连通图,简称为连通图。
如果有向图G中任意两点u、v,G中既有从u到v的路径,又有从v到u的路径,即u与v相互可达,则称G为强连通图。对于非强连通图,称其极大强连通子图为强连通分量。
我们仍然采用tarjan算法来求解有向图的强连通分量。不同于无向连通图,从有向图某一结点出发未必能遍历所有结点,故通常dfs所得到的并非一棵dfs树,而是一座dfs森林。并且较之于无向图的dfs树,在这座dfs森林中还多了一类被称为横叉边的边。横叉边定义如下:对于某棵dfs树上两结点u、v,二者没有直系亲属关系,并且dft[u]<dft[v]在,则称这条从u到v的边为横叉边。容易发现,横叉边对于强连通分量的求解并无影响,我们要关注仍然是那些返祖边。考虑某棵dfs树上任意两结点u、v,二者是相互可达的,当且仅当dfs树上从u到v的路径上没有dft==low的结点。因此我们可以将满足这种条件的结点最为划分强连通分量的依据,用类似求解无向图边双连通分量的方法求解出有向图的强连通分量。
1 void tarjan(int u){ 2 dft[u]=low[u]=++tot;q[++top]=u; 3 for(int i=f[u];i;i=e[i].nxt){ 4 int v=e[i].v; 5 if(!dft[v]){ 6 tarjan(v); 7 low[u]=min(low[u],low[v]); 8 } 9 else if(!vis[v]) low[u]=min(low[u],dft[v]); 10 } 11 if(low[u]==dft[u]){ 12 vis[u]=++cnt; 13 while(q[top]!=u){ 14 vis[q[top]]=cnt;--top; 15 } 16 --top; 17 } 18 }//tarjan求强连通分量
可以发现这段代码与1.3中的代码非常相似。
浙公网安备 33010602011771号