缩点学习笔记概念与实现缩点也称为强连通分量缩点是图论中处理有向图的一种重要技术主要用于将有向图中的强连通分量Strongly Connected Component, SCC合并为单个节点从而简化图的结构。缩点后的图是一个有向无环图DAG便于后续分析。强连通分量的定义在有向图中如果从节点u到节点v存在一条路径且从v到u也存在一条路径则称u和v是强连通的。强连通分量是图中所有节点之间两两强连通的最大子图。缩点的意义缩点后的图保留了原图的拓扑结构但减少了节点数量使得许多问题如最长路径、拓扑排序等更容易解决。缩点常用于以下场景检测有向图中的环路。优化动态规划或路径查找问题。简化图的拓扑结构。缩点的实现方法缩点的实现通常分为两步使用 Kosaraju 算法或 Tarjan 算法找到所有强连通分量。将每个强连通分量合并为一个节点构建新图。以下以 Tarjan 算法为例展示缩点的实现。Tarjan 算法原理Tarjan 算法通过深度优先搜索DFS遍历图利用栈和两个数组dfn和low记录节点的访问顺序和能够回溯到的最早节点从而识别强连通分量。代码实现以下是使用 Python 实现的 Tarjan 算法缩点代码def tarjan_scc(graph): n len(graph) dfn [0] * n # DFS访问顺序 low [0] * n # 能够回溯到的最早节点 on_stack [False] * n # 节点是否在栈中 stack [] index 0 # 当前DFS的访问顺序 sccs [] # 存储所有强连通分量 def dfs(u): nonlocal index dfn[u] low[u] index index 1 stack.append(u) on_stack[u] True for v in graph[u]: if dfn[v] 0: # 未访问过 dfs(v) low[u] min(low[u], low[v]) elif on_stack[v]: # 在栈中说明是同一个SCC low[u] min(low[u], dfn[v]) if dfn[u] low[u]: # 发现一个SCC scc [] while True: v stack.pop() on_stack[v] False scc.append(v) if v u: break sccs.append(scc) for u in range(n): if dfn[u] 0: dfs(u) return sccs def build_condensed_graph(graph, sccs): scc_id [0] * len(graph) for i, scc in enumerate(sccs): for u in scc: scc_id[u] i condensed_graph [[] for _ in range(len(sccs))] for u in range(len(graph)): for v in graph[u]: if scc_id[u] ! scc_id[v]: condensed_graph[scc_id[u]].append(scc_id[v]) # 去重 for u in range(len(condensed_graph)): condensed_graph[u] list(set(condensed_graph[u])) return condensed_graph # 示例图邻接表表示 graph [ [1], # 0 - 1 [2], # 1 - 2 [0, 3], # 2 - 0, 3 [4], # 3 - 4 [5], # 4 - 5 [3] # 5 - 3 ] sccs tarjan_scc(graph) print(强连通分量:, sccs) condensed_graph build_condensed_graph(graph, sccs) print(缩点后的图:, condensed_graph)代码说明tarjan_scc函数使用 DFS 遍历图记录每个节点的dfn和low值。当dfn[u] low[u]时从栈中弹出节点形成一个强连通分量。build_condensed_graph函数为每个节点分配其所属的强连通分量 ID。构建新图确保不同强连通分量之间的边被保留。示例图图中的强连通分量为[[0, 1, 2], [3, 4, 5]]。缩点后的图是一个包含两个节点的 DAG。缩点的应用场景检测环路缩点后的图是一个 DAG如果原图中存在环路缩点后的节点数会减少。通过比较缩点前后的节点数可以判断原图是否有环路。拓扑排序缩点后的图是 DAG可以直接进行拓扑排序。以下是一个拓扑排序的示例代码from collections import deque def topological_sort(condensed_graph): in_degree [0] * len(condensed_graph) for u in range(len(condensed_graph)): for v in condensed_graph[u]: in_degree[v] 1 queue deque() for u in range(len(in_degree)): if in_degree[u] 0: queue.append(u) topo_order [] while queue: u queue.popleft() topo_order.append(u) for v in condensed_graph[u]: in_degree[v] - 1 if in_degree[v] 0: queue.append(v) return topo_order # 示例 print(拓扑排序:, topological_sort(condensed_graph))动态规划优化在 DAG 上运行动态规划通常比在原图上更高效。例如计算最长路径def longest_path_dag(condensed_graph): n len(condensed_graph) topo_order topological_sort(condensed_graph) dp [1] * n # 假设每个节点的初始长度为1 for u in topo_order: for v in condensed_graph[u]: if dp[v] dp[u] 1: dp[v] dp[u] 1 return max(dp) print(最长路径:, longest_path_dag(condensed_graph))总结缩点技术通过将有向图中的强连通分量合并为单个节点将复杂图简化为 DAG便于后续分析和优化。Tarjan 算法是缩点的高效实现方法适用于检测环路、拓扑排序和动态规划等场景。 学会与孤独共处才能在喧嚣的世界中找到自己的声音享受那份内心的宁静与自省的力量。每一次微笑都能带来温暖而勇于面对不平和困扰才能让人生变得愈发光辉灿烂充满立体感。在追梦的过程中学会欣赏沿途的风景只有用心去看才能体会到生活的深意与美好。修行的路上分享是最美的财富让爱与善良如阳光般温暖自己和他人为生活添一缕光辉。每一个夜晚都是思考的时刻勇于反省自我的过往才能在新的日出时看到更广阔的未来。https://github.com/hellcourt42/29f_txuo/issues/27https://github.com/katojahcfer/3au_ekv8/issues/28https://github.com/naspudk43390/rzm_j1o4/issues/25https://github.com/phraman/j7m_v1kh/issues/29https://github.com/josecaro9123/fz2_rtry/issues/28