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 思想,是最优解法
相关推荐
itzixiao17 分钟前
L1-067 洛希极限(10分)[java][python]
java·开发语言·算法
jinyishu_23 分钟前
链表经典OJ题
c语言·数据结构·算法·链表
葫三生28 分钟前
三生原理文章被AtomGit‌开源社区收录的意义探析?
人工智能·深度学习·神经网络·算法·搜索引擎·开源·transformer
AI进化营-智能译站31 分钟前
ROS2 C++开发系列15-模板实现通用算法|宏定义ROS2调试开关|一次编码适配多平台
java·c++·算法·ai
刀法如飞34 分钟前
Java数组去重的20种实现方式——指导AI解决不同问题的思路
java·算法·面试
良木生香40 分钟前
【C++初阶】STL——Vector从入门到应用完全指南(1)
开发语言·c++·神经网络·算法·计算机视觉·自然语言处理·数据挖掘
Brilliantwxx40 分钟前
【C++】String的模拟实现(代码实现与坑点讲解)
开发语言·c++·笔记·算法
憨波个1 小时前
【说话人日志】DOVER:diarization 输出融合算法
人工智能·算法·音频·语音识别·聚类
爱学习的张大1 小时前
具身智能论文问答(四):pi0
人工智能·算法
上弦月-编程1 小时前
指针编程:高效内存管理核心
java·数据结构·算法