强连通分量Tarjan算法学习笔记
定义
一个有向图 强连通,指的是 的任意两个结点连通。强连通分量 SCC
指的是极大的强连通子图。
Tarjan 的做法
首先来看一个 DFS 树,图源 OI Wiki
其中所有的边都是原有向图的边。从 号节点开始跑 DFS ,我们把这些边分为两类:
- 树边,也就是图中黑色的边,构成了 DFS 树本树;
- 返祖边,也就是图中红色的边,返祖边的出现可能会带来环;
- 横叉边,也就是图中红色的边,在 DFS 树中,在同一层之间连接点的边即为横叉边;
- 前向边,也就是途中绿色的边,在 DFS 树种主打一个“跳级”操作。
那么,你可以思考到,如果节点 是某个强连通分量在搜索树种遇到的第一个节点,那么其余结点一定在以 为根的子树中。
反证法:假设有个结点 在该强连通分量中但是不在以 为根的子树中,那么 到 的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和 是第一个访问的结点矛盾了。得证。
Tarjan 还另外对每个维护了两个变量:
- :深度优先搜索时,结点 被搜到的次序;
- :从 开始搜索,能搜索到的深度最低的结点 —— 显然,如果 上有一个返祖边,肯定是跟着返祖边往上跑;再就是寄希望于自己的孩子能有一个返祖边往回跑,否则就只能是他自身。
一个结点的子树内结点的 都大于该结点的 。
并且可以观察到:从根开始的一条路径上的 严格递增, 严格非降。
那么接下来就是 Tarjan 算法的流程:
按照深度优先搜索的顺序对所有的结点进行搜索,维护每个节点的 和 ,并让这个结点入栈。
在搜索过程中,对于 与其相邻的非父结点 ,只可能有以下三种情况
- 未被访问过。继续对 进行 DFS ,在回溯的过程中用 更新 。理由是:如果 能回到比 更早的结点,那 一定能通过走 这条路径走到更早的结点;
- 被访问过,且还在栈中,那么就用 更新 ;
- 被访问过,已不在栈中:说明 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。
实现如下
应用
把一个图的强连通分量找出来,可以把它缩成一个点。
于是这张图就变成了一个 DAG 。
缩点的一个经典应用如下
求一条路径,可以经过重复结点,要求经过的不同结点数量最多。
容易想到,如果我们能走一个环,那走一个环一定很优,可以使用 Tarjan 缩点,然后跑一个最长路即可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程