Tarjan 求有向图的强连通分量

简介: Tarjan算法是一种用于查找有向图中强连通分量的高效方法。作者分享了自己的笔记,强调了网上解释的不清晰性,并引用了OI Wiki的资源。算法维护每个节点的`dfn`(深度优先搜索顺序)和`low`(能到达的最小DFS序)。通过遍历节点和其邻接节点,更新`low`值,识别环路。当`dfn[x] == low[x]`,表示找到一个强连通分量。代码示例展示了具体的实现细节。

重温Tarjan, 网上看了许多博客感觉都讲的不清楚. 故传上来自己的笔记, 希望帮到大家.

提到的一些概念可以参考 oi wiki, 代码也是 oi wiki 的, 因为我不认为我能写出比大佬更好的代码了.


强连通分量: 有向图的最大强连通子图 ( 有向图中任意两点可达 )

  • Tarjan
  1. 对每个结点维护:
  • dfn[x]: 当前节点的 dfs 序.
  • low[x]: x 向下搜索能到达的最小 dfs 序.
  1. 更新 low:
  1. v 未被访问过: 初始 low[v] = dfn[v].v 入栈. 回溯时用 low[v] 更新它的 fa 的 low[ ].
  2. v 被访问过, 且还在栈中: 用 dfs[v] 更新 fa 的 low.
  3. v 被访问过, 不在栈中: 说明这是一个 fa 到 v 的单向访问, 跳过.
  1. 获取答案:能让dfn[x] > low[x], 只有当 X 的子树中某个节点 C 有一条横向边连接到一棵已遍历过的子树一条返祖边连接到的祖先一条横向边连接到一棵已遍历过的子树一条返祖边连接到的祖先{1.一条横向边连接到一棵已遍历过的子树 𝐴2.一条返祖边连接到 𝑋 的祖先 𝑥𝑓𝑎.
  1. 横向边: 说明 A 没有连接到 C 的边, 否则在之前 C 就被遍历了, 轮不到 X 来遍历. 就用是否 C 在栈中来排除这个情况, 子树 A 中的所有强连通分量之前已经出栈过了( 看代码的实现 ).
  2. 返祖边: 说明 xfa -> x -> c -> xfa 形成环, 在同一个强连通子图( 我们知道, 强连通图是许多环嵌套成的 ). 而且这个子图的根是 xfa 满足 dfn[xfa] = low[xfa].
  1. 此时栈中进来过三类节点 :
    在的子树中属于上述循环的在同一个强连通子图不在同一个强连通子图那递归的讲在之前就因为属于某个在的子树中而被踢出栈了不在的子树中即在已遍历过的子树中在栈中的位置一定在的下面在的子树中属于上述循环的在同一个强连通子图不在同一个强连通子图那递归的讲在之前就因为属于某个在的子树中而被踢出栈了不在的子树中即在已遍历过的子树中在栈中的位置一定在的下面{1. 在 𝑥 的子树中{1. 属于上述 𝑥𝑓𝑎 循环的, 在同一个强连通子图.2. 不在同一个强连通子图, 那递归的讲, 在之前就因为属于某个 𝑥𝑓𝑎′ (在 𝑋 的子树中),而被踢出栈了.2.不在 𝑥 的子树中(即在已遍历过的子树中), 在栈中的位置一定在 𝑥 的下面.
    故, 回溯时若节点符合 dfn[x] = low[x], 说明当前节点是它所属连通块的最小节点. 栈里它之上所有点都是一个强连通块.

代码:

const int Maxn = 1e5 + 10;
    
    int dfn[Maxn], low[Maxn], dfncnt, s[Maxn], in_stack[Maxn], tp;
    int scc[Maxn], sc;  // 结点 i 所在 SCC 的编号
    int sz[Maxn];       // 强连通 i 的大小
    
    void tarjan(int u) {
        low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
        for (int i = head[u]; i; i = eg[i].nex) {
            const int &v = eg[i].to;
            if (!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (in_stack[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (dfn[u] == low[u]) {
            ++sc;
            while (s[tp] != u) {
                scc[s[tp]] = sc;
                sz[sc]++;
                in_stack[s[tp]] = 0;
                --tp;
            }
            scc[s[tp]] = sc;
            sz[sc]++;
            in_stack[s[tp]] = 0;
            --tp;
        }
    }
相关文章
|
Windows
关于 Qt设置置顶窗口,透明部分显示黑色底色(已设置透明窗口) 的解决方法
关于 Qt设置置顶窗口,透明部分显示黑色底色(已设置透明窗口) 的解决方法
关于 Qt设置置顶窗口,透明部分显示黑色底色(已设置透明窗口) 的解决方法
|
Ubuntu Linux
几种Linux系统切换内核启动顺序方法
几种Linux系统切换内核启动顺序方法
|
10月前
|
机器学习/深度学习 人工智能 并行计算
《算力觉醒!ONNX Runtime + DirectML如何点燃Windows ARM设备的AI引擎》
ONNX Runtime 是一个跨平台高性能推理引擎,可运行不同框架转为 ONNX 格式的模型,通过深度分析与优化计算图提升效率。在 Windows ARM 设备上,它针对硬件特性优化,结合微软 DirectML API,充分利用 GPU 并行计算能力加速 AI 推理。两者深度融合,灵活调整参数以满足实时性或高精度需求,在文本分类、图像识别、智能安防等领域显著提升性能,为多样化应用场景提供高效支持。
429 16
|
机器学习/深度学习 存储 编解码
什么是图像噪声?是如何产生的?图像去噪技术都有哪些?
图像噪声是在图像采集、传输和处理过程中产生的像素值异常现象,主要由光子计数统计、电子偏移和放大器噪声等因素引起。噪声影响图像质量,降低信噪比,使特征难以识别。图像去噪技术包括传统方法(如空间域滤波、频域滤波、图像压缩和超糅合)和基于深度学习的方法(如卷积神经网络、残差网络和生成对抗网络),旨在有效去除噪声,提高图像质量。
|
安全 数据处理 数据中心
不同类型的光纤电缆及其应用特点
【10月更文挑战第22天】
766 6
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
移动开发 测试技术 开发工具
探索移动应用开发之旅:从概念到实战
【8月更文挑战第29天】在数字化时代的潮流中,移动应用已成为我们日常生活和工作中不可或缺的一部分。本文将引导读者了解移动应用的开发流程,包括设计思路、开发工具的选择以及操作系统的基本知识。我们将通过实际案例,深入探讨如何将一个想法转化为现实中的应用,同时确保内容的通俗易懂和结构的清晰性,为初学者提供一扇进入移动开发世界的大门。
132 1
|
监控 测试技术 数据安全/隐私保护
新产品测试流程如何?
新产品测试流程如何?【10月更文挑战第10天】
773 0
|
安全 算法 Go
有向图的强联通分量(SCC)Tarjan算法
有向图的强联通分量(SCC)Tarjan算法
696 0
|
Rust 编译器 Linux
Rust编译过程讲解与开发环境准备
目前主流编译平台有,GNU、MSVC、LLVM。因为rustc调用了llvm,因此我们以LLVM为例,我们从C语言的编译过程聊,再对比Rust,看它们的编译过程有何差异。
687 3

热门文章

最新文章