POJ1144——Tarjan+计算割点

本文深入探讨了无向图中的割点集和割点的概念,通过实例解释了如何识别割点,并详细阐述了点连通度的定义及其在寻找割点中的应用。此外,介绍了Tarjan算法在查找割点方面的高效方法,包括算法的实现细节和常见错误的避免。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[点割集与割点]
设无向图G=(V,E),G的连通分支数为X。
如果说存在顶点子集V’,删除V’中所有点(以及点关联的边)之后,子图V-V’的连通分支数Y>X。
那么V’是G的割点集。
如果割点集只有一个节点,则称为割点。

特别的:
无向连通图中G中,去掉某一节点和该节点所有有关联的边之后,剩下的图如果不再连通(连通分支数增加),称该点为割点。<也称为关节点:Articulation Point>。

[点连通度]
图G的点连通度定义为最小割点集合中的顶点数目。

[Tarjan算法求割点]
设在无向图G的DFS树中:
对于一个候选节点v1,当它满足下列两种情况的一种的时候,判断v是割点。[需要注意的是:这里的节点是指在深搜树上的节点而非原图上的节点,相当于将一个无向连通图转换为一棵深搜树后进行相应的判断操作]
1.如果节点v是根节点 v1 == root且root有两个以上的孩子,那么根节点root必定是割点。
2.如果节点v1不是根节点
v1是G的割点当且仅当v1存在一个子节点v2使得v2及其后代都没有反向边回到v1的祖先。(如果v2及v2的后代有反向边连回到v1不影响该结论).
证明:
1.考虑v1的任意子节点v2。如果v2及其后代不能通过后向边连回father节点,那么删除v1后,v2和father不再连通。
[这里体现在代码上即:low[v2] >= dfn[v1]]
如果不理解这两个变量的含义,传送门
2.反过来,如果v2或者它的某一个后代存在一条反向边连回father节点,则删除v1后,以v2为根的整棵子树都可以连回father。

这里写图片描述

/*
POJ 1144
算法:裸求无向连通图的割点
错误:误以为和一个节点v1有关系的节点{v2,v3....}在深搜树上一定是v1的儿子,实际上各种情况都存在(而且很有可能是叶子)
因为{v2,v3...}也会与其他的节点有关系
Accepted
*/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;

const int maxn = 105;
vector<int> edge[maxn];
int parent[maxn];//记录父节点,本题无用
int dfn[maxn],low[maxn];
int belong[maxn];//本题无用
bool instack[maxn];//标记是否在栈中,实际上就是是否访问过
bool cutpoint[maxn];//cutpoint[x] = true表示标号为x的节点是割点
int time,color;

int Min(int x,int y) { return (x < y ) ? x : y ; }
void Init()
{
    time = 0;
    color = 0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(parent,0,sizeof(parent));
    memset(belong,0,sizeof(belong));
    memset(instack,false,sizeof(instack));
    memset(cutpoint,false,sizeof(cutpoint));
    for(int i = 0 ; i < maxn ; ++i)
        edge[i].clear();
}
void Tarjan(int v1)
{
    int v2;
    int childnum = 0;//孩子数目
    dfn[v1] = low[v1] = ++time;
    instack[v1] = true;

    for(unsigned  i = 0 ; i < edge[v1].size() ; ++i){
        v2 = edge[v1][i];
        childnum++;
        if(dfn[v2] == 0){//继续搜索
            Tarjan(v2);
            low[v1] = Min(low[v1],low[v2]);
            /*if(v1 == 1 && edge[v1].size() > 1)//这样只是和v1节点有连边关系未必是孩子数
                cutpoint[v1] = true;
            例如v1 = 1 ,edge[v1][0] = 2,edge[v1][1] = 3....虽然2,3是v1的后继,但是在深搜树上,2,3可能是最后的叶子
            反而不是v1的儿子*/

            if(v1 == 1 && childnum >= 2)//case1,root 节点
                cutpoint[v1] = true;
            if(v1 != 1 && low[v2] >= dfn[v1])//case2,非根节点
                cutpoint[v1] = true;
        }
        else if(instack[v2] == true)//后向边
            low[v1] = Min(low[v1],dfn[v2]);
        //后向边的意义:如果(v1,v2)是一条后向边,说明v1可以通过v2回到v1的祖先
    }
}
int main()
{
    int v1,v2,n;
    char c;

    while(scanf("%d",&n) != EOF && n){
        Init();
        while(scanf("%d",&v1) && v1){
            while( (c = getchar() ) != '\n'){
                scanf("%d",&v2);
                edge[v1].push_back(v2);
                edge[v2].push_back(v1);//无向图
            }
        }
        Tarjan(1);//默认1节点是根节点
        int ans = 0;
        for(int i = 1 ; i <= n ; ++i)
            if(cutpoint[i])
                ans++;
        printf("%d\n",ans);
        //printf("ans : %d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值