割点和桥的概念:
对于无向图G,如果删除某个点u后,连通分量数目增加(本来为一个连通集,现在变为多个),称u为图的割点。对于连通图,割点就是删除之后不再连通的点。当然了,桥也同理,只不过桥是边,也称为割边,如果删掉(u,v)之后连通分量数目增加,那么我们称(u,v)这条边为桥(割边)。
求割点和桥的方法:
①很容易想到的,暴力每个结点,然后用DFS判断连通分量是否增加,时间复杂度为O(n(n+m)),其中n和m分别是图中的点数和边数。
②深入挖掘DFS的性质,在线性时间O(n+m)时间内求出所有的割点和桥。
当然,效率来说当然选择第二种方法了,下面介绍第二种方法。
说明:
对于每一个顶点v,我们称其前序编号为pre[v],对于深度优先搜索生成树上的每个顶点v,计算编号最低的顶点,我们称之为Low(v),该点可从v开始通过树的零条或多条边,且可能还有一条后向边而达到。我们可以通过对该深度优先生成树执行一次后序遍历有效地算出Low,根据Low的定义可知,Low(v)是 ①pre(v) ②所有后向边(v,w)中最低的pre(w)。③树的所有边(v,w)中的最低Low(w)。对于任意一条边(v,w),只要检查pre(v)和pre(w)就可以知道它是一条树的边还是一条后向边。因此,Low(v)容易计算:我们只需扫描v的邻接表,应用适当的法则,并记住最小者。
割点满足的条件:
根是割点当且仅当它有多个儿子,因为如果有两个儿子,那么删除根结点后两个儿子就分别属于不同的子树上了,即产生了两个连通分量。如果根只有一个儿子,那么除去根不过是断离该根,对于其他任何的顶点v,当且仅当它某个儿子w使得Low(w) >= pre(v)才是割点。
伪代码:
void Graph::findArt(Vertex v)
{
v.visited = true;
v.low = v.num = counter++; //Rule 1
for each Vertex w adjacent to v //遍历每一个邻接于v的顶点
{
if(!w.visited) //树边
{
w.parent = v; //父节点为v
findArt(w);
if(w.low >= v.num)
{
cout<<v<<" is cut point."<<endl;
if(w.low > v.num)
{
cout<<edge<<" is cut edge."<<endl;
}
}
v.low = min(v.low,w.low); //Rule 3
}
else
{
if(v.parent != w) //后向边
{
v.low = min(v.low,w.num); //Rule 2
}
}
}
}
c++实现代码:
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int maxn = 1001;
vector<int> G[maxn];
int pre[maxn];
bool iscut[maxn]; //割点
bool bridge[maxn][maxn]; //桥(割边)
int low[maxn];
int n,m; //n个结点,m条两顶点彼此相连的边
int dfs_clock;
void init()
{
int num1,num2;
cout<<"total n vertexes:"<<endl;
cin>>n;
memset(iscut,false,sizeof(iscut));
memset(pre,0,sizeof(pre));
memset(low,0,sizeof(low));
memset(bridge,false,sizeof(bridge));
cout<<"total m edges which connect each other"<<endl;
cin>>m;
for(int i=0;i<m;i++)
{
cin>>num1>>num2;
G[num1].push_back(num2);
G[num2].push_back(num1);
}
}
int dfs(int u,int fa) //u在DFS树中的父结点是fa
{
int lowu = pre[u] = ++dfs_clock;
int child = 0; //子结点数目
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(!pre[v]) //没有访问过v,所以为树边
{
child++; //子结点数目+1
int lowv = dfs(v,u);
lowu = min(lowu,lowv); //用后代的low函数更新u的low函数 (u,v)为树边
if(lowv >= pre[u]) //非根结点u是G的割点,当且仅当u存在一个子结点v,使得v及其所有后代都没有反向边连回u的祖先
{
iscut[u] = true; //u为割点
if(lowv > pre[u]) //(u,v)为桥
{
bridge[u][v] = true;
}
}
}
else if(pre[v] < pre[u] && v != fa){ //(u,v)为回边且v不为u的父结点,v为u的子结点,但是它指向的pre比pre[u]小的父亲
lowu = min(lowu,pre[v]); //用反向边更新u的low函数
}
}
if(fa < 0 && child == 1) iscut[u] = 0; //如果是根结点且儿子数只有一个,那么u不是割点
low[u] = lowu; //更新u的low值
return lowu;
}
void display()
{
for(int i=1;i<=n;i++)
{
if(iscut[i] == true)
{
cout<<i<<" is cut point."<<endl;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(bridge[i][j] == true && i != j)
{
cout<<"("<<i<<","<<j<<")"<<" is bridge."<<endl;
}
}
}
}
int main()
{
//freopen("111","r",stdin);
init();
dfs_clock = 0;
dfs(1,-1);
display();
}
样例图:
输出结果: