相关笔记

概览

本节介绍 强连通分量(Strongly Connected Components, SCC) 的概念及 Kosaraju 算法,该算法利用两次 20.3 深度优先搜索 在 时间内找出有向图的所有强连通分量。核心知识点包括:

  • 强连通分量的定义:有向图中顶点间互相可达的最大子集
  • Kosaraju 算法三步走:①对 做 DFS 计算完成时间 ②计算转置图 ③按完成时间递减对 做 DFS
  • 分量图(condensation):将每个 SCC 缩为一个超顶点,得到的分量图是 20.4 拓扑排序 中的 DAG
  • 定理20.9:Kosaraju 算法的正确性
  • 引理20.10~20.14:构成正确性证明链的五个关键引理

知识结构总览

flowchart TD
    A["20.5 强连通分量"] --> B["基本概念"]
    A --> C["Kosaraju 算法"]
    A --> D["正确性证明链"]
    A --> E["分量图"]
    A --> F["应用与拓展"]

    B --> B1["强连通: u 可达 v 且 v 可达 u"]
    B --> B2["SCC: 最大强连通子集"]
    B --> B3["SCC 划分: 每个顶点恰属于一个 SCC"]

    C --> C1["第一步: DFS(G) 计算完成时间"]
    C --> C2["第二步: 计算 G^T"]
    C --> C3["第三步: 按完成时间递减 DFS(G^T)"]

    D --> D1["引理20.10: SCC 内顶点的完成时间区间"]
    D --> D2["引理20.11: 分量图的边与完成时间"]
    D --> D3["引理20.12: G^T 中的 SCC 树"]
    D --> D4["引理20.13: G^T 中根的 SCC"]
    D --> D5["引理20.14: G^T 中 DFS 树的根"]

    E --> E1["分量图 G^SCC 是 DAG"]
    E --> E2["分量图的拓扑排序"]

    F --> F1["Kosaraju vs Tarjan SCC"]
    F --> F2["PageRank 预处理"]
    F --> F3["编译器循环检测"]
    F --> F4["2-SAT 求解"]

核心概念

强连通与强连通分量

强连通(Strongly Connected)

在有向图 中,如果两个顶点 满足 可达 (存在从 到 的有向路径)且 可达 ,则称 和 是强连通的。

性质: 强连通关系是 上的一个等价关系(自反性、对称性、传递性均成立)。

强连通分量(Strongly Connected Component, SCC)

有向图 的一个强连通分量是一个最大顶点集合 ,使得 中任意两个顶点互相可达。

“最大”的含义: 不存在更大的顶点集合 使得 中所有顶点互相可达。SCC 是强连通等价关系的等价类。

强连通分量的直观理解

SCC 就是图中”紧密抱团”的一组顶点——组内任意两个顶点可以互相到达,但组外的顶点无法同时到达组内所有顶点并被组内所有顶点到达。

类比: 想象一个社交网络中的”朋友圈”。如果 A 能联系到 B,B 能联系到 C,C 也能联系到 A,那么 A、B、C 就形成一个”强连通分量”——他们之间可以互相传达消息。但如果 D 只能被 A 联系到,D 却联系不到 A,那么 D 不在这个分量中。

转置图

转置图(Transpose Graph)

给定有向图 ,其转置图 ,其中 。

直观理解: 就是将 中所有边的方向反转。 可以在 时间内从 的邻接表表示中构造出来。

转置图的重要性质

与 具有完全相同的 SCC。这是因为强连通关系是对称的:如果 可达 (在 中),那么在 中 可达 。因此 和 在 中强连通当且仅当它们在 中强连通。

Kosaraju 算法

Kosaraju 算法(1978)通过两次 DFS 在 时间内找出所有 SCC。

算法执行流程

  1. 对原图 G 调用 DFS,记录每个顶点的完成时间 f[v]
  2. 计算 G 的转置图 G^T(将所有边方向反转)
  3. 按完成时间递减的顺序,对 G^T 调用 DFS
  4. 第二次 DFS 中每棵 DFS 树就是一个强连通分量
flowchart TD
    A["开始:输入有向图 G"] --> B["第一次 DFS(G)<br/>记录每个顶点的完成时间 f[v]"]
    B --> C["计算转置图 G^T<br/>反转所有边的方向"]
    C --> D["按 f[v] 递减顺序排列顶点"]
    D --> E["取下一个顶点 u"]
    E --> F{"u 已被访问?"}
    F -- 是 --> G{"还有未处理的顶点?"}
    F -- 否 --> H["从 u 开始对 G^T 调用 DFS-VISIT"]
    H --> I["本次 DFS-VISIT 访问的所有顶点<br/>构成一个强连通分量"]
    I --> G
    G -- 是 --> E
    G -- 否 --> J["结束:输出所有强连通分量"]
STRONGLY-CONNECTED-COMPONENTS(G)
1  调用 DFS(G) 计算每个顶点的完成时间 f[v]
2  计算 G 的转置图 G^T
3  按照完成时间 f[v] 递减的顺序,对 G^T 调用 DFS
4  第三步中产生的每一棵 DFS 树就是一个强连通分量

Kosaraju 算法的核心直觉

为什么需要转置图?为什么按完成时间递减?

核心思想分为两步理解:

  1. 第一次 DFS(在 上):完成时间最晚的顶点一定位于某个”源 SCC”(source SCC)中——即分量图中没有入边的 SCC。这是因为源 SCC 中的顶点不会被其他 SCC 中的顶点”阻挡”,DFS 会深入到源 SCC 的最深处。

  2. 第二次 DFS(在 上):转置图 将源 SCC 变成了”汇 SCC”(sink SCC)。按完成时间递减处理,就先处理 的汇 SCC(即 的源 SCC),从中探索能到达的所有顶点——它们恰好构成一个完整的 SCC(因为在 中,源 SCC 变成了汇,不会被其他 SCC 的顶点”泄漏”到)。

类比: 想象水流从高处(源 SCC)流向低处。第一次 DFS 找到”最高处”的水源,第二次 DFS 在反转的图(水从低处流回高处)中从这些水源开始收集所有能流回的顶点——它们恰好形成一个 SCC。

分量图(Component Graph / Condensation)

分量图

给定有向图 ,其分量图 定义为:

  • 每个顶点 对应 的一个强连通分量
  • 边 当且仅当 中存在从 中某顶点到 中某顶点的边

分量图也称为 的缩图(condensation)。

引理(分量图是 DAG)

有向图 的分量图 是一个有向无环图(DAG)。

分量图的意义

分量图将任意有向图”简化”为 DAG。在分量图上可以应用 20.4 拓扑排序 的所有算法——例如对分量图做拓扑排序,就得到了 SCC 之间的”依赖顺序”。这是 Kosaraju 算法正确性的基础。


正确性证明链

Kosaraju 算法的正确性由五个引理(引理20.10~20.14)和一个定理(定理20.9)构成完整的证明链。下面逐一展开。

引理20.10(SCC 内顶点的完成时间区间)

引理20.10

设 和 是有向图 的两个不同的强连通分量。如果 中存在一条边 ,其中 ,,则 。

这里 表示分量 中所有顶点完成时间的最大值。

引理20.10 的直观含义

如果 SCC 有一条边指向 SCC ,则 的”完成时间”晚于 。这意味着在分量图的拓扑排序中, 排在 前面——分量图中从 到 有边,对应拓扑排序中 在前。

引理20.11(分量图中边的方向与完成时间)

引理20.11

设 和 是有向图 的两个不同的强连通分量,且分量图 中存在一条从 到 的边。则 。

引理20.12( 中的 SCC 树)

引理20.12

设 是有向图 的一个强连通分量, 是 中在 的第二次 DFS 中第一个被发现的顶点。则在 的 DFS 中,以 为根的 DFS 树恰好包含 中的所有顶点(不多不少)。

引理20.13( 中根的 SCC)

引理20.13

在 的第二次 DFS 中,每棵 DFS 树的根 对应的 SCC 满足:在 的分量图 中, 没有入边(即 是 中的源 SCC)。

引理20.14( 中 DFS 树的根对应源 SCC)

引理20.14

在 的第二次 DFS 中, 的分量图 中的源 SCC 对应的顶点会成为 DFS 树的根。

定理20.9(Kosaraju 算法的正确性)

定理20.9

STRONGLY-CONNECTED-COMPONENTS 过程正确地计算出有向图 的所有强连通分量。

证明链的逻辑结构

五个引理构成一条严密的逻辑链:

  • 引理20.10:建立 SCC 间完成时间的基本不等式(核心工具)
  • 引理20.11:将引理20.10 推广到分量图的边
  • 引理20.12:证明 的 DFS 树恰好对应一个 SCC(关键步骤)
  • 引理20.13:证明 DFS 树的根对应源 SCC
  • 引理20.14:证明源 SCC 一定会成为 DFS 树的根
  • 定理20.9:综合以上引理得出算法正确性

补充理解与拓展

Kosaraju 算法 vs Tarjan SCC 算法

补充:两种经典 SCC 算法的全面对比

来源: S. R. Kosaraju, “Analysis of a Simple Linear Time Path Finding Algorithm”, 1978; R. E. Tarjan, “Depth-First Search and Linear Graph Algorithms”, SIAM Journal on Computing, 1(2), 1972

比较维度Kosaraju 算法 (1978)Tarjan SCC 算法 (1972)
核心思想两次 DFS + 转置图一次 DFS + 栈
DFS 次数2 次1 次
是否需要转置图需要(第二步构造 )不需要
额外空间(转置图)(栈 + 数组)
时间复杂度
实现难度简单直观较复杂(需要维护栈和 lowlink)
历史地位教学经典历史更早,影响深远
实际使用竞赛编程(实现简单)需要单次遍历时

Tarjan 算法的核心思想: 在一次 DFS 中维护一个栈,记录当前正在探索的顶点。当发现某个顶点的”lowlink”值等于自身的发现时间时,弹出栈中从该顶点开始的所有顶点——它们构成一个 SCC。lowlink 值表示从当前顶点出发,通过树边和最多一条后向边/交叉边能到达的发现时间最小的顶点。

选择建议: 竞赛编程中 Kosaraju 更受欢迎(代码短、不易出错);需要最小化空间或只需要一次图遍历时,Tarjan 更优。

SCC 的实际应用

补充:强连通分量的工程应用

来源: 综合整理

  1. PageRank 预处理:Google 的 PageRank 算法在计算网页排名前,先将网页图分解为 SCC。每个 SCC 内的网页具有相同的 PageRank 值(因为互相可达),只需对分量图(DAG)计算 PageRank,大幅减少计算量。

  2. 编译器循环检测:编译器的控制流分析中,基本块之间的跳转关系构成有向图。检测 SCC 可以识别循环结构(loop),这是编译器优化的基础——循环不变量外提、强度削弱等优化都依赖循环的识别。

  3. 2-SAT 求解:2-SAT 问题(每个子句恰好包含两个文字的布尔可满足性问题)可以通过构造”蕴含图”(implication graph),求 SCC 来判定可满足性。如果某个变量 和 属于同一个 SCC,则 2-SAT 不可满足;否则可满足。

  4. 社交网络社区发现:在 Twitter/X 等社交平台中,用户之间的关注关系(有向)可以分解为 SCC。大型 SCC 代表”紧密社区”——社区内信息可以传播到每个成员。

分量图的性质和应用

补充:分量图(Condensation)的深层性质

来源: 教材第20.5节

分量图 是 DAG,因此可以应用 DAG 上的所有算法:

  • 拓扑排序:对 做拓扑排序,得到 SCC 之间的依赖顺序
  • 最长路径: 上的最长路径对应原图中”最长 SCC 链”
  • 传递闭包: 的传递闭包可以通过对 计算传递闭包,再扩展到 SCC 内部得到

分量图的顶点数: 的顶点数等于 的 SCC 数量。对于”稀疏 SCC”的图(大多数 SCC 很小), 远小于 ,在其上操作更高效。

半连通性判定(习题22.5-7)

补充:半连通性

来源: 习题22.5-7

有向图 是半连通的(semiconnected),如果对任意两个顶点 ,要么 可达 ,要么 可达 。

判定算法:

  1. 计算 的所有 SCC
  2. 构造分量图
  3. 对 做拓扑排序
  4. 检查拓扑排序中相邻 SCC 之间是否都有边(即 的拓扑排序中,每对相邻顶点之间都有边)
  5. 如果是,则 半连通;否则不半连通

时间复杂度: 。

正确性: 半连通当且仅当 的拓扑排序中每对相邻顶点之间有边。如果存在相邻 SCC 之间没有边,则 中顶点不可达 中顶点(因为拓扑排序中 排在 前面, 排在 后面的所有 SCC 前面,如果没有从 到 的边,则 中顶点无法到达 中顶点),且 中顶点也不可达 中顶点(因为 排在 后面),违反半连通性。


易混淆点与辨析

误区:无向图的连通分量和有向图的强连通分量是同一概念

错误理解: “强连通分量就是无向图的连通分量在有向图中的推广。”

正确理解: 无向图的连通分量只要求顶点之间有路径(无向路径),而有向图的强连通分量要求双向可达。有向图还有”弱连通分量”的概念——忽略边的方向后得到的连通分量。

关系: 每个 SCC 是弱连通分量的一个子集。一个弱连通分量可以包含多个 SCC。

误区:单个顶点不构成 SCC

错误理解: “SCC 至少要有两个顶点。”

正确理解: 单个顶点本身就是一个 SCC(平凡 SCC)。如果一个顶点不与任何其他顶点强连通,它自己就构成一个 SCC。在 Kosaraju 算法中,每个顶点恰好属于一个 SCC——要么是平凡 SCC,要么是非平凡 SCC。

误区:Kosaraju 算法中第二次 DFS 的顺序不重要

错误理解: “第二次 DFS 可以按任意顺序处理顶点。”

正确理解: 第二次 DFS 必须按第一次 DFS 的完成时间递减顺序处理顶点。这是算法正确性的关键——只有按这个顺序,才能保证先处理 中的源 SCC(在 中是汇 SCC),从而正确地”剥离”每个 SCC。

反例: 如果按完成时间递增处理,可能先处理非源 SCC,导致 DFS 树跨越多个 SCC,无法正确分离。

误区:转置图改变了 SCC 的结构

错误理解: “转置图 的 SCC 和 的 SCC 不同。”

正确理解: 和 具有完全相同的 SCC。强连通关系是对称的( 可达 且 可达 ),反转所有边的方向不改变这种双向可达性。转置图改变的只是 SCC 之间的边方向。


习题精选

题号题目描述难度
22.5-1图 20-10 中有向图的 SCC 有哪些?⭐
22.5-2给出一个 时间算法,判断有向图 是否只有一个 SCC⭐
22.5-3给出一个 时间算法,将有向图 的每个 SCC 缩为单个顶点,构造分量图⭐⭐
22.5-4证明:如果 的分量图中有从 SCC 到 SCC 的路径,则 ⭐⭐
22.5-5给出一个 时间算法,计算有向图 的分量图中每个 SCC 的入度和出度⭐⭐
22.5-6给出一个 时间算法,将有向图 的顶点划分为”树边 SCC”、“前向边 SCC”、“交叉边 SCC”和”后向边 SCC”⭐⭐⭐
22.5-7给出一个 时间算法,判断有向图 是否是半连通的⭐⭐⭐

由引理20.11,对每条边 ,有 。

因此 ,即 。

解题思路提示

  • 22.5-2:判断”只有一个 SCC”等价于”任选一个顶点能到达所有其他顶点且被所有其他顶点到达”。利用 和 各做一次 DFS/BFS 即可。
  • 22.5-3:先计算 SCC(Kosaraju),再遍历所有边,将端点属于不同 SCC 的边映射到分量图上。
  • 22.5-7:半连通性是”弱化的强连通”——不要求双向可达,只要求单向可达。利用分量图的拓扑排序,将问题简化为检查相邻 SCC 之间是否有边。

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 13Graph Search, Strongly Connected Componentshttps://www.youtube.com/watch?v=za-BT3C4NN8Kosaraju 算法完整讲解
WilliamFisetKosaraju’s Algorithmhttps://www.youtube.com/watch?v=RpgcYiky7QM含逐步演示和代码
Abdul BariStrongly Connected Componentshttps://www.youtube.com/watch?v=Rs6DXyWpWrI直观的 SCC 概念讲解
Tarjan’s SCC by WilliamFisetTarjan’s Algorithmhttps://www.youtube.com/watch?v=wUgWX0nc4OMTarjan 算法对比

教材原文

CLRS 第4版 20.5节原文

In this section, we show how to use depth-first search to decompose a directed graph into its strongly connected components. This section assumes some familiarity with the concept of a strongly connected component, which was defined in Section B.4.

The algorithm we present uses two depth-first searches. The first depth-first search is performed on the original graph , and the second is performed on the transpose of . The transpose of a directed graph is the graph , where . That is, is with all its edges reversed. Given an adjacency-list representation of , the time to create is .

STRONGLY-CONNECTED-COMPONENTS() 1 call DFS() to compute finishing times for each vertex 2 compute 3 call DFS(), but in the main loop of DFS, consider the vertices in order of decreasing (as computed in line 1) 4 output the vertices of each tree in the depth-first forest formed in line 3 as a separate strongly connected component

Theorem 20.9: The STRONGLY-CONNECTED-COMPONENTS procedure correctly computes the strongly connected components of the directed graph .

The intuition behind why the algorithm works is that the first depth-first search identifies the “source” strongly connected components, and the second depth-first search, on the transpose, peels off the strongly connected components one by one.


参见Wiki: 第20章_基本图算法-章节汇总 | 20.3 深度优先搜索 | 20.4 拓扑排序

第20章-基本图算法 #学习/算法导论/基本图算法/强连通分量