//求无向连通图的割点和桥
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define MaxSize 1000
int pre[MaxSize]; //记录节点在dfs生成树中的编号,并可以用来判定节点是否已访问
int iscut[MaxSize],isbrige[MaxSize][MaxSize]; //记录顶点和边是否是割点和桥
int low[MaxSize]; //记录每个节点所能返回的祖先的最小编号
int n,m,dfs_clock; //图的顶点数和边数,记录节点再dfs森林中的编号
vector<int> map[MaxSize]; //vector模拟图的邻接表
//利用图的邻接表建立图
void Create()
{
int i,j,k1,k2;
cin>>n>>m;
for(i=0;i<n;i++)
map[i].clear(); //记得清空
for(i=0;i<m;i++)
{
cin>>k1>>k2;
map[k1].push_back(k2);
map[k2].push_back(k1);
}
}
//首先对于割点:
//(1): 若u是根节点,如果u的儿子数大于1,则u一定是割点
//(2):若u不是根节点当且仅当存在父子边(u,v)且pre[u]<=low[v]
//然后对于割边
//必须满足存在父子边(u,v)且pre[u]<low[v],则(u,v)为一条割边
/*对于求low值主要是依据:low[u]=min
{
pre[u]; //节点自己在dfs森林中的编号
pre[v]; //若存在反向边(u,v),则要考虑v的编号
low[v]; //考虑u的儿子v的low值
}程序用递归实现这个过程*/
//传入的fa是u的父节点,根节点的父节点指定为负数
int dfs(int u,int fa)
{
int lowu=pre[u]=++dfs_clock; //lowu代表要求节点的low值,初始化为pre[u]的值
int child=0; //记录当前节点的儿子数
int i,j,k;
k=map[u].size();
for(i=0;i<k;i++) //依次检查u的每一个子节点
{
int v=map[u][i];
if(!pre[v]) //(u,v)为父子边情况
{
child++;
int lowv=dfs(v,u); //计算儿子节点的low值
lowu=min(lowu,lowv);//用儿子节点的low值更新父节点的low值
if(lowv>=pre[u]) //利用儿子节点的low值判断自身是否为割点
iscut[u]=1;
if(lowv>pre[u]) //判断(u,v)是否为割边
isbrige[u][v]=1;
}
else if(pre[v]&&v!=fa) //是反向边情况,但要排除反向父子边的情况
lowu=min(lowu,pre[v]); //用pre[v]更新lowu的值
}
if(fa<0&&child==1) //对根节点情况进行特判
iscut[u]=0;
low[u]=lowu; //记录最终结果
return lowu;
}
//初始化函数
void Init()
{
memset(iscut,0,sizeof(iscut));
memset(isbrige,0,sizeof(isbrige));
memset(pre,0,sizeof(pre));
dfs_clock=0;
}
int main()
{
while(1)
{
int i,j,k;
Create();
Init();
dfs(0,-1); //对于无向图连通图来说,可以从任意一点遍历整个图
cout<<"割点有:"<<endl;
for(i=0;i<n;i++)
if(iscut[i])
cout<<i<<" ";
cout<<endl;
cout<<endl<<"桥有:"<<endl;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(isbrige[i][j])
cout<<i<<" "<<j<<endl;
cout<<endl;
}
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define MaxSize 1000
int pre[MaxSize]; //记录节点在dfs生成树中的编号,并可以用来判定节点是否已访问
int iscut[MaxSize],isbrige[MaxSize][MaxSize]; //记录顶点和边是否是割点和桥
int low[MaxSize]; //记录每个节点所能返回的祖先的最小编号
int n,m,dfs_clock; //图的顶点数和边数,记录节点再dfs森林中的编号
vector<int> map[MaxSize]; //vector模拟图的邻接表
//利用图的邻接表建立图
void Create()
{
int i,j,k1,k2;
cin>>n>>m;
for(i=0;i<n;i++)
map[i].clear(); //记得清空
for(i=0;i<m;i++)
{
cin>>k1>>k2;
map[k1].push_back(k2);
map[k2].push_back(k1);
}
}
//首先对于割点:
//(1): 若u是根节点,如果u的儿子数大于1,则u一定是割点
//(2):若u不是根节点当且仅当存在父子边(u,v)且pre[u]<=low[v]
//然后对于割边
//必须满足存在父子边(u,v)且pre[u]<low[v],则(u,v)为一条割边
/*对于求low值主要是依据:low[u]=min
{
pre[u]; //节点自己在dfs森林中的编号
pre[v]; //若存在反向边(u,v),则要考虑v的编号
low[v]; //考虑u的儿子v的low值
}程序用递归实现这个过程*/
//传入的fa是u的父节点,根节点的父节点指定为负数
int dfs(int u,int fa)
{
int lowu=pre[u]=++dfs_clock; //lowu代表要求节点的low值,初始化为pre[u]的值
int child=0; //记录当前节点的儿子数
int i,j,k;
k=map[u].size();
for(i=0;i<k;i++) //依次检查u的每一个子节点
{
int v=map[u][i];
if(!pre[v]) //(u,v)为父子边情况
{
child++;
int lowv=dfs(v,u); //计算儿子节点的low值
lowu=min(lowu,lowv);//用儿子节点的low值更新父节点的low值
if(lowv>=pre[u]) //利用儿子节点的low值判断自身是否为割点
iscut[u]=1;
if(lowv>pre[u]) //判断(u,v)是否为割边
isbrige[u][v]=1;
}
else if(pre[v]&&v!=fa) //是反向边情况,但要排除反向父子边的情况
lowu=min(lowu,pre[v]); //用pre[v]更新lowu的值
}
if(fa<0&&child==1) //对根节点情况进行特判
iscut[u]=0;
low[u]=lowu; //记录最终结果
return lowu;
}
//初始化函数
void Init()
{
memset(iscut,0,sizeof(iscut));
memset(isbrige,0,sizeof(isbrige));
memset(pre,0,sizeof(pre));
dfs_clock=0;
}
int main()
{
while(1)
{
int i,j,k;
Create();
Init();
dfs(0,-1); //对于无向图连通图来说,可以从任意一点遍历整个图
cout<<"割点有:"<<endl;
for(i=0;i<n;i++)
if(iscut[i])
cout<<i<<" ";
cout<<endl;
cout<<endl<<"桥有:"<<endl;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(isbrige[i][j])
cout<<i<<" "<<j<<endl;
cout<<endl;
}
return 0;
}