Articulation Point(割点)算法详解

🧠 Articulation Point(割点)算法详解

割点(Articulation Point) 是图论中的一个非常经典的问题:

在无向图中,如果删除某个顶点会导致图不连通,则该顶点就是一个割点。

本篇文章将基于 TheAlgorithms/Go 实现的版本,对代码进行完整中文解读,并详细解释其中的 DFS(深度优先搜索)与"时间戳"逻辑,从而帮助读者彻底理解割点的判断方式。


📌 一、Articulation Point(割点)是什么?

我们在图中找一个点,如果删掉这个点(以及与它相连的边),图会被拆成多个不连通部分,那么这个点就是"割点"。

例如:

复制代码
A - B - C - D
      |
      E

顶点 B 是割点,因为删除 B 后,E 会与其他点断开。

割点广泛用于:

  • 网络可靠性分析
  • 社交网络关键人物分析
  • 通信链路的冗余分析
  • 强连通性算法
  • 拓扑结构弱点定位

📌 二、算法基础:DFS + 时间戳(Tarjan 思想)

算法核心思想:

  1. 对每个节点 DFS 编号,得到 discovery time
  2. 同时计算每个节点能回退到的最早时间戳 low-link(earliest discovery)
  3. 如果满足:

low[child] >= disc[parent]

parent 是一个割点

代表:子节点无法通过回边访问到父节点之前的节点 → 割裂发生

特殊规则:

  • 根节点如果有 两个及以上的子树 才是割点

📌 三、代码中文翻译(逐行解释版)

下面是你给出的代码的中文翻译版+中文注释。


✨ 代码中文翻译与注释

go 复制代码
// Package graph 提供图结构分析相关的算法。
package graph

import "github.com/TheAlgorithms/Go/math/min"

// apHelper 用于在搜索割点过程中存储辅助数据。
type apHelper struct {
    isAP              []bool // 标记每个顶点是否为割点
    visited           []bool // DFS 是否访问过
    childCount        []int  // 每个顶点的子节点数(仅 DFS 树意义)
    discoveryTime     []int  // DFS 遍历时节点的发现时间戳
    earliestDiscovery []int  // 能回退到的最早时间(low-link 值)
}

// ArticulationPoint 用于寻找图中的割点。
// 返回一个布尔切片,表示每个顶点是否是割点。
// 时间复杂度:O(|V| + |E|)
// 空间复杂度:O(|V|)
func ArticulationPoint(graph *Graph) []bool {
    // time 用于记录 DFS 访问时间戳
    time := 0

    // apHelper 结构初始化
    apHelperInstance := &apHelper{
        isAP:              make([]bool, graph.vertices),
        visited:           make([]bool, graph.vertices),
        childCount:        make([]int, graph.vertices),
        discoveryTime:     make([]int, graph.vertices),
        earliestDiscovery: make([]int, graph.vertices),
    }

    // 从根节点 0 开始 DFS
    articulationPointHelper(apHelperInstance, 0, -1, &time, graph)

    // 根节点如果只有一个孩子,则不能视为割点
    if apHelperInstance.childCount[0] == 1 {
        apHelperInstance.isAP[0] = false
    }

    return apHelperInstance.isAP
}

// articulationPointHelper 通过 DFS 遍历图并标记割点。
// 更新 childCount、discoveryTime 和 earliestDiscovery。
func articulationPointHelper(
    apHelperInstance *apHelper,
    vertex int,
    parent int,
    time *int,
    graph *Graph,
) {
    apHelperInstance.visited[vertex] = true

    // 设置发现时间和最早可回退时间
    apHelperInstance.discoveryTime[vertex] = *time
    apHelperInstance.earliestDiscovery[vertex] = *time
    *time++

    for nextVertex := range graph.edges[vertex] {
        // 跳过父节点
        if nextVertex == parent {
            continue
        }

        if apHelperInstance.visited[nextVertex] {
            // 遇到回边,更新 low-link
            apHelperInstance.earliestDiscovery[vertex] = min.Int(
                apHelperInstance.earliestDiscovery[vertex],
                apHelperInstance.discoveryTime[nextVertex],
            )
            continue
        }

        // 作为 DFS 子节点计数
        apHelperInstance.childCount[vertex]++
        articulationPointHelper(apHelperInstance, nextVertex, vertex, time, graph)

        // DFS 完成后更新 earliestDiscovery
        apHelperInstance.earliestDiscovery[vertex] = min.Int(
            apHelperInstance.earliestDiscovery[vertex],
            apHelperInstance.earliestDiscovery[nextVertex],
        )

        // 满足割点条件
        if apHelperInstance.earliestDiscovery[nextVertex] >= apHelperInstance.discoveryTime[vertex] {
            apHelperInstance.isAP[vertex] = true
        }
    }
}

📌 四、算法关键逻辑解析

🧩 1. discoveryTime(发现时间)

DFS 每访问一个节点,会赋予一个递增时间戳。

例如:

复制代码
0 → 1 → 3 → 4 → 2

发现时间可能是:

复制代码
disc = [0, 1, 4, 2, 3]

表示该节点通过"回边"能跳到的最早祖先。

计算方式:

go 复制代码
earliest[v] = min(earliest[v], discovery[u])

或在 DFS 回溯时更新:

go 复制代码
earliest[parent] = min(earliest[parent], earliest[child])

🧩 3. 割点判定条件

非根节点

如果:

复制代码
low[child] >= disc[parent]

则 parent 是割点。

因为 child 无法回到 parent 之前 → 删除 parent 会断链。


🧩 4. 根节点单独判断

根节点必须有 两个及以上子树 才是割点。


📌 五、图示理解(低值回退)

节点 A → B → C

如果 C 有一条边连回 A,则 low[C] 会是 disc[A],因此 B 不是割点

复制代码
A
| \
|  C
|
B

📌 六、总结

名称 含义
discoveryTime DFS 第几次访问到
earliestDiscovery(low-link) 能回退到的最早祖先
childCount 根节点割点判定专用
isAP 是否为割点

算法性能:

  • 时间复杂度:O(V + E)
  • 空间复杂度:O(V)
  • 使用 DFS + Tarjan 思想,是最优解法
相关推荐
热心市民小刘05052 小时前
11.18二叉树中序遍历(递归)
数据结构·算法
brave and determined2 小时前
可编程逻辑器件学习(day24):异构计算:突破算力瓶颈的未来之路
人工智能·嵌入式硬件·深度学习·学习·算法·fpga·asic
未若君雅裁2 小时前
LeetCode 18 - 四数之和 详解笔记
java·数据结构·笔记·算法·leetcode
2501_941111462 小时前
高性能计算集群部署
开发语言·c++·算法
AIpanda8882 小时前
AI销冠系统和AI提效软件系统是什么?主要特点和应用场景有哪些?
算法
普通网友3 小时前
模板编译期机器学习
开发语言·c++·算法
普通网友3 小时前
C++与机器学习框架
开发语言·c++·算法
普通网友3 小时前
C++安全编程指南
开发语言·c++·算法
鱼骨不是鱼翅3 小时前
力扣hot100----1day
python·算法·leetcode·职场和发展