无向图中割点的概念:
割点:一个结点称为割点(或者割顶)当且仅当去掉该节点及其相关的边之后的子图不连通。
判断一个结点是否为割点有如下两个条件:
条件一:若该结点u为根结点,它应有两个及两个以上的子结点。
条件二:若该结点u非根结点,它应有一个满足条件的子结点v:该子结点v及其后代没有反向边可以连回结点u的祖先。
在Tarjan算法中,有两个points非常重要:
1.dfn[i]表示结点i被访问的时间戳,即深度优先访问次序。
2.low[i]表示结点i及其子树的结点通过反向边能到达的结点的最小dfn值。
下面贴一个求割点的模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 10005
using namespace std;
int n,m,x,y,root;
int tot; //边的序号
int id; //深度优先数即遍历次序
int Next[maxn],Head[maxn],Dst[maxn];
bool iscut[maxn];
int low[maxn],dfn[maxn];
void init()
{
tot=0;id=0;
memset(iscut,0,sizeof(iscut));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
}
void add_edge(int x,int y)
{
tot++;
Next[tot]=Head[x];
Head[x]=tot;
Dst[tot]=y;
}
void dfs(int x,int pre)
{
int child=0;
low[x]=dfn[x]=++id;
for(int i=Head[x];i;i=Next[i])
{
int y=Dst[i];
if(!dfn[y])
{
dfs(y,x);
low[x]=min(low[x],low[y]);
if(x==root) child++;
if(x==root&&child>=2) //满足条件一
iscut[root]=true;
if(x!=root&&low[y]>=dfn[x]) //满足条件二
iscut[x]=true;
}
else if(y!=pre)//结点y在结点x之前已经访问过了,且y不是x的父亲结点,必有dfn[y]<dfn[x]
low[x]=min(low[x],dfn[y]);
}
}
int main()
{
init();
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&x,&y);
add_edge(x,y);
add_edge(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
{
root=i;
dfs(i,i);
}
printf("输出割点:\n");
for(int i=1;i<=n;i++)
if(iscut[i])
printf("%d ",i);
printf("\n");
return 0;
}
其中low[x]=min(low[x],dfn[y])这个地方,起初让我很不理解,为什么不可以是low[x]=min(low[x],low[y])呢?
再读一遍第二个point:low[i]表示 结点i及其子树的结点 通过反向边 能到达的结点的最小dfn值。
因为y不是x的子结点,因此这里只能用low[x]=min(low[x],dfn[y])。
附求割点裸题
POJ1144传送门