参看资料:
https://www.cnblogs.com/maple-kingdom/p/maple-kingdom_wind.html
https://www.cnblogs.com/collectionne/p/6847240.html
https://baike.baidu.com/item/%E5%89%B2%E7%82%B9/9384042?fr=aladdin
割点集合&割点:
在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。
如果某个割点集合只含有一个顶点X(也即{X}是一个割点集合),那么X称为一个割点。
求割点:
1》直接BFS;
2》Tarjan算法:
1> BFS搜索树,详细:https://blog.csdn.net/sodacoco/article/details/86488033;
2> 定义 DFN (u) 为 u 在搜索树中被遍历到的次序号(时间戳);定义Low (u) 为 u 或者 u 子树中的节点经过最多一条后向边能追溯到的最早的树中节点的次序号。
根据定义,则有:
如果 (u , v) 为树枝边,u为v的父节点,则Low (u) =Min { Low (u) , Low (v) } 。
如果 (u , v) 为后向边,u不为v的父节点,则Low (u) =Min { Low (u) , DFN (v) } 。
判断割点:
一个顶点 u 是割点,当且仅当满足以下两个条件之一:
条件1:u 为树根,且 u 有多于一个子树,则 u 为割点;
条件2:u 不是树根,且满足存在 (u , v) 为树枝边【即满足 u 为 v 在搜索树中的父亲】,并使得 DFN (u) <= Low (v) 。【因为删除 u 后 v 以及 v 的子树不能达到 u 的祖先】。
代码实现:
void tarjian(int x,int pre){
dfn[x]=low[x]=++dfstime;
stk[++top]=x;
int cnt=0;
for(int i=head[x];i!=-1;i=next[i]){
int y=to[i];
if(!dfn[y]){
cnt++;
tarjian(y,x);
low[x]=min(low[x],low[y]);//判断割点两个条件,且不用判断重边
//对于割点来说,两个点多条边和一条边效果一样
if(x==root&&cnt>1||(x!=root&&dfn[x]<=low[y]))//判断割点
cut[x]=1;
}
else low=min(low[x],dfn[y]);//无向图,不用判断是否在栈中
}
}
例题:
题目描述
给出一个n个点,m条边的无向图,求图的割点。
输入格式:第一行输入n,m
下面m行每行输入x,y表示x到y有一条边
输出格式:第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
输入样例#1:
6 7 1 2 1 3 1 4 2 5 3 5 4 5 5 6输出样例#1:
1 5说明
n,m均为100000
tarjan 图不一定联通!!!
注意与tarjan 求强连通分量的方法区别。
求割点不需要用到栈。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int N,M,x,y,cnt,Index,ans=0;
int first[100010],next[200010];
int dfn[100010],low[100010];
bool del[100010];
struct maple{
int f,t;
}Rode[200010];
void Build(int f,int t)
{
Rode[++cnt]=(maple){f,t};
next[cnt]=first[f];
first[f]=cnt;
}
void Tarjan(int n,int fa)
{
int cd=0;
dfn[n]=low[n]=++Index;
for(int i=first[n];i;i=next[i])
{
if(!dfn[Rode[i].t])
{
Tarjan(Rode[i].t,n);
low[n]=min(low[n],low[Rode[i].t]); // 更新n能向上回溯到的最小时间戳
if(low[Rode[i].t]>=dfn[n]&&fa!=-1) // 如果n的子树中最多能追溯到n,并且n不为根的话,
del[n]=1; //就为一个割点,根要特殊处理
if(fa==-1) ++cd; // 统计根的子树
}
else if(Rode[i].t!=fa) low[n]=min(low[n],dfn[Rode[i].t]);
}
if(fa==-1&&cd>=2) del[n]=1; // 如果n为根且n的子树数量大于等于 2 ,即为一个割点
}
int main()
{
scanf("%d%d",&N,&M);
for(int i=1;i<=M;++i)
{
scanf("%d%d",&x,&y);
Build(x,y);
Build(y,x);
}
for(int i=1;i<=N;++i)
if(!dfn[i]) Tarjan(i,-1);
for(int i=1;i<=N;++i) if(del[i]) ++ans;
printf("%d\n",ans);
for(int i=1;i<=N;++i)
if(del[i])
printf("%d ",i);
return 0;
}