Articulation Point(关节点)
在连通图G中,如果删除顶点u及以顶点u出发的所有边后所得的子图不连通,我们就称顶点u为图G的关节点或者连接点。
上图中灰色的顶点就是图的关节点。
输入 |V||E|
s0 t0
...
sn-1 tn-1
输出 按照升序依次输出图G的关节点
限制 1≤|V|≤100000、1≤|E|≤100000、G连通、不存在多重边、不存在自身循环
题解:我们可以检查图在单独删除各顶点之后的连通性,但这个算法要对每一个顶点执行一次深度优先搜索(DFS),效率实在不高
我们可以将深度优先搜索加以应用,就可以有效地找出图中的所有关节点。我们需要求得一下变量:
prenum[u];//顶点u的访问顺序
parent[u];//顶点u的父亲结点
lowest[u];//与顶点u相连的顶点的最小值顺序
有了这些变量我们便可以通过下述方法确定关节点:
- T的根结点r拥有2个以上子结点时(充分必要条件),r为关节点
- 对于各顶点u,设u的父节点parent[u]为p。如果prenum[p]≤lowest[u](充分必要条件),则p为关节点(p为根结点时则套用1)。这表明顶点u及u在T中的子孙均不具备与p的祖先相连的边。
下面举个例子,如图
case1.注意顶点5(父结点为顶点3):由于满足prenum[3]≤lowest[5](4≤6),因此顶点3为关节点。这表明顶点5及其在T中所有子孙都不具备与顶点3的祖先相连的边。
case2.注意顶点2(父结点为顶点1):由于不满足prenum[1]≤lowest[2],因此顶点1不是关节点。这表明顶点2或其在T中的某个子孙具备与顶点1的祖先相连的边。
#include<iostream>
#include<vector>
#include<set>
using namespace std;
#define MAX 100000
vector<int>G[MAX];
int N;
bool visited[MAX];
int prenum[MAX],parent[MAX],lowest[MAX],timer;
void dfs(int current,int prev){
//访问结点current之后立刻执行的处理
//prenum[i]结点i的访问顺序
//lowest[i]与结点i相连的下一个最小顺序结点
prenum[current]=lowest[current]=timer;
timer++;
visited[current]=true;
int next;
for(int i=0;i<G[current].size();i++){
//与current结点相连的下一个节点
next=G[current][i];
if(!visited[next]){
//即将通过结点current访问结点next时执行的处理
parent[next]=current;
dfs(next,current);
//结点next搜索完毕之后立刻处理
//若是没有访问的,则在current与next取最小顺序的下一个节点
lowest[current]=min(lowest[current],lowest[next]);
}
//若下一个结点已被访问,且该结点不是前一个结点
else if(next!=prev){
//边current-->next为Back-edge时的处理
lowest[current]=min(lowest[current],prenum[next]);
}
}
}
void art_points(){
for(int i=0;i<N;i++){
visited[i]=false;
}
timer=1;
//计算lowest
dfs(0,-1);//0 == root
set<int>ap;
int np=0;
for(int i=1;i<N;i++){
//节点i的父亲结点
int p=parent[i];
//记录父结点的子结点数
if(p==0)
np++;
//如果要去点p,p的访问顺序在结点i的访问的下一个结点顺序之前时
else if(prenum[p]<=lowest[i])
ap.insert(p);
}
if(np>1)//父结点的子结点数为两个以上是则也是一个关节点
ap.insert(0);
for(set<int>::iterator it = ap.begin();it!=ap.end();it++){
cout<<*it<<endl;
}
}
int main(){
int m;
cin>>N>>m;
for(int i=0;i<m;i++){
int s,t;
cin>>s>>t;
G[s].push_back(t);
G[t].push_back(s);
}
art_points();
return 0;
}