强连通分量(SCC, Strongly Connected Components)

强连通分量的概念

在有向图中,任意两个顶点 v i v_i vi v j v_j vj 互相可达(也即存在路径 v i → v j v_i \rightarrow v_j vivj 和路径 v j → v i v_j \rightarrow v_i vjvi),则称这些顶点构成连通分量

强连通分量(SCC, Strongly Connected Component),即极大连通分量,对于一个联通分量,如果每个包含它的极大联通分量就是其本身,则称为极大联通分量。或者说强连通分量只要增加一个顶点,则无法构成连通分量。
在这里插入图片描述
例如在上图中,BCDE构成强连通分量,而CD则不是强连通分量。

强连通分量的应用

将有向图中的强连通分量缩成一个顶点,有向图则成为一个有向五环图(DAG),而DAG也称为拓扑图,其顶点可以进行拓扑排序。

在这里插入图片描述
而拓扑图在图论中可以用于求解最短/长路径。拓扑图在优化编译器中也有重要应用。

强连通分量的算法——Tarjan算法

比较典型的算法是Tarjan算法,该算法时间复杂度为 O(V+E)。下面介绍下该算法原理,参考了youtube一位大佬的视频

Tarjan算法主体采用DFS遍历,每访问一个顶点,给该顶点赋值一个id和low-link,low-link表示当前顶点能到达的顶点中最小的id(包括它自己的id)。
在这里插入图片描述
可见low-link相同的节点就组成强联通分量。该算法核心就是计算每个节点low-link值。

该算法需要维护一个栈,防止跨Sccs跟新low-link,也就是说在有效的顶点范围内更新low-link。
在这里插入图片描述
DFS访问一个未访问的顶点即加入栈中作为有效顶点,当找到一个Scc时,将这些顶点从栈中弹出。

low-link的更新条件为:使用节点v的low-link更新顶点u的low-link,必须有一个从u到v的边,并且顶点v必须在栈中。

Tarjan算法概览:

  • 标记所有节点为未访问状态(unvisited)。
  • 开始DFS,访问节点,并为其分配id和low-link值,标记该顶点已经访问过(visited),并加入到栈中。
  • 当DFS回溯时,如果先前节点在栈中,则使用当前顶点的前一个顶点来更新当前顶点的low-link(取二者最小值)。
  • 在完成当前顶点的所有邻居后,如果以当前顶点为起始的顶点组成Sccs,则将栈中的顶点弹出,直到当前顶点。

例如:
在这里插入图片描述
任意选择顶点开始DFS,这里选择的是顶点0,依次将顶点0、1、2入栈,当顶点2继续DFS时,发现顶点0已经访问过了,于是开始回溯,并更新计算low-link值。当回溯到顶点0时,其id值等于low-link值,说明找到了一个Scc,此时将相关顶点弹出。

然后再选择一个顶点继续DFS,这里选择顶点3。
在这里插入图片描述
将顶点3、4、5依次入栈,从顶点5继续DFS,访问顶点0,顶点已经访问过了,则回溯到顶点5,此时不更新low-link,因为顶点0不在栈中。接着继续DFS到顶点6和4,将顶点6和4入栈(顶点2和顶点0的情况一样),顶点4已经访问过了,开始回溯,并更新low-link值。当回溯到顶点4时,其id值和low-link值相等,找到一个Scc,依次将顶点6、5、4弹出。

在这里插入图片描述
此时栈中还剩顶点3,继续DFS到顶点4,节点4已经访问过了,回溯到顶点3,顶点4不在栈中,不更新顶点3的low-link。此时虽然顶点3的id和low-link相等,但顶点3的邻居还没访问完,继续DFS到顶点7。
在这里插入图片描述
同样地,找到一个新的Scc。

最后,给出完整的为伪代码:
在这里插入图片描述在这里插入图片描述

参考:

  • https://www.youtube.com/watch?v=wUgWX0nc4NY
  • https://github.com/williamfiset/Algorithms
  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用深度优先搜索算法实现有向图的强连通分量的示例代码。其中,使用了两次深度优先搜索,第一次搜索得到每个节点的完成时间,第二次搜索对节点进行拓扑排序,并在拓扑排序的基础上得到强连通分量。 ```python from collections import defaultdict class Graph: def __init__(self, vertices): self.V = vertices self.graph = defaultdict(list) def addEdge(self, u, v): self.graph[u].append(v) # 第一次深度优先搜索,得到每个节点的完成时间 def DFS(self, v, visited, finish_time): visited[v] = True for i in self.graph[v]: if visited[i] == False: self.DFS(i, visited, finish_time) finish_time.append(v) # 第二次深度优先搜索,得到强连通分量 def DFS2(self, v, visited, res): visited[v] = True res.append(v) for i in self.graph[v]: if visited[i] == False: self.DFS2(i, visited, res) # 对图进行拓扑排序,并得到强连通分量 def SCC(self): finish_time = [] visited = [False] * self.V # 第一次深度优先搜索 for i in range(self.V): if visited[i] == False: self.DFS(i, visited, finish_time) # 反转图 gr = Graph(self.V) for i in self.graph: for j in self.graph[i]: gr.addEdge(j, i) # 初始化 visited 数组 visited = [False] * self.V # 第二次深度优先搜索 res = [] while finish_time: i = finish_time.pop() if visited[i] == False: gr.DFS2(i, visited, res) print(res) res = [] # 测试代码 g = Graph(5) g.addEdge(1, 0) g.addEdge(0, 2) g.addEdge(2, 1) g.addEdge(0, 3) g.addEdge(3, 4) print("Strongly Connected Components:") g.SCC() ``` 运行结果如下: ``` Strongly Connected Components: [4] [3] [0, 2, 1] ``` 以上结果表示图中有三个强连通分量,分别是节点 4、节点 3 和节点 0、2、1。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值