图论 SCC(CCF高速公路)

本文介绍了图论中的强连通分量(SCC)概念,并详细解析了Tarjan算法求解SCC的过程。通过实例分析了算法的核心思想,包括DFN和LOW数组的作用,以及如何在DFS过程中判断强连通分量。最后,结合CCF题目解释了如何利用SCC解决实际问题,强调了在实现过程中需要注意的数据结构选择和处理细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

首先需要复习一下图论的一系列知识,理解有向图的强连通分量的定义,求解有向图的强连通分量算法有很多,例如Kosaraju,Gabow和Tarjan算法,其中Gabow和Tarjan算法时间复杂度要优于Kosaraju。在这里我们使用Tarjan求解SCC。
在此之前你可以对SCC有个直观的认识,放图:
强连通分量求解过程
然后看一篇浅显的说明性的文字,了解这个算法的思想:
http://www.voidcn.com/blog/wr132/article/p-4891712.html
然后我们需要贴一篇经典的文章讲解,传送门如下:
https://www.byvoid.com/blog/scc-tarjan/

理解:

核心思想:一个强连通分量中任意两个节点一定是互相可达的,那么这个强连通分量的从i到j的路径构成一个环(when i=j),因此我们的核心思想就是寻找环。
1. DFN[i]数组存储第i个节点的访问时间time,LOW[i]数组存储当前第i个节点能够直接或者间接访问到的最早的节点的时序time
2. 本算法基于DFS,当遍历到第i个节点的时候,递归结束的条件就是考虑第i个节点的DFN值和LOW是否相等,通俗点说就是这个点能找到的最小时间的点是自身,即从i出发可以回到i,那么i后面遍历到的点包括i在内构成了一个有向图的强连通分量。
3. 其次我们考虑递归遍历的时候,第i个节点的下一个节点i+1如果遍历完毕,我们考虑i+1的LOW值,取i和i+1的LOW值中小的;如果i+1在栈中,说明之前遍历过,如果DFN值小,说明之前遍历过,从当前i到i+1很可能构成环,取之。
下面用一道CCF的题目应用一下SCC算法。


问题描述
  某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。
输入格式
  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。
输出格式
  输出一行,包含一个整数,表示便利城市对的数量。
样例输入
5 5
1 2
2 3
3 4
4 2
3 5
样例输出
3

题解

  1. 本题思路在于求出SCC,每个SCC内部的任意两个节点都是相互可达的,我们使用 C(
### SCC 的全称与定义 在图论中,**Strongly Connected Components(SCC)** 是指 **强连通分量**。具体来说,强连通分量表示的是一个有向图中的极大强连通子图[^1]。这意味着在一个有向图中,如果某个子图的所有节点之间都能够互相到达,并且这个子图无法通过加入其他节点或边来扩展而仍然保持强连通性,那么这个子图就被称为一个强连通分量。 对于两个节点 \( u \) 和 \( v \),如果在有向图 \( G \) 中存在从 \( u \) 到 \( v \) 的路径以及从 \( v \) 到 \( u \) 的路径,则称这两个节点是强连通的[^2]。进一步地,如果一个有向图的所有节点都满足上述条件,则该图被称为强连通图。然而,在实际问题中,很多有向图并非整体强连通,因此需要将它们分解为若干个强连通分量。 以下是求解强连通分量的一个经典算法——Kosaraju 算法的简单实现代码示例: ```python def kosaraju(graph): def dfs_first_pass(node, visited, stack): visited.add(node) for neighbor in graph.get(node, []): if neighbor not in visited: dfs_first_pass(neighbor, visited, stack) stack.append(node) def dfs_second_pass(node, visited, component): visited.add(node) component.append(node) for neighbor in reversed_graph.get(node, []): if neighbor not in visited: dfs_second_pass(neighbor, visited, component) # 构建反向图 reversed_graph = {} for node, neighbors in graph.items(): for neighbor in neighbors: if neighbor not in reversed_graph: reversed_graph[neighbor] = [] reversed_graph[neighbor].append(node) visited = set() stack = [] # 第一遍 DFS 遍历原图 for node in graph: if node not in visited: dfs_first_pass(node, visited, stack) visited = set() scc = [] # 第二遍 DFS 遍历反向图 while stack: node = stack.pop() if node not in visited: component = [] dfs_second_pass(node, visited, component) scc.append(component) return scc ``` 此代码展示了如何通过两次深度优先搜索(DFS)来计算有向图中的所有强连通分量。 ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值