Kasaraju算法--强连通图遍历及其python实现
在理解有向图和强连通分量前必须理解与其对应的两个概念,连通图(无向图)和连通分量。
连通图的定义是:如果一个图中的任何一个节点可以到达其他节点,那么它就是连通的。
例如以下图形:
这是最简单的一个连通图,即使它并不闭合。由于节点间的路径是没有方向的,符合从任意一个节点出发,都可以到达其他剩余的节点这一条件,那么它就是连通图了。
连通分量
显然这也是一个图,只不过是由三个子图组成而已,但这并非一个连通图。这三个子图叫做这个图的连通分量,连通分量的内部归根还是一个连通图。
有向图:
在连通图的基础上增加了方向,两个节点之间的路径只能有单一的方向,即要么从节点A连向节点B,要么从节点B连向节点A。有向图与连通图(更准确来说是无向图)最大的区别在于节点之间的路径是否有方向。
有向图也分两种,一种是有环路的有向图。另外一种是无环路的有向图,即通常所说的有向无环图DAG(Directed Acyclic Graph)。严格来说,第一种有环路的图,如果任意一个节点都可以与其他节点形成环路,那么它也是一个连通图。
例如下面的就为一个有向图同时也是连通图:
强连通分量
强连通分量SCCs(strongly connected components)是一种有向的连通图。
如果一个图的连通分量是它里面所有节点到能够彼此到达的最大子图,那么强连通分量SCCs就是一个有向图中所有节点能够彼此到达的子图。
显然由345组成的子图是无法到达由012组成的子图的。那么012和345分别组成两个强连通分量。
在实际的现实问题中,我们考虑问题可能就不会简单地研究无向图。例如地图上的最短路径规划,ARP路由算法等等,考虑的都是有向图的问题。
如果有这样一个需求,我们希望用最少的次数遍历所有节点,怎么处理呢?
时间效应问题,强连通分量间的时间问题。
如果有向图的各个强连通分量中的元素个数相仿,那么,它们内部分别进行遍历的时间量级别是相等的,但实际情况是,这种情况很少发生。一般从一个强连通分量到另一个强连通分量。
正如上面的需求:如何用最少的次数遍历整个有向图的所有节点。假设我们将0、1、2组成子图1,将3、4、5组成子图,子图1有一条指向子图2的路径。这时候,我们从子图1的任意一点开始遍历。假设我们从1开始遍历,那么遍历的顺序将会是1—2,那么来到2的时候问题来了,是先走0的路径还是走子图1和子图2之间的路径去遍历节点3呢?
如果我们先遍历节点0,那么我们遍历完节点0之后,发现节点1已经遍历过,就会返回节点2,再沿着子图1和子图2之间的路径去遍历子图2。这看起来是挺合理的。
但问题是,如果是先遍历节点3(也就是说先遍历子图2)呢?
假设沿着子图1和子图2的路径去遍历子图2,那么子图2遍历完后,子图1还剩下节点0没有被遍历,这时候就会出现很为难的事情,因为之前遍历的情况无法判断哪些节点是没有遍历的,只能是原路返回,依次去从新遍历,“发现”哪些节点是还没去遍历的。似乎上图比较简单,这种方法不会耗费太多的时间。但如果是节点2连接着(并指向)许多个强连通子图的有向图,这种“返回式”的遍历将会是很费劲的一件事。
为了解决这个问题,Kosaraju算法提出了它的解决方案。Kosaraju算法的核心操作是将所有节点间的路径都翻转过来。下面分析一下为什么这种算法有它的优势。
还是拿上面的图来讲述。想象一下上面的有向图中的所有节点间的路径都翻转过来了。读者可以自己用一张纸简单画一下。就像下面的图:
这一次,我们还是以0、1、2组成子图1,以3、4、5组成子图2。所不同的是,这次遍历的起始点从子图1开始。
多强连通分量的有向图
再来看一下这个多子图的强连通图,如果像上图所示,从子图1开始,就会像上文提到的那样,遍历到节点2,会出现多个去向的问题。而在还没有遍历完子图1的前提下,从节点2过渡到子图2/子图3,再回溯的时候会引来较大的麻烦。通过Kosaraju算法之后,从2节点出发的路径都会变成指向2。此时,遍历的起点还是从子图1开始,由于子图1没有出路,就不会出现上面所说的问题。再遍历完子图1后,继续遍历子图2、子图3。而子图2、子图3的遍历都是在强连通分量内部实现的。
算法实现
邻接集表示的有向图
N={
"a":{"b"}, #a
"b":{"c"}, #b
"c":{"a","d","g"}, #c
"d":{"e"}, #d
"e":{"f"}, #e
"f":{"d"}, #f
"g":{"h"}, #g
"h":{"i"}, #h
"i":{"g"} #i
}
翻转图实现代码:
def re_tr(G):
GT = {}
for u in G:
for v in G[u]:
# print(GT)
if GT.get(v):
GT[v].add(u)
else:
GT[v] = set()
GT[v].add(u) return GT
深度遍历算法实现代码:
#递归实现深度优先排序
def rec_dfs(G,s,S=None):
if S is None:
#S = set() #集合存储已经遍历过的节点
S = list() #用列表可以更方便查看遍历的次序,而用集合可以方便用difference求差集
# S.add(s)
S.append(s)
print(S)
for u in G[s]:
if u in S:continue
rec_dfs(G,u,S) return S
在强连通图内遍历
#遍历有向图的强连通分量
def walk(G,start,S=set()): #传入的参数S,即上面的seen很关键,这避免了通过连通图之间的路径进行遍历
P,Q = dict(),set() #list存放遍历顺序,set存放已经遍历过的节点
P[start] = None
Q.add(start)
while Q:
u = Q.pop() #选择下一个遍历节点(随机性)
for v in G[u].difference(P,S): #返回差集
Q.add(v)
P[v] = u
print(P)
return P
获得强连通分量
#获得各个强连通图
def scc(G):
GT = re_tr(G)
sccs,seen = [],set()
for u in rec_dfs(G,"a"): #以a为起点
if u in seen:continue
C = walk(GT,u,seen)
seen.update(C)
sccs.append(C)
return sccs
单元测试
print(scc(N)) 结果:
{'a': None, 'c': 'a', 'b': 'c'}
{'d': None, 'f': 'd', 'e': 'f'}
{'g': None, 'i': 'g', 'h': 'i'}
[{'a': None, 'c': 'a', 'b': 'c'}, {'d': None, 'f': 'd', 'e': 'f'}, {'g': None, 'i': 'g', 'h': 'i'}]
这是本人学习过程所写的第一篇关于图的算法文章,供大家一起学习讨论。其中难免会有错误。如有错误之处,请各位指出,万分感谢!
Kasaraju算法--强连通图遍历及其python实现的更多相关文章
- 图的遍历(Python实现)
图的遍历(Python实现) 记录两种图的遍历算法——广度优先(BFS)与深度优先(DFS). 图(graph)在物理存储上采用邻接表,而邻接表是用python中的字典来实现的. 两种遍历方式的代码如 ...
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector ...
- javascript数据结构与算法--二叉树遍历(后序)
javascript数据结构与算法--二叉树遍历(后序) 后序遍历先访问叶子节点,从左子树到右子树,再到根节点. /* *二叉树中,相对较小的值保存在左节点上,较大的值保存在右节点中 * * * */ ...
- javascript数据结构与算法--二叉树遍历(先序)
javascript数据结构与算法--二叉树遍历(先序) 先序遍历先访问根节点, 然后以同样方式访问左子树和右子树 代码如下: /* *二叉树中,相对较小的值保存在左节点上,较大的值保存在右节点中 * ...
- javascript数据结构与算法--二叉树遍历(中序)
javascript数据结构与算法--二叉树遍历(中序) 中序遍历按照节点上的键值,以升序访问BST上的所有节点 代码如下: /* *二叉树中,相对较小的值保存在左节点上,较大的值保存在右节点中 * ...
- 机器学习经典算法具体解释及Python实现--线性回归(Linear Regression)算法
(一)认识回归 回归是统计学中最有力的工具之中的一个. 机器学习监督学习算法分为分类算法和回归算法两种,事实上就是依据类别标签分布类型为离散型.连续性而定义的. 顾名思义.分类算法用于离散型分布预測, ...
- 机器学习经典算法具体解释及Python实现--K近邻(KNN)算法
(一)KNN依旧是一种监督学习算法 KNN(K Nearest Neighbors,K近邻 )算法是机器学习全部算法中理论最简单.最好理解的.KNN是一种基于实例的学习,通过计算新数据与训练数据特征值 ...
- 模拟退火算法SA原理及python、java、php、c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径
模拟退火算法SA原理及python.java.php.c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径 模拟退火算法(Simulated Annealing,SA)最早的思 ...
- 【Python算法】遍历(Traversal)、深度优先(DFS)、广度优先(BFS)
图结构: 非常强大的结构化思维(或数学)模型.如果您能用图的处理方式来规范化某个问题,即使这个问题本身看上去并不像个图问题,也能使您离解决问题更进一步. 在众多图算法中,我们常会用到一种非常实用的思维 ...
随机推荐
- 使用Task
http://www.cnblogs.com/Charltsing/p/taskpoolthread.html task默认对线程的调度是逐步增加的,连续多次运行并发线程,会提高占用的线程数,而等若干 ...
- Spark MLlib
MLlib 数据挖掘与机器学习 数据挖掘体系 数据挖掘:也就是data mining,是一个很宽泛的概念,也是一个新兴学科,旨在如何从海量数据中挖掘出有用的信息来. ...
- Ceres Solver 在win8+vs2013环境下的安装
参考博文:https://blog.csdn.net/wzheng92/article/details/79504709
- Scala安装教程
首先去Java官网下载Java的安装包 jdk-8u121-windows-x64.exe 再去Scala官网下载Scala的安装包 Scala2.12.1 安装Java: 配置Java环境变量(系统 ...
- 从源码分析如何优雅的使用 Kafka 生产者
前言 在上文 设计一个百万级的消息推送系统 中提到消息流转采用的是 Kafka 作为中间件. 其中有朋友咨询在大量消息的情况下 Kakfa 是如何保证消息的高效及一致性呢? 正好以这个问题结合 Kak ...
- github访问很慢解决方案
首先要解决的就是这个访问速度的问题: 获取Github相关网站的ip 访问https://www.ipaddress.com,拉下来,找到页面中下方的“IP Address Tools – Quick ...
- 设计模式总结篇系列:桥接模式(Bridge)
在实际类设计过程中,有时会遇到此类情况:由于实际的需要,某个类具有两个或两个以上的维度变化,如果利用继承将每种可能的变化情况都定义成一个类,一是会导致类膨胀的问题,二是以后不太好维护和并且违背类的设计 ...
- PC逆向之代码还原技术,第一讲基本数据类型在内存中的表现形式.浮点,指针寻址公式
目录 代码还原技术 一丶简介代码还原 二丶代码还原中的数据类型表现形式 1.整数类型 2.无符号整数 3.有符号整数 4.浮点数数据类型 5.浮点编码 4.Double类型解析. 三丶浮点汇编 1.浮 ...
- 如何正确使用Java序列化?
前言 什么是序列化:将对象编码成一个字节流,这样一来就可以在通信中传递对象了.比如在一台虚拟机中被传递到另一台虚拟机中,或者字节流存储到磁盘上. “关于Java的序列化,无非就是简单的实现Serial ...
- Django 系列博客(八)
Django 系列博客(八) 前言 本篇博客介绍 Django 中的模板层,模板都是Django 使用相关函数渲染后传输给前端在显式的,为了想要渲染出我们想要的数据,需要学习模板语法,相关过滤器.标签 ...