绘制含公共节点的层级树时触发Python RecursionError问题
解决NetworkX绘制层级结构时的RecursionError问题
这问题我之前也碰到过,本质是你的图结构和布局函数的适配性出了问题,我帮你拆解下原因和解决办法:
问题根源
你现在用的是nx.Graph()(无向图),但_hierarchy_pos这个函数是为严格树结构设计的——树的特点是无环,且每个节点(除根节点外)只有一个父节点。
当多个分支指向同一个公共节点(比如B和C都指向G)时,无向图会给G和B、C建立双向连接。递归布局时,程序会陷入A→B→G→B→A→C→G→C→...的无限循环,直接触发Python的递归深度限制,也就是你看到的RecursionError。
解决方案
这里有几个可行的思路,你可以根据需求选择:
1. 改用有向图(最简单的修复)
把图换成nx.DiGraph()(有向图),让边保持单向性,同时给_hierarchy_pos加个小过滤,避免递归回溯父节点:
import networkx as nx import matplotlib.pyplot as plt import time timestr = time.strftime("%Y%m%d-%H%M%S") def _hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None): if pos is None: pos = {root: (xcenter, vert_loc)} else: pos[root] = (xcenter, vert_loc) # 获取当前节点的邻居,移除父节点防止回溯 children = list(G.neighbors(root)) if parent is not None and parent in children: children.remove(parent) if not children: return pos dx = width / len(children) nextx = xcenter - width/2 - dx/2 for child in children: nextx += dx pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, vert_loc=vert_loc-vert_gap, xcenter=nextx, pos=pos, parent=root) return pos def Create_Hierarchy(chunks): # 替换为有向图 G = nx.DiGraph() G.add_edges_from(chunks) pos = _hierarchy_pos(G, root='A', width=0.07, vert_gap=0.02, vert_loc=0.09, xcenter=0.50) nx.draw(G, pos, arrows=True, with_labels=True) plt.subplots_adjust(top=0.9, wspace=4.0, hspace=2.99) plt.savefig(f'D:\\Hierarchy{timestr}.png') plt.show() Create_Hierarchy(chunks=[('A','B'), ('A','C'), ('A','D'), ('A','E'), ('A','F'), ('B','G'), ('C','G'), ('D','I'), ('E','J'), ('F','K')])
2. 使用DAG专用布局(更专业的选择)
你的结构其实是有向无环图(DAG),不是严格的树。用NetworkX结合Graphviz的dot布局会更适合这种多分支汇聚的场景,布局效果也更规整:
首先需要安装依赖:
- 下载安装Graphviz软件(对应你的系统版本)
- 安装Python库:
pip install graphviz
然后修改代码:
import networkx as nx import matplotlib.pyplot as plt import time timestr = time.strftime("%Y%m%d-%H%M%S") def Create_Hierarchy(chunks): G = nx.DiGraph() G.add_edges_from(chunks) # 用Graphviz的dot布局自动处理DAG层级 pos = nx.nx_agraph.graphviz_layout(G, prog='dot') nx.draw(G, pos, arrows=True, with_labels=True) plt.subplots_adjust(top=0.9, wspace=4.0, hspace=2.99) plt.savefig(f'D:\\Hierarchy{timestr}.png') plt.show() Create_Hierarchy(chunks=[('A','B'), ('A','C'), ('A','D'), ('A','E'), ('A','F'), ('B','G'), ('C','G'), ('D','I'), ('E','J'), ('F','K')])
3. 给递归函数加访问标记(兼容无向图的方案)
如果你一定要用无向图,可以在_hierarchy_pos里加个已访问集合,避免重复遍历同一个节点:
def _hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None, visited=None): if visited is None: visited = set() visited.add(root) if pos is None: pos = {root: (xcenter, vert_loc)} else: pos[root] = (xcenter, vert_loc) # 只遍历未访问过的邻居 children = [child for child in G.neighbors(root) if child not in visited] if not children: return pos dx = width / len(children) nextx = xcenter - width/2 - dx/2 for child in children: nextx += dx pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, vert_loc=vert_loc-vert_gap, xcenter=nextx, pos=pos, parent=root, visited=visited) return pos
总结
核心问题就是无向图的双向边导致递归无限循环,要么换成有向图切断反向边,要么用更适合DAG的布局算法,要么给递归加访问标记避免重复遍历。
内容的提问来源于stack exchange,提问作者Chits
相关产品推荐
相关产品推荐

