如何缩减NetworkX中MultiDiGraph的冗余边与中间节点?
处理NetworkX MultiDiGraph中的冗余边与中间节点压缩
我有一个NetworkX的MultiDiGraph,需要移除冗余边和中间节点,具体需求:
- 对于双向链(如6↔7↔11、5↔9↔8↔11、5↔10↔2↔1↔0↔12),移除中间节点,直接在两端节点间建立双向边
- 对于单向链(如5→3→4),移除中间节点,直接在两端节点间建立单向边
- 当双向边与单向边相连时,不执行合并操作
现有的解决方案仅适用于无向Graph,无法适配MultiDiGraph,需要可行的实现方法。
原图形代码
import networkx as ntx import matplotlib.pyplot as plt edges = [ (6, 7), (7, 6), (7, 11), (11, 7), (11, 8), (8, 11), (8, 9), (9, 8), (9, 5), (5, 9), (5, 10), (10, 5), (10, 2), (2, 10), (2, 1), (1, 2), (1, 0), (0, 1), (0, 12), (12, 0), (11, 14), (14, 11), (5, 3), (3, 4), (4, 13), (13, 4), ] G = ntx.MultiDiGraph() G.add_edges_from(edges) fig, ax = plt.subplots(figsize=(10, 10)) ntx.draw_networkx(G, with_labels=True) plt.show()
目标图形效果代码
new_edges = [ (6, 11), (11, 6), (11, 14), (14, 11), (11, 5), (5, 11), (5, 12), (12, 5), (5, 4), (4, 13), (13, 4), ] new_G = ntx.MultiDiGraph() new_G.add_edges_from(new_edges) fig, ax = plt.subplots(figsize=(10, 10)) ntx.draw_networkx(new_G, with_labels=True) plt.show()
解决方案
实现思路
核心是识别可压缩的中间节点,并逐步替换链边:
- 双向链中间节点:节点u需满足:
- 入度=1且出度=1
- 存在唯一前驱v和唯一后继w,且v≠w
- v与u、u与w之间均为双向边
此时直接建立v与w的双向边,移除u
- 单向链中间节点:节点u需满足:
- 入度=1且出度=1
- 存在唯一前驱v和唯一后继w,且v≠w
- v到u、u到w均为单向边(无反向边)
此时直接建立v到w的单向边,移除u
- 循环执行上述操作,直到没有可压缩节点(移除中间节点可能产生新的可压缩节点)
代码实现
import networkx as ntx import matplotlib.pyplot as plt def compress_multi_digraph(G): # 复制原图,避免修改原始数据 compressed = ntx.MultiDiGraph(G) changed = True while changed: changed = False nodes_to_remove = [] edges_to_add = [] for u in list(compressed.nodes()): in_edges = list(compressed.in_edges(u)) out_edges = list(compressed.out_edges(u)) # 入度和出度都为1才可能是中间节点 if len(in_edges) != 1 or len(out_edges) != 1: continue v, _ = in_edges[0] w, _ = out_edges[0] if v == w: continue # 跳过自环 # 判断是否为双向链中间节点 is_bidirectional = compressed.has_edge(u, v) and compressed.has_edge(w, u) # 判断是否为单向链中间节点 is_unidirectional = not compressed.has_edge(u, v) and not compressed.has_edge(w, u) if is_bidirectional: edges_to_add.append((v, w)) edges_to_add.append((w, v)) nodes_to_remove.append(u) changed = True elif is_unidirectional: edges_to_add.append((v, w)) nodes_to_remove.append(u) changed = True # 先添加新边,再移除节点,避免遍历过程中修改图结构出错 compressed.add_edges_from(edges_to_add) compressed.remove_nodes_from(nodes_to_remove) return compressed # 构建原始图 edges = [ (6, 7), (7, 6), (7, 11), (11, 7), (11, 8), (8, 11), (8, 9), (9, 8), (9, 5), (5, 9), (5, 10), (10, 5), (10, 2), (2, 10), (2, 1), (1, 2), (1, 0), (0, 1), (0, 12), (12, 0), (11, 14), (14, 11), (5, 3), (3, 4), (4, 13), (13, 4), ] G = ntx.MultiDiGraph() G.add_edges_from(edges) # 压缩图 compressed_G = compress_multi_digraph(G) # 绘制对比图 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10)) ntx.draw_networkx(G, ax=ax1, with_labels=True) ax1.set_title("原始图") ntx.draw_networkx(compressed_G, ax=ax2, with_labels=True) ax2.set_title("压缩后图") plt.show() # 输出压缩后的边列表验证结果 print("压缩后的边列表:") print(sorted(compressed_G.edges()))
代码说明
- 函数通过循环迭代处理节点,每次遍历所有节点识别可压缩目标
- 先收集所有待添加的边和待移除的节点,避免遍历过程中修改图结构导致异常
- 双向链与单向链的判断逻辑,自动排除了双向边与单向边相连的节点(这类节点不满足压缩条件)
- 循环直到无节点可压缩,确保长链被完全压缩
验证结果
运行代码后,压缩后的边列表将与目标图形的new_edges一致,完全满足需求中的节点压缩效果。
内容的提问来源于stack exchange,提问作者MaxDragonheart
相关产品推荐
相关产品推荐

