图论 - 割点(Tarjan)

文章目录

定义

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。
如果某个割点集合只含有一个顶点X(也即{X}是一个割点集合),那么X称为一个割点。
如果图是连通的,那么割点的定义就是,在一个无向连通图中,如果删除某个顶点后,图不再连通,则这个顶点为割点。

通过Tarjan算法可以求出所有割点,时间复杂度为O(N + M)。

例题

洛谷P3388
给定一个无向图(不一定连通),求出所有割点。

#include <iostream>
#include <vector>
#include <cstdio>
#include <string>
#include <map>
#include <set>
#include <cstring>
#include <climits>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <cassert>
using namespace std;

const int MAXN = 20005;
const int MAXM = 200005;

struct Edge {
    int dest, next;
} edges[MAXM];
int head[MAXN];
int edges_cnt = 0;
int n, m;

int low[MAXN];  // 记录每个结点在不结果父结点时,能够回到的最小时间戳
int dfn[MAXN];  // 记录每个结点的时间戳
bool cut[MAXN]; // 记录每个结点是否为割点
int dfi = 0;    // 当前时间戳
int root;       // 根结点

void addEdge(int src, int dest) {
    ++edges_cnt;
    edges[edges_cnt].dest = dest;
    edges[edges_cnt].next = head[src];
    head[src] = edges_cnt;
}

void tarjan(int node, int father) {
    int child = 0;  // 记录生成树中当前结点node的孩子数量
    dfn[node] = low[node] = ++dfi;
    for (int i = head[node]; i; i = edges[i].next) {
        int dest = edges[i].dest;
        if (!dfn[dest]) {  // 如果dest的时间戳为0,说明它还没被访问过
            ++child;
            tarjan(dest, node);
            // 更新当前结点node能访问到的最早时间戳
            low[node] = min(low[node], low[dest]);
            // 如果当前结点不是根结点且low[dest]>=dfn[node],则当前结点为割点
            if (node != root && low[dest] >= dfn[node]) {
                cut[node] = true;
            }
            // 如果当前结点是根结点,在生成树中必须有2个孩子,才是割点
            else if (node == root && child == 2) {
                cut[node] = true;
            }
        }
        // 否则如果dest曾被访问过,并且不是node的父结点,则说明此时dest为
        // node的祖先,因此需要更新node能访问到的最早时间戳
        else if (dest != father) {
            low[node] = min(low[node], dfn[dest]);
        }
    }
}

int main() {
#ifdef DEBUG
    freopen("in.txt", "r", stdin);
#endif
    scanf("%d%d", &n, &m);
    while (m--) {
        int src, dest;
        scanf("%d%d", &src, &dest);
        addEdge(src, dest);
        addEdge(dest, src);
    }

    for (int node = 1; node <= n; ++node) {
        if (!dfn[node]) {
            root = node;
            tarjan(node, 0);
        }
    }

    int cut_cnt = 0;
    for (int node = 1; node <= n; ++node) {
        if (cut[node])
            ++cut_cnt;
    }
    printf("%d\n", cut_cnt);
    for (int node = 1; node <= n; ++node) {
        if (cut[node])
            printf("%d ", node);
    }
    putchar('\n');

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值