前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Kosaraju算法、Tarjan算法分析及证明--强连通分量的线性算法

Kosaraju算法、Tarjan算法分析及证明--强连通分量的线性算法

作者头像
老白
发布于 2018-03-19 08:00:03
发布于 2018-03-19 08:00:03
2.7K00
代码可运行
举报
文章被收录于专栏:架构之路架构之路
运行总次数:0
代码可运行

一、背景介绍

强连通分量是有向图中的一个子图,在该子图中,所有的节点都可以沿着某条路径访问其他节点。强连通性是一种非常重要的等价抽象,因为它满足

  • 自反性:顶点V和它本身是强连通的
  • 对称性:如果顶点V和顶点W是强连通的,那么顶点W和顶点V也是强连通的
  • 传递性:如果V和W是强连通的,W和X是强连通的,那么V和X也是强连通的

强连通性可以用来描述一系列属性,如自然界中物种之间的捕食关系,互相捕食的物种可以看作等价的,在自然界能量传递中处于同一位置。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

二、Kosaraju算法描述

Kosaraju算法通过以下步骤获得一个有向图的强连通分量。

  • 在图G中,计算图G的反向图G'的深度优先搜索的逆后序排列。反向图是比如原图中有V到W的链接,那么反向图中就有W到V的链接。逆后序排列是指,在对一个图进行深度优先遍历时,先进行子节点的递归,再将本节点加入到一个栈中,最后依次出栈以获得的序列。逆后序排列有一个重要特性,就是如果有W到V的有向链接,那么实际出栈时,W仍然排在V的前面
  • 在G中进行普通的深度优先搜索,但是搜索顺序不是按照正统的( i = 0, i < N )去依次搜索,而是以刚刚获得的逆后序排列的顺序进行搜索。
  • 每个以这个逆后序排列中的元素开始的DFS搜索,找到的所有元素,都是同一个强联通分量的元素。

为什么这个算法可以获得强连通分量呢?网上的证明很少,所以下面给出我的逻辑证明。

三、Kosaraju算法证明

我们按照算法描述的步骤往下走:

  1. 按照算法的结论,假设我们已经获得了一个逆后序排列,我们从中找两个元素,分别是V,W,W先出栈并且通过DFS找到了V。那么,V和W就是同一个联通分量的元素。到底是不是呢?
  2. 不管是不是,我们至少可以确定对于该有向图G,W有一条链接通往V,我们记作W->V。那么,对于该有向图的反向图G',确定有链接V->W
  3. 我们开始思考,在什么条件下,我们能够在反向图 G' 中获得V...W(即W先出栈)这样一个排列呢?要知道,我们刚刚确定了有链接V->W,所以逆后序排列中,应该是V排在W的前面,W...V这样啊?
  4. 所以在G'中,要么是我们之前提到的,在V->W的同时有W->V的链接;要么就是W和V之间没有任何联系,这样V的DFS结束之后,包含W的联通分量的DFS才开始遍历,才能造成W比V先出栈
  5. 但是我们已经知道,V和W不是毫无关系的,确定有链接V->W,所以第二个可能不成立,所以必然存在一个W->V的链接,也就是W和V是互相联通的!
  6. 证明完毕。

四、算法源码

 因为代码很长,放在github上了。代码是在Idea中编译运行通过的,实现了一个基本的Graph数据结构,在此基础上实现了Kosaraju类,供参考。

源文件


五、更加迅速的tarjan算法

部分内容转自https://www.byvoid.com/blog/scc-tarjan

1.Tarjan算法

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Low(u)=Min
{
    DFN(u),
    Low(v),(u,v)为树枝边,u为v的父节点
    DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
}

当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

算法伪代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
tarjan(u)
{
    DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
    Stack.push(u)                              // 将节点u压入栈中
    for each (u, v) in E                       // 枚举每一条边
        if (v is not visted)               // 如果节点v未被访问过
            tarjan(v)                  // 继续向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in S)                   // 如果节点v还在栈内
            Low[u] = min(Low[u], DFN[v])
    if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
        repeat
            v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
            print v
        until (u== v)
}

2.算法的精要之处如下:

  • 为每个加入的节点设定序号,使得后搜索到的节点的序号一定高于前面的节点
  • 那么,如果后搜索到的节点的子节点里居然有比它本身还要小的节点,则一定出现了环。有环则必定强连通
  • 那么,把该节点的标识节点Low(u)设为发现的后向节点的值DFN(V)
  • 然后递归程序返回该节点的上级节点u-1,上级节点判断Low(u-1)的值,也把它指向了刚刚找到的后向节点
  • 最后,除了后向节点本身,所有环中的节点都指向该后向节点,那么,我们找到了一个强连通分量。

3.算法流程的演示

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。

求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与求无向图的双连通分量(割点、桥)的Tarjan算法也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-10-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【基础】JavaScript 中 null 和 undefined 的区别?
通过运行代码可以看出null和undefined是相等的,但是当他们做全等比较时,又不等。原因是什么呢?我们再来看下他们的类型:
青年码农
2020/12/17
7800
JavaScript中undefined与null详解
对于undefined和null我一直知道他们有很多区别,也知道一点关于他们的区别,但却不具体系统,因此总结了一下,主要心得如下:
Javanx
2019/09/04
7710
JavaScript中undefined与null详解
undefined与null的区别
大多数计算机语言,有且仅有一个表示"无"的值,比如,C语言的NULL,Java语言的null,Python语言的None,Ruby语言的nil。 有点奇怪的是,JavaScript语言居然有两个表示"
ruanyf
2018/04/12
1.2K0
undefined与null的区别
null和undefined的区别是什么
这两个东西其实从表面来看,没有多大的区别,都表示的是空,在其他的语言中一般情况下只有null这个值,undefined 却是javascript才有的。
OECOM
2020/07/02
8840
从此理解清楚undefined与null
在JavaScript中,将一个变量赋值为undefined或null,老实说,几乎没区别。
matinal
2023/10/13
1480
javaScript中is-not-defined,undefined和null的区别
之前没太注意is not defined和undefined有什么区别,每次都是简单的把两者理解为未定义,现在回过头来梳理js基础的时候才发现其中区别还是很鲜明的。 先从单纯的字面意思来理解一下(有道词典):
用户2458785
2018/08/29
1.2K0
null 和 undefined
浏览器会弹出窗口显示 undefined。因为对于使用了 var 声明但没有进行初始化定义的变量, 其值默认为 undefined。
Chor
2019/11/08
1.8K0
null,undefined的区别?
在 JavaScript 中,null 和 undefined 都表示没有值或缺失值的状态,但它们之间有一些区别。
王小婷
2023/10/30
2160
null和undefined的区别
在Js中null与undefined是两种基本数据类型,都可以用来表示"无"这个概念,但是在语义表达以及实际使用上是有所区别的。
WindRunnerMax
2020/08/27
2.5K0
关于 JavaScript 的 null 和 undefined,判断 null 的真实类型
Undefined 和 Null 是 Javascript 中两种特殊的原始数据类型(Primary Type),它们都只有一个值,分别对应 undefined 和 null ,这两种不同类型的值,即有着不同的语义和场景,但又表现出较为相似的行为:
Krry
2018/09/10
1.7K0
关于 JavaScript 的 null 和 undefined,判断 null 的真实类型
javascript基础语法
JavaScript 是一种具有面向对象能力的、解释型的程序设计语言。更具体一点,它是基于对象和事件驱动并具有相对安全性的客户端脚本语言。它的主要目的是,验证发往服务器端的数据、增加 Web 互动、加强用户体验度等。
wolf
2020/09/18
7810
Javascript 中 null和undefined的区别
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
wfaceboss
2019/04/08
7830
JavaScript中的null和undefined的区别
1、null 表示没有对象,即该处不应该有值,用法如下: 作为函数的参数,表示该函数的参数不是对象; 作为原型链的终点。 2、undefined 表示缺少值,就是此处应该有一个值,但是还没有定义,情况如下: 变量被声明了,但没有赋值时,就等于undefined; 调用函数时,应该提供的参数没有提供,该参数等于undefined; 对象没有赋值的属性,该属性的值为undefined; 函数没有返回值的时,默认返回undefined;
IT工作者
2021/12/31
7730
null == undefined ?
最近在看《JavaScript高级程序设计》一书,书中讲到相等操作符(==)时说,要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,但要记住 null == undefined 会返回 true 。的确,在ECMAScript规范中也是这样定义的,但我认为这样来理解这件事情,似乎有些浮于表面,网上也有很多关于这个问题的文章,下面我希望从一个全新的角度来分析 null 和 undefined 的区别,从而理解两者为何会相等:
疯狂的技术宅
2019/03/28
2.5K0
null == undefined ?
每天学点JavaScript基础(1)—— null 和 undefined
对null执行typeof操作,结果返回字符串"object" ,null可以认为是一个特殊的对象值,含义是非对象。
CherishTheYouth
2020/08/11
6480
JavaScript Undefined类型
undefined 是 Undefined 类型的唯一值,它表示未定义的值。当声明变量未赋值时,或者定义属性未设置值时,默认值都为 undefined。 示例1 undefined 派生自 null,null 和 undefined 都表示空缺的值,转化为布尔值时都是假值,可以相等。
用户3519280
2023/07/07
2050
细数 JavaScript 实用黑科技(一)
从接触前端开发到现在已经将近 2 年了,最近又看了阮一锋写的: 《JavaScript 语言入门教程》 一书,重温 JavaScript 。
夜尽天明
2019/11/13
7600
细数 JavaScript 实用黑科技(一)
3《JavaScript高级程序设计》__ 语言基础(上)
大家好,我是HoMeTown,web领域有一本神书大家应该都有看过,这本书我看过两遍,但是每次看都是粗粗的略过一些重要的知识点,甚至一些面试过程中的问题,在这本书里都能找到答案。
HoMeTown
2022/10/26
6630
3《JavaScript高级程序设计》__ 语言基础(上)
【JavaScript】JavaScript 变量 ⑦ ( JavaScript 数据类型 | Boolean 布尔类型 | Undefined 类型 | Null 类型 )
在 JavaScript 中 , Boolean 布尔类型 是 基本 数据类型之一 ,
韩曙亮
2024/03/18
1120
【JavaScript】JavaScript 变量 ⑦ ( JavaScript 数据类型 | Boolean 布尔类型 | Undefined 类型 | Null 类型 )
【面试说】聊聊JavaScript中的数据类型
答:Javascript 中的数据类型包括原始类型和引用类型。其中原始类型包括 Null、Undefined、Boolean、Number、String、Symbol、BigInt。引用类型指的是 Object。
GopalFeng
2022/08/01
5650
【面试说】聊聊JavaScript中的数据类型
相关推荐
【基础】JavaScript 中 null 和 undefined 的区别?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验