详解Tarjan之割点【C++】【P3388 题解】

例题

P3388 割点

首先认识两个数组:

int dfn[N];
int low[N];

dfn[i] = 节点i的时间戳,就是第几个访问到该节点(子节点之间时间戳不同不影响结果)。

low[i] = 从节点i出发,不经过父节点能够回到的最早时间戳

如下图所示(动图包含递归计算dfn和low的过程):

如果你觉得太快了,请跳转

low怎么计算?

// v为u的孩子节点
1. 若 v 是未访问子节点:low[u] = min(low[u], low[v])
// 孩子能到哪 我就能到哪
2. 若 v 是已访问节点且非父节点:low[u] = min(low[u], dfn[v])

相信你已经明白dfn和low了。

判断是否为割点:

情况一:非根节点

如果子节点v无法通过其他路径绕过u访问u的祖先节点【即时间戳更小的节点】,说明u必不可少,是割点。

如下图所示(u是割点):

if (low[v] >= dfn[u] && fa != -1) {
    isCut[u] = true;
}

情况二:根节点

如果根节点的子节点中【子连通块】数量(即移除根节点后剩下的连通块数量)大于等于2,它就是割点。

如下图所示(u是割点):

(u不是割点):

int child = 0;
for (int v : e[u]) {
    if (dfn[v] == 0) {
        child++;
        Tarjan(v, u);
        // ... 
    }
}
if (fa == -1 && child >= 2)
    isCut[u] = true;

完整代码

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

const int N = 2e4 + 5;

int n, m, dfn[N], low[N], t = 0;
bool isCut[N];
vector<int> e[N];

void Tarjan(int u, int fa) {
    dfn[u] = low[u] = ++t;
    int child = 0;

    for (int v : e[u]) {
        if (dfn[v] == 0) {
            child++;
            Tarjan(v, u);
            low[u] = min(low[u], low[v]);

            // 非根节点且满足割点条件
            if (low[v] >= dfn[u] && fa != -1) {
                isCut[u] = true;
            }
        }
        else if (v != fa) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa == -1 && child >= 2)
        isCut[u] = true;
}


int main() {
    cin >> n >> m;
    while (m--) {
        int x, y;
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for (int i = 1; i <= n; i++) {
        if (dfn[i] == 0)
            Tarjan(i, -1);
    }
            
    int cnt = 0;
    for (int i = 1; i <= n; i++) 
        if (isCut[i]) cnt++;
    cout << cnt << endl;
    bool f = false;
    for (int i = 1; i <= n; i++) {
        if (isCut[i] == true) {
            if (f) cout << " ";
            cout << i;
            f = true;
        }
    }
    return 0;
}

### Tarjan算法求解图中的实现方法及其原理 Tarjan算法是一种高效的深度优先搜索(DFS)算法,用于求解图中的和桥(边)。它通过一次DFS遍历即可完成所有关键信息的收集,从而避免了暴力法多次DFS的低效性。 #### 的定义 是指在一个无向连通图中,删除某个顶及其相连的边后,图的连通分量数量增加。换句话说,该顶是图中连接多个子图的关键节。判断一个顶是否为的核心思想是:如果该顶的子图无法通过回边连接到其祖先节,则该顶。 #### Tarjan算法的基本原理 Tarjan算法基于DFS,并维护三个关键数组:`dfn[]`、`low[]`和`parent[]`。 - `dfn[u]`:记录顶`u`在DFS中的访问顺序(时间戳)。 - `low[u]`:记录顶`u`通过其子节能够访问到的最早的祖先节的时间戳。 - `parent[u]`:记录顶`u`的父节。 在DFS过程中,对于每条边`(u, v)`: 1. 如果`v`未被访问,则递归访问`v`,并更新`low[u] = min(low[u], low[v])`。 2. 如果`v`已被访问且`v`不是`u`的父节,则说明存在回边,此时更新`low[u] = min(low[u], dfn[v])`。 的判定条件为: - 对于非根节`u`,如果存在一个子节`v`,使得`low[v] >= dfn[u]`,则`u`是。 - 对于根节,如果其有多个子节分支,则根节。 #### 实现细节 以下是Tarjan算法求解C++实现代码: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 10005; vector<int> graph[MAXN]; // 图的邻接表表示 int dfn[MAXN], low[MAXN], parent[MAXN]; // 时间戳、最早祖先时间戳、父节 bool isCut[MAXN]; // 记录是否为 int timestamp = 0; // 时间戳变量 // DFS函数 void tarjan(int u, int root) { int child = 0; // 记录当前节的子节数量 dfn[u] = low[u] = ++timestamp; for (int v : graph[u]) { if (!dfn[v]) { // 如果v未被访问 parent[v] = u; tarjan(v, root); low[u] = min(low[u], low[v]); child++; // 判定 if ((u != root && low[v] >= dfn[u]) || (u == root && child > 1)) { isCut[u] = true; } } else if (v != parent[u]) { // 回边 low[u] = min(low[u], dfn[v]); } } } // 主函数 int main() { int n, m; cin >> n >> m; for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; graph[u].push_back(v); graph[v].push_back(u); } for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i, i); } } // 输出所有 cout << "为:"; for (int i = 1; i <= n; i++) { if (isCut[i]) { cout << i << " "; } } return 0; } ``` #### 算法复杂度 Tarjan算法的时间复杂度为`O(n + m)`,其中`n`是顶数,`m`是边数。这是因为算法只需要对图进行一次DFS遍历,且每个顶和边仅被访问一次。 #### 应用场景 Tarjan算法广泛应用于网络分析、电路设计和社交网络等领域。例如,在通信网络中,可能代表网络中的关键节,删除这些节可能导致网络分裂。通过识别这些关键节,可以优化网络结构以提高其鲁棒性。 #### 总结 Tarjan算法通过DFS和三个关键数组(`dfn[]`、`low[]`、`parent[]`)高效地求解图中的问题。其核心思想是利用回边和时间戳信息判断顶是否为。该算法在理论研究和实际应用中均具有重要意义。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值