tarjan算法--求无向图的割点和桥

一.基本概念
1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥
2.割点:无向连通图中,如果删除某点后,图变成不连通,则称该点为割点。

二、在介绍算法之前,先介绍几个基本概念
DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树
树边:(称为父子边),在搜索树中的实线所示,可理解为在DFS过程中访问未访问节点时所经过的边。
回边:(称为返祖边、后向边),在搜索树中的虚线所示,可理解为在DFS过程中遇到已访问节点时所经过的边。

三、求割点
有两类节点可以成为割点:
1>对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
2>对非叶子节点u(非根节点),若其子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点;
我们用dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小);
所以得出以下结论:
1)当前节点为树根的时候,条件是“要有多于一棵子树”(如果这有一颗子树,去掉这个点也没有影响,如果有两颗子树,去掉这点,两颗子树就不连通了。)
2)当前节点u不是树根的时候,条件是“low[v]>=dfn[u]”,也就是在u之后遍历的点,能够向上翻,最多到u,如果能翻到u的上方,那就有环了,去掉u之后,图仍然连通。保证v向上最多翻到u才可以

四、求桥
若是一条无向边(u,v)是桥:
当且仅当无向边(u,v)是树枝边的时候,需要满足dfn(u)<low(v),也就是v向上翻不到u及其以上的点,那么u–v之间一定能够有1条或者多条边不能删去,因为他们之间有一部分无环,是桥。
如果v能上翻到u那么u–v就是一个环,删除其中一条路径后,能然是连通的。
注意: 求桥的时候:因为边是无方向的,所以父亲孩子节点的关系需要自己规定一下,在tarjan的过程中if(v不是u的父节点) low[u]=min(low[u],dfn[v]);因为如果v是u的父亲,那么这条无向边就被误认为是环了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
const int N = 201;

vector<int>ve[N];
int n,m,low[N],dfn[N];
bool cut[N];
int fa[N];
int cnt = 0;

void Tarjan(int u,int father)
{
    fa[i] = father;
    low[u] = dfn[u] = cnt++;
    int len = ve[u].size();
    for(int i = 0;i < len;++i){
        int v = ve[u][i];
        if(dfn[v] == -1){
            Tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }else if(v != father){/*假如k是i的父亲的话,那么这就是无向边中的重边,有重边那么一定不是桥*/
            low[u] = min(low[u],dfn[v]);//dfn[k]可能!=low[k],所以不能用low[k]代替dfn[k],否则会上翻过头了
        }
    }
}

void solve()
{
    memset(cut,false,sizeof(cut));
    memset(low,-1,sizeof(low));
    memset(dfn,-1,sizeof(dfn));
    memset(fa,0,sizeof(fa));
    cnt = 1;
    int rootson = 0;
    Tarjan(1,0);
    for(int i = 2;i <= n;++i){
        int v = fa[i];
        if(v == 1){
            rootson++;
        }else{
            if(low[i] >= dfn[v]) cut[v] = true;
        }
    }
    if(rootson > 1) cut[1] = true;
    for(int i = 1;i <= n;++i){
        if(cut[i])
            printf("%d\n",i);
    }
    for(int i = 1;i <= n;++i){
        int v = fa[i];
        if(v > 0 && low[i] > dfn[v])
            printf("%d %d\n",v,i);
    }
}

int main()
{
    while(~scanf("%d %d",&n,&m)){
        for(int i = 0;i < m;++i){
            int a,b;
            scanf("%d %d",&a,&b);
            ve[a].pb(b);
            ve[b].pb(a);
        }
    }
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值