Jerry's Blog

Back

我们可以使用 tarjan 来求取一张有向图中强连通分量的个数,以及每个强连通分量包含的点的编号.

强连通#

在一张有向图中,如果存在由一些点组成的集合,满足这个集合中的点都可以两两连通,那么称这些点和边为原图的强连通子图

如果对于一个强连通子图 TT,不存在任意一个其他点在加入 TT 后,能够仍然保持强连通子图的性质,那么称 TT 为原图的强连通分量.通俗来说,强连通分量就是极大的强连通图.

DFS 生成树#

在有向图上,以任意一个节点为根节点进行一次 DFS,我们可以使用DFS 生成树记录其过程.对于一个节点 A,其子节点 B 满足没有在图上出现过、且有一条边从 A 连接到 B.

DFS 生成树可以辅助将图转换为树 + 边.

示例:

flowchart LR
    A((A)) --> B((B))
    A --> D((D))
    D --> A
    B --> C((C))
    D --> E((E))
    E --> F((F))
    E --> H((H))
    F --> G
    B --> E
    E --> G((G))

对于上面这条边,如果以 A 为根节点建 DFS 生成树,一种是这样的:

flowchart TD
    A((A)) --> B((B))
    B --> C((C))
    %% cross
    B -.-> E((E))
    E --> H((H))
    E --> F((F))
    F --> G((G))
    %% forward
    E -.-> G
    A --> D((D))
    D --> E
    %% back
    D -.-> A
    linkStyle 2 stroke:blue
    linkStyle 6 stroke:green
    linkStyle 9 stroke:red

在这张图中,有四种边:

  • 树边:原有的,构成生成树的边 除此以外,还有三种额外的边(假定这些边是从节点 uu 连接到节点 vv 的):
  • 反祖边vvuu 的祖先节点.
  • 前向边uu 不是 vv 的父节点,但是是 vv 的祖先节点.
  • 横叉边:不是树边、反祖边前向边的边.

不难发现,每一个强连通分量都至少有一条反祖边

如何在 DFS 的同时判断一条边是什么边?

我们需要定义一些数据:

  • bool in_stack[i] 表示点 ii 是否在当前 DFS 的路径上.
  • bool vis[i] 表示点 ii 是否被访问过.
in_stack 成立in_stack 不成立
vis[i] 成立反祖边前向边横叉边
vis[i] 不成立不可能树边

Tarjan 求强连通分量#

Tarjan 只需要进行一次 DFS 搜索就能办到.

为了解决问题,我们定义了以下数据:

  • bool in_stack[i] 表示点 ii 是否在当前 DFS 的路径上.
  • stack<int> s 当前强连通分量中的节点编号.
  • int dfn[i]ii 个点先序遍历到达时间.
  • int low[i]ii 个点通过当前子树内返祖边(即不经过其父节点)回到的最小时间.
  • 无需定义 vis[i],应为 vis[i] 等效于 dfn[i] == 0

不难发现:

  1. 祖先 dfn 一定小于当前 dfn
  2. 如果一个点 uu 能够到达一个点 vv,点 u 的 dfn 大于点 v 的 dfn,那么这样的一组构成了一个强连通分量

对于到达一个新的节点 uu,我们会进行以下操作:

  • 初始化操作
    • 初始化 dfn[u]
    • 初始化 low[u] = dfn[u]
    • 将当前节点入栈 s,并且标记 in_stack
  • 遍历所有起点为 uu,终点为 vv 的边
    • 如果这条边是反祖边,将 low[u] 进行更新.
    • 如果这条边是树边,DFS 搜索节点 vv,然后将 low[u] 进行更新.
    • 如果这条边是横叉边前向边,那么说明 vv 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作.
  • 检查当前节点是不是一个新的强连通分量的根节点(low[u] == dfn[u]
    • 如果是,那么当前 s 中存储的就是一个强连通分量.清空 s,初始化 in_stack 来处理下一个强连通分量.

时间复杂度分析#

  • 初始化操作:O(n)O(n)
  • 遍历边操作:O(m)O(m)
  • 检查节点是不是强连通分量的根节点:O(n)O(n)

综合一下,时间复杂度 O(m+n)O(m+n)

标程#

下面这个程序求解了图中的强连通分量数量和每一个强连通分量的组成部分.

强连通分量
https://blog.jerrylab.top/blog/graph/scc
Author Jerry
Published at 2026年3月3日