You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何缩减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()

解决方案

实现思路

核心是识别可压缩的中间节点,并逐步替换链边:

  1. 双向链中间节点:节点u需满足:
    • 入度=1且出度=1
    • 存在唯一前驱v和唯一后继w,且v≠w
    • v与u、u与w之间均为双向边
      此时直接建立v与w的双向边,移除u
  2. 单向链中间节点:节点u需满足:
    • 入度=1且出度=1
    • 存在唯一前驱v和唯一后继w,且v≠w
    • v到u、u到w均为单向边(无反向边)
      此时直接建立v到w的单向边,移除u
  3. 循环执行上述操作,直到没有可压缩节点(移除中间节点可能产生新的可压缩节点)

代码实现

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 12:10:23