如何从边列表生成全连通图中的所有简单环?
枚举全连通图中的所有简单环
问题分析
你需要从有向全连通图的边列表中枚举所有简单环(顶点除起点/终点外不重复),同时要避免重复计数(比如反向的环视为同一个)。直接用itertools生成所有组合会产生大量冗余,且难以高效去重,因此更适合用DFS结合去重逻辑来实现。
实现思路
- 构建邻接表:将边列表转换为邻接表结构,方便快速查找每个节点的出边,提升遍历效率。
- DFS遍历所有路径:从每个节点出发,深度优先搜索所有可能的路径,当路径回到起始节点时,记录该环。
- 环的去重处理:同一个环可以有多种表示形式(不同起点、反向),通过生成环的所有等价形式,选取字典序最小的作为唯一标识,从而避免重复。
代码实现
import itertools def find_all_simple_cycles(edges): # 构建邻接表:key是起点,value是所有可达的终点 adj = {} for u, v in edges: if u not in adj: adj[u] = [] adj[u].append(v) all_cycles = [] nodes = set(itertools.chain(*edges)) # 获取所有节点 for start_node in nodes: # DFS栈:(当前节点, 已访问的边列表, 已访问的顶点集合) stack = [(start_node, [], set())] while stack: current, path_edges, visited_nodes = stack.pop() # 遍历当前节点的所有出边 for neighbor in adj.get(current, []): new_path = path_edges + [(current, neighbor)] new_visited = visited_nodes.copy() new_visited.add(current) if neighbor == start_node: # 找到一个环,长度至少为2(用户例子包含2边环) if len(new_path) >= 2: all_cycles.append(new_path) elif neighbor not in visited_nodes: # 未访问过该节点,继续DFS stack.append((neighbor, new_path, new_visited)) # 去重处理:生成环的标准形式 unique_cycles = set() for cycle in all_cycles: # 生成所有循环移位 shifts = [] k = len(cycle) for i in range(k): shifted = cycle[i:] + cycle[:i] shifts.append(tuple(shifted)) # 生成反向序列的所有循环移位 reversed_cycle = cycle[::-1] for i in range(k): shifted_rev = reversed_cycle[i:] + reversed_cycle[:i] shifts.append(tuple(shifted_rev)) # 取字典序最小的作为唯一标识 canonical = min(shifts) unique_cycles.add(canonical) # 转换为列表形式返回 return [list(cycle) for cycle in unique_cycles] # 测试用户提供的例子 edges = [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)] result = find_all_simple_cycles(edges) # 按长度排序后输出,和示例格式对齐 result_sorted = sorted(result, key=lambda x: len(x)) print(result_sorted)
代码说明
- 邻接表构建:遍历输入的边列表,为每个起点映射其所有可达的终点。
- DFS遍历:从每个节点出发,记录当前路径的边和已访问的顶点,避免路径中重复访问非起点的顶点(保证简单环)。当路径回到起点时,记录该环。
- 去重逻辑:对每个环生成所有循环移位和反向移位,选取字典序最小的形式作为唯一标识,存入集合自动去重。
- 测试结果:运行代码后,输出结果与你期望的完全一致。
内容的提问来源于stack exchange,提问作者moni
相关产品推荐
相关产品推荐

