前言: T a r j a n Tarjan Tarjan
求割点和割边建立在 T a r j a n Tarjan Tarjan算法的基础之上,因此建议在看这篇博客之前先去学一学 T a r j a n Tarjan Tarjan。
L i n k Link Link
T a r j a n Tarjan Tarjan算法详见博客Tarjan在图论中的应用(一)——用Tarjan来实现强连通分量缩点
回顾 T a r j a n Tarjan Tarjan中各个数组的定义
首先,我们来回顾一下 T a r j a n Tarjan Tarjan中各个数组的定义:
d f n [ dfn[ dfn[ ] ] ]:每个点的 d f s dfs dfs序。
l o w [ low[ low[ ] ] ]:每个点能到达的 d f s dfs dfs序最小的节点的 d f s dfs dfs序。
而其他数组在求割点和割边的过程中则不太必要了。
割点
首先,我们要了解一下割点的定义:把这个点去掉之后,这个点所在的联通块就会被分成若干个联通块。
既然这样,也就是说,只要这个节点某一个子节点所到达的节点的 d f s dfs dfs序大于等于该节点的 d f s dfs dfs序,即它的这个子节点无法到达 d f s dfs dfs序小于该节点的节点,就说明它是一个割点了。
而对于一个联通块第一个访问的节点,则需特判,如果它在遍历完一个节点所能遍历到的所有节点,还能找到没有被遍历过的节点,就说明它是一个割点。
代码如下:
inline void Tarjan(int x,int lst)//Tarjan求割点
{
register int i,tot=0;//tot记录访问到的子节点个数
for(dfn[x]=low[x]=++d,i=lnk[x];i;i=e[i].nxt)//枚举每一个子节点
{
if(!(e[i].to^lst)) continue;//如果这个节点是当前节点的父亲节点,就跳过
if(!dfn[e[i].to])//如果这个子节点没有被访问过
{
Tarjan(e[i].to,x),low[x]=min(low[x],low[e[i].to]),++tot;//遍历该子节点,更新low[x],并将tot加1
if(lst&&low[e[i].to]>=dfn[x]) IsCut[x]=1;//如果当前节点不是一个联通块第一个访问的节点,且当前节点的这个子节点dfs序大于等于当前节点的dfs序,那么就说明当前节点是割点
}
else low[x]=min(low[x],dfn[e[i].to]);//更新low[x]
}
if(!lst&&tot>=2) IsCut[x]=1;//如果当前节点是联通块第一个节点,且访问到的子节点个数≥2,那么就说明当前节点是割点
}
割边
还是一样,先了解一下割边的定义:把这条边去掉之后,这条边所在的联通块就会被分成若干个联通块,看起来与割点的定义很像。
因此,如果一条边所连接的两个节点,若其中 d f s dfs dfs序较大的节点不经过这条边所能到达的 d f s dfs dfs序最小的节点大于这条边连接的点中 d f s dfs dfs序较小的节点,就说明这条边是一条割边。
不过还要注意判重的情况,要注意如果有两条相同的边,那么这两条边肯定都不是割边。
代码如下:
inline void Tarjan(int x,int lst)//Tarjan求割边
{
register int i,flag=1;//flag记录当前节点是否第一次访问它的父亲节点
for(dfn[x]=low[x]=++d,vis[Stack[++top]=x]=1,i=lnk[x];i;i=e[i].nxt)//枚举每一个子节点
{
if(flag&&!(e[i].to^lst)) {flag=0;continue;}//如果是第一次访问父亲节点,就将flag标记为0,并跳过这条边
if(!dfn[e[i].to])//如果这个子节点没有被访问过
{
Tarjan(e[i].to,x),low[x]=min(low[x],low[e[i].to]);//访问这个节点,并更新low[x]
if(low[e[i].to]>low[x]) IsBridge[i]=1;//如果这条边连向的另一个节点所能到达的dfs序最小的节点大于该节点的dfs序,就说明这条边是割边
}
else if(vis[e[i].to]) low[x]=min(low[x],dfn[e[i].to]);//否则,更新low[x]
}
}