强连通分量(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
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现求强连通分量,可以使用深度优先搜索(DFS)算法。以下是一个用 Python 实现的示例代码: ```python from collections import defaultdict class Graph: def __init__(self, vertices): self.V = vertices self.graph = defaultdict(list) self.Time = 0 def addEdge(self, u, v): self.graph[u].append(v) def SCCUtil(self, u, low, disc, stackMember, st): disc[u] = self.Time low[u] = self.Time self.Time += 1 stackMember[u] = True st.append(u) for v in self.graph[u]: if disc[v] == -1: # 如果 v 还没被访问过,则递归访问它 self.SCCUtil(v, low, disc, stackMember, st) low[u] = min(low[u], low[v]) elif stackMember[v] == True: # 如果 v 在栈中,则更新 low[u] low[u] = min(low[u], disc[v]) w = -1 # 存储强连通分量的顶点 if low[u] == disc[u]: while w != u: w = st.pop() print(w, end=" ") stackMember[w] = False print() def SCC(self): disc = [-1] * (self.V) low = [-1] * (self.V) stackMember = [False] * (self.V) st = [] for i in range(self.V): if disc[i] == -1: self.SCCUtil(i, low, disc, stackMember, st) # 示例用法 g1 = Graph(5) g1.addEdge(1, 0) g1.addEdge(0, 2) g1.addEdge(2, 1) g1.addEdge(0, 3) g1.addEdge(3, 4) print("强连通分量为:") g1.SCC() ``` 这段代码首先定义了一个 `Graph` 类,其中 `addEdge` 方法用于添加边。`SCCUtil` 方法是实现强连通分量的核心函数。`SCC` 方法用于遍历图中所有的强连通分量并打印出来。 在示例中,我们创建了一个包含 5 个顶点的图,并添加了几条边。然后调用 `SCC` 方法来获取强连通分量。输出结果将显示强连通分量的顶点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值