无向图的割顶与桥
具体的概念和定义比较多,在刘汝佳<<训练指南>>P312-314页都有详细的介绍.
下面来写求无向图割顶和桥的DFS函数.我们令pre[i]表示第一次访问i点的时间戳,令low[i]表示i节点及其后代所能连回(通过反向边)的最早祖先的pre值.
下面的dfs函数返回的是当前遍历的节点u的low值.如果u是割顶还会标记u节点.且如果u->v(v是u的儿子节点)边是桥也会标记该边.
测试代码:
/*刘汝佳 训练指南P 312-314
测试输入数据:
6 6
0 1
1 2
1 3
2 4
2 5
4 5
输出为:
边(1, 2)是桥
边(1, 3)是桥
边(0, 1)是桥
割顶是:1
割顶是:2
注意上面 虽然0点是根节点且只有1个儿子,但是边(0,1)也是桥
*/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=100000+10;
int n,m;
int dfs_clock;
vector<int> G[maxn];//点从0到n-1编号
int pre[maxn],low[maxn];
bool iscut[maxn];
int dfs(int u,int 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])
{
child++;//未访问过的节点才能算是u的孩子
int lowv=dfs(v,u);
lowu=min(lowu,lowv);
if(lowv>=pre[u])
{
iscut[u]=true; //u点是割点
if(lowv>pre[u]) //(u,v)边是桥
printf("边(%d, %d)是桥\n",u,v);
}
}
else if(pre[v]<pre[u] && v!=fa)//v!=fa确保了(u,v)是从u到v的反向边
{
lowu=min(lowu,pre[v]);
}
}
if(fa<0 && (child==1||child==0) )
iscut[u]=false;//u若是根且孩子数<=1,那u就不是割顶
return low[u]=lowu;
}
int main()
{
while(scanf("%d%d",&n,&m)==2&&n)
{
dfs_clock=0;
memset(pre,0,sizeof(pre));
memset(iscut,0,sizeof(iscut));
for(int i=0;i<n;i++) G[i].clear();
for(int i=0;i<m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(0,-1);
for(int i=0;i<n;i++)if(iscut[i]==true)
printf("割顶是:%d\n",i);
}
return 0;
}