博客
关于我
20行代码实现,使用Tarjan算法求解强连通分量
阅读量:489 次
发布时间:2019-03-06

本文共 2679 字,大约阅读时间需要 8 分钟。

今天是算法数据结构专题的第36篇文章,我们将继续探讨强连通分量分解的另一个经典算法——Tarjan算法。这一算法与Kosaraju算法相比,虽然复杂度相同,但实现更为高效,且常数较小,更加适合在竞赛环境中应用。

强连通分量分解的核心目标是对一个图中所有强连通分量进行识别。强连通分量是指图中存在相互之间可以到达的点的集合。换句话说,强连通分量中任意两个点之间都可以通过一条路径相互到达。

在上一篇文章中,我们讨论了Kosaraju算法,它通过图的反转和两次深度优先搜索(DFS)来完成强连通分量的分解。与Kosaraju算法不同,Tarjan算法只需要一次递归调用,能够更高效地完成任务。Tarjan算法的名字来源于它的发明者Robert Tarjan,而不是因为其复杂度更高或其他原因。

Tarjan算法的主要思想可以分为以下几个关键步骤:

时间戳机制

Tarjan算法首先引入了一个时间戳机制。这个机制通过递归深度来为每个访问的点赋予一个唯一的时间戳。时间戳的作用是记录访问顺序,帮助识别强连通分量的边界。

具体来说,我们维护一个全局的时间戳变量stamp,每当我们访问一个点时,就将该点的时间戳记录下来,并将变量递增1。例如:

stamp = 0stamp_dict = {}def dfs(u):    stamp_dict[u] = stamp    stamp += 1    for v in Graph[u]:        if not dfn[v]:            dfs(v)

通过时间戳,我们可以清晰地看到每个点被访问的顺序。时间戳越小,说明该点在搜索树中的位置越靠上。

低值(Low)机制

Tarjan算法的另一个关键机制是low值。对于每个点ulow[u]表示u能够连通到的所有点的时间戳中的最小值。这个值的意义在于,low[u]越小,说明u能够连通到的点越高在搜索树中。

例如,假设我们有一个图,其中节点1、2、3、4构成一个强连通分量,而节点5、6、7构成另一个强连通分量。通过时间戳和low值的比较,我们可以确定哪些点属于同一个强连通分量。

栈的使用

在递归过程中,我们维护一个栈来记录当前的遍历路径。当我们发现某个点的low值等于其时间戳时,说明已经完成了一个强连通分量的遍历。这时,我们将栈中所有未被处理的点作为一个强连通分量并记录下来。

具体实现如下:

scc = []stack = []def tarjan(u):    dfn[u], low[u] = stamp, stamp    stamp += 1    stack.append(u)    for v in Graph[u]:        if not dfn[v]:            tarjan(v)            low[u] = min(low[u], low[v])        elif v in stack:            low[u] = min(low[u], dfn[v])    if dfn[u] == low[u]:        cur = []        while True:            cur.append(stack[-1])            stack.pop()            if cur[-1] == u:                break        scc.append(cur)

代码解释

  • 初始化:我们初始化一个时间戳变量stamp和一个记录强连通分量的列表scc
  • 递归函数:函数tarjan(u)负责对点u进行深度优先搜索。首先,为点u分配一个时间戳,并将其加入栈。
  • 遍历邻居:对于点u的每一个邻居v,如果v尚未被访问过,则递归调用tarjan(v)。在返回时,更新low[u]low[u]low[v]中的较小值。
  • 处理栈中的点:如果v已经在栈中,说明v位于u的路径上,更新low[u]low[u]dfn[v]中的较小值。
  • 识别强连通分量:当dfn[u]等于low[u]时,说明已经完成了一个强连通分量的遍历。此时,从栈中弹出所有点,并将它们作为一个强连通分量记录下来。
  • 代码示例

    以下是一个完整的Python代码示例,展示了Tarjan算法的实现:

    def tarjan(u):    global stamp, dfn, low, stack, scc    dfn[u] = low[u] = stamp    stamp += 1    stack.append(u)    for v in Graph[u]:        if dfn[v] == -1:            tarjan(v)            low[u] = min(low[u], low[v])        elif v in stack:            low[u] = min(low[u], dfn[v])    if dfn[u] == low[u]:        scc.append(stack.copy())        while stack:            stack.pop()# 初始化global stamp, dfn, low, stack, sccstamp = 1dfn = {}low = {}stack = []scc = []# 示例图的邻接表Graph = {    1: [2, 3, 4],    2: [1, 5],    3: [1, 6],    4: [1, 7],    5: [2],    6: [3],    7: [4],}# 开始Tarjan算法for u in Graph:    if dfn[u] == -1:        tarjan(u)# 输出结果for component in scc:    print("Strongly Connected Component:", component)

    总结

    Tarjan算法通过时间戳和low值机制,深度递归地遍历图中的每一个点,并利用栈来记录强连通分量的边界。这种方法不仅能够高效地完成强连通分量的分解,还避免了Kosaraju算法中的图反转操作,从而在时间和空间上都更加节省。

    希望今天的内容对你有所帮助。如果你觉得这篇文章有价值,欢迎分享给更多的朋友。我们下次再见!

    转载地址:http://ieqfz.baihongyu.com/

    你可能感兴趣的文章
    SpringBoot中集成LiteFlow(轻量、快速、稳定可编排的组件式规则引擎)实现复杂业务解耦、动态编排、高可扩展
    查看>>
    SpringBoot中集成influxdb-java实现连接并操作Windows上安装配置的influxDB(时序数据库)
    查看>>
    P8738 [蓝桥杯 2020 国 C] 天干地支
    查看>>
    package,source folder,folder相互转换
    查看>>
    SpringBoot中集成Flyway实现数据库sql版本管理入门以及遇到的那些坑
    查看>>
    package.json文件常用指令说明
    查看>>
    SpringBoot中集成eclipse.paho.client.mqttv3实现mqtt客户端并支持断线重连、线程池高并发改造、存储入库mqsql和redis示例业务流程,附资源下载
    查看>>
    Padding
    查看>>
    paddlehub安装及对口罩检测
    查看>>
    SpringBoot中集成Actuator实现监控系统运行状态
    查看>>
    paddle的两阶段基础算法基础
    查看>>
    Page Object模式:为什么它是Web自动化测试的必备工具
    查看>>
    SpringBoot中重写addCorsMapping解决跨域以及提示list them explicitly or consider using “allowedOriginPatterns“ in
    查看>>
    PageHelper 解析及实现原理
    查看>>
    pageHelper分页工具的使用
    查看>>
    pageHelper分页技术
    查看>>
    PageHelper分页查询遇到的小问题
    查看>>
    PageHelper实现分页详细版、整合SSM应用
    查看>>
    PageHelper常见问题
    查看>>
    SpringBoot中配置为开发模式,代码修改后不用重新运行
    查看>>