《强连通分量(tarjan算法)》基础概念

一、算法概述

  1. 定义:Tarjan算法是一种用于在有向图中求解强连通分量(Strongly Connected Component, SCC)的高效算法。强连通分量指有向图中任意两顶点互相可达的最大子图。
  2. 核心思想:基于深度优先搜索(DFS),利用时间戳和栈结构,通过追踪顶点的访问顺序和回溯能力,识别强连通分量。
  3. 应用场景
    • 有向图缩点(将每个强连通分量缩为一个点,构建DAG)
    • 电路设计中的环路检测
    • 任务调度中的依赖关系分析

二、算法思路

  1. 关键变量
    • dfn[u]:顶点u的首次访问时间戳
    • low[u]:顶点u能回溯到的最早时间戳(通过有向边)
    • inStack[u]:标记顶点u是否在栈中
    • stack:存储当前DFS路径上的顶点
  2. 核心逻辑
    • 对每个未访问顶点启动DFS,记录时间戳dfn[u]low[u]
    • 若顶点v已访问且在栈中,更新low[u]min(low[u], dfn[v])
    • dfn[u] == low[u]时,栈中从u开始的连续顶点构成一个强连通分量
  3. 强连通分量判定:当一个顶点的时间戳等于其能回溯到的最早时间戳时,该顶点是所在强连通分量的根节点,此时从栈中弹出直至该顶点,构成一个SCC。

三、伪代码实现

1. 类定义与数据结构

类 TarjanSCC:
    私有变量:
        n: 整数  # 图的顶点数
        adj: 二维数组  # 邻接表,adj[u]存储u的所有出边
        dfn: 数组  # 顶点访问时间戳
        low: 数组  # 顶点能回溯到的最早时间戳
        inStack: 数组  # 标记顶点是否在栈中
        st: 栈  # 存储当前路径顶点
        timeStamp: 整数  # 时间戳计数器
        sccId: 数组  # 顶点所属的强连通分量编号
        sccCount: 整数  # 强连通分量数量
        sccNodes: 二维数组  # 每个强连通分量包含的顶点列表

    公有方法:
        构造函数 TarjanSCC(_n):
            n = _n
            adj 调整大小为 n+1
            dfn 调整大小为 n+1,初始化为 0
            low 调整大小为 n+1,初始化为 0
            inStack 调整大小为 n+1,初始化为 false
            sccId 调整大小为 n+1,初始化为 0
            timeStamp = 0
            sccCount = 0
            sccNodes 清空

        方法 addEdge(u, v):
            将 v 添加到 adj[u] 中

        方法 solve():
            对 i 从 1 到 n:
                如果 dfn[i] 为 0:
                    tarjanDFS(i)

        方法 getSCCCount():
            返回 sccCount

        方法 getSCCId(u):
            返回 sccId[u]

        方法 getSCCNodes(sccId):
            返回 sccNodes[sccId]

    私有方法:
        方法 tarjanDFS(u):
            dfn[u] = low[u] = ++timeStamp
            将 u 压入栈 st
            inStack[u] = true

            对 i 从 0 到 adj[u].长度-1:
                v = adj[u][i]
                如果 dfn[v] 为 0:
                    tarjanDFS(v)
                    low[u] = min(low[u], low[v])
                否则如果 inStack[v] 为 true:
                    low[u] = min(low[u], dfn[v])

            如果 dfn[u] == low[u]:
                新建空数组 scc
                v = -1
                循环:
                    v = st.top()
                    st.pop()
                    inStack[v] = false
                    sccId[v] = sccCount
                    将 v 添加到 scc 中
                直到 v == u
                将 scc 添加到 sccNodes 中
                sccCount++

2. 主程序示例

输入 n, m  # 顶点数和边数
创建 TarjanSCC 实例 ts(n)
对 i 从 0 到 m-1:
    输入 u, v
    ts.addEdge(u, v)
ts.solve()
输出 "强连通分量数量: " + ts.getSCCCount()
对 i 从 0 到 ts.getSCCCount()-1:
    输出 "SCC " + i + ": " + ts.getSCCNodes(i)

四、算法解释

1. 初始化阶段

  • 构造函数中初始化邻接表、时间戳数组、栈和强连通分量记录结构,为后续计算做准备。

2. DFS遍历与时间戳更新

  • tarjanDFS(u)中,首次访问u时记录dfn[u]low[u]为当前时间戳。
  • 遍历u的所有邻接顶点v
    • v未访问,递归访问v,并通过low[v]更新low[u](说明u可通过v回溯到更早的顶点)。
    • v已访问且在栈中,说明存在环,通过dfn[v]更新low[u]v在当前路径上)。

3. 强连通分量识别

  • dfn[u] == low[u]时,u是当前强连通分量的根节点。
  • 从栈中弹出顶点直至u,这些顶点构成一个强连通分量,记录其编号和包含的顶点。

4. 示例演示

  • 图结构:有向图顶点1→2,2→3,3→1,2→4,4→5,5→4
  • DFS过程
    1. 访问1,dfn[1]=low[1]=1,压栈。
    2. 访问2,dfn[2]=low[2]=2,压栈,访问3。
    3. 访问3,dfn[3]=low[3]=3,压栈,发现3→1,1在栈中,low[3] = min(3, dfn[1])=1
    4. 返回2,low[2] = min(2, low[3])=1,访问4,dfn[4]=low[4]=4,压栈,访问5。
    5. 访问5,dfn[5]=low[5]=5,发现5→4,4在栈中,low[5] = min(5, dfn[4])=4
    6. 返回4,low[4] = min(4, low[5])=4,此时dfn[4]==low[4],弹出4、5,形成SCC{4,5}。
    7. 返回2,low[2]=1,返回1,dfn[1]==low[1],弹出1、2、3,形成SCC{1,2,3}。

五、复杂度分析

  1. 时间复杂度
    • 每个顶点和边仅被访问一次,时间复杂度为O(n + m),其中n为顶点数,m为边数。
  2. 空间复杂度
    • 邻接表存储边:O(m)
    • 数组和栈存储状态:O(n)
    • 总空间复杂度为O(n + m)。
  3. 算法优势
    • 线性时间复杂度,适用于大规模有向图的强连通分量求解。
    • 仅使用常数额外空间,空间效率高。
  4. 应用扩展
    • 缩点后可在DAG上进行拓扑排序、最短路径等操作。
    • 结合2-SAT问题求解布尔表达式的可满足性。

  本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值