图论算法补充--Tarjan求割点(AI梳理版)

基本概念

在无向图中,割点是指去掉该点及与该点相连的所有边后,图的连通分量会增加的点。比如在一个城市交通网络(可看作无向图 ,节点是地点,边是道路 )中,某个关键地点(割点 )被封锁,会导致原本连通的区域被分割成多个不相连的部分。

Tarjan 算法原理

Tarjan 算法通过深度优先搜索(DFS)遍历无向图,给每个节点引入两个重要属性:

  • dfn[u]:时间戳,记录节点 u 在 DFS 过程中被首次访问的次序。
  • low[u]:表示 u 或 u 的子树中的节点,经过最多一条后向边能追溯到的最早的树中节点的次序号(dfn 值 ) 。可以理解为 u 及其子树能 “回退” 到的最早访问的节点 。

割点判断条件

  1. 若 u 是根节点:当根节点 u 在 DFS 树中至少有两个子节点时,u 是割点。因为去掉根节点 u 后,这些子树就会相互分离,使得连通分量增加。
  2. 若 u 不是根节点:对于 u 的子节点 v ,当 low [v] ≥ dfn [u] 时,u 是割点。这表明 v 及其子树中的节点,不通过 u 就无法到达比 u 更早访问的节点,所以去掉 u 会使图的连通性被破坏。

算法实现步骤

  1. 初始化:对所有节点的 dfn 和 low 数组初始化为 0,表示未被访问。设置时间戳计数器(如 idx = 0 )用于记录访问次序。
  2. DFS 遍历:从图中某个未访问节点开始 DFS。在访问节点 u 时,初始化 dfn [u] = low [u] = ++idx 。
  3. 遍历邻边:对 u 的每个邻接节点 v :
    • 若 v 未访问:递归调用 DFS 访问 v ,并在回溯时更新 low [u] = min (low [u], low [v]) 。若满足 low [v] ≥ dfn [u] 且 u 不是 DFS 的起始根节点,则 u 是割点;若 u 是根节点,记录其孩子节点数量,当孩子节点数≥2 时,u 是割点。
    • 若 v 已访问且不是 u 的父节点:说明存在后向边,更新 low [u] = min (low [u], dfn [v]) 。
  4. 重复步骤:不断从图中未访问节点开始 DFS,直到所有节点都被访问过。

示例代码(C++ )

#include 
#include 
#include 
using namespace std;

const int N = 100010;
vector g[N];  // 邻接表存储图
int dfn[N], low[N], idx;  // dfn数组记录时间戳,low数组记录追溯值,idx是时间戳计数器
bool cut[N];  // 标记节点是否为割点

void tarjan(int u, int fa) {  // u为当前节点,fa为u的父节点
    dfn[u] = low[u] = ++idx;
    int child = 0;  // 记录u的孩子节点数量
    for (int v : g[u]) {
        if (!dfn[v]) {  // v未访问过
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u] && u != fa) {
                cut[u] = true;
            }
            if (u == fa) child++;
        }
        else if (v != fa) {  // v已访问且不是u的父节点
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (child >= 2 && u == fa) {
        cut[u] = true;
    }
}

调用方式:

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) {
            tarjan(i, i);
        }
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (cut[i]) cnt++;
    }
    cout << cnt << endl;
    for (int i = 1; i <= n; i++) {
        if (cut[i]) cout << i << " ";
    }
    return 0;
}

通过 Tarjan 算法,可以高效地找出无向图中的割点,在网络拓扑分析、通信网络健壮性分析等领域有广泛应用。

例子

在图 1 - 2, 2 - 3, 3 - 1 中,以 1 为根节点进行 DFS 遍历 ,1 不是割点。
因为从 1 开始 DFS,不管先访问 2 还是 3,1 在 DFS 树中都只有一个子树(实际上该图 DFS 树就是一条链 ) 。根据根节点是割点的判断条件(根节点在 DFS 树中至少有两个子节点时才是割点 ) ,不满足条件,所以 1 不是割点 。 从割点定义理解,去掉 1 后,剩下的 2 和 3 依然连通,图的连通分量未增加,也能说明 1 不是割点。

你可能感兴趣的:(图论,算法,深度优先)