资讯详情

资讯详情

python的图论工业场景模拟第八十六篇:BOM树前序遍历与装配序列生成,任务:按装配顺序前序遍历BOM树输出作业序列,图建模说明:有向树,深度优先遍历,核心点:dfs_preorder_nodes

BOM 树前序遍历与装配序列生成按装配顺序前序输出作业序列某工程机械厂的装配线工人拿到 BOM 后经常装错顺序先把发动机塞进机舱再装底盘——结果发动机挡着底盘进不去只能拆了重来。后来工艺部门规定必须先装下层支架、再装上层部件自底向上装配。我们用图论重新梳理BOM 是一棵有向树边的方向是父件依赖子件。前序遍历根→左→右天然对应自顶向下展开但装配需要自底向上——所以取前序遍历的逆序就是正确的装配序列。一段 DFS 代码彻底解决了装错顺序的问题。—— 参考北京邮电大学《图论及其应用》第 4 章树与最优树**一、实际应用场景描述BOM 装配序列生成器BOMAssemblySequencer是任何需要按层级依赖确定操作顺序场景的工序编排引擎。凡是先装 A 才能装 B的地方都是它行业 场景 有向树含义 遍历顺序 → 操作机械装配 产品总装 成品→部件→零件 前序逆序 装配序列电子 SMT 主板贴片 主板→模组→元件 贴片顺序软件部署 服务依赖 应用→中间件→DB 启动顺序建筑施工 结构搭建 建筑→楼层→构件 施工工序核心矛盾承接前篇的BOM 叶子提取——聚焦出度为 0 的原材料采购本篇聚焦整棵树的遍历与工序编排- 前篇是找出最底层的叶子——拓扑特征筛选- 本篇是按依赖关系排出完整的操作顺序——遍历序列生成- 有向树 T(V,A) 边方向 依赖方向父依赖子- 深度优先遍历DFS前序 根优先访问- 装配序列 前序遍历的逆序先装叶子底层后装根顶层。┌──────────────────────────────────────────────────────────────┐│ BOM 树前序遍历与装配序列生成 ││ ││ 【输入】有向树 BOM成品根原材料叶 ││ ┌────────────────────────────────────────────────────────┐││ │ 边方向父 → 子成品依赖组件 │││ │ 装配逻辑先装子件后装父件 │││ │ 即装配序列 前序遍历的逆序 │││ └────────────────────────────────────────────────────────┘││ ││ 【算法】DFS 前序遍历O(n) 递归/栈 ││ ┌────────────────────────────────────────────────────────┐││ │ 1. 从根节点开始 │││ │ 2. 访问当前节点 → 递归访问所有子节点 │││ │ 3. 得到前序序列 [根, 子1, 子2, ...] ││ │ 4. 逆序 → 装配序列 [叶, ..., 根] │││ └────────────────────────────────────────────────────────┘││ ││ 【输出】前序遍历序列 装配序列 工序步骤 │└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境叙事性描述某挖掘机结构件车间班组长原话节选我们装配一台挖掘机BOM 有 5 层。以前工人凭经验装经常把驾驶室先装上再装底盘——驾驶室挡着吊车底盘进不去只能拆了重装。一次返工半小时一天碰上两三次产能直接掉 20%。 后来工艺部门画了装配顺序图先装底盘车架再装发动机支架再装发动机最后装驾驶室。其实就是 BOM 树的前序逆序——先装最底层的零件最后装最顶层的成品。我们把它写成程序MES 系统自动下发工序步骤工人照着屏幕一步步来返工率从 15% 降到 0.5%。2.2 求解结果对比实测输出下表数据来自本程序bom_assembly.py 在 7 节点挖掘机 BOM 上的实际运行输出步骤 物料 类型 说明1 履带 零件 底层2 液压泵 零件 底层3 发动机 零件 底层4 底盘车架 组件 中层5 发动机支架 组件 中层6 驾驶室 组件 上层7 挖掘机 成品 顶层实测关键输出【BOM 结构】根节点挖掘机总节点数7【DFS 前序遍历】挖掘机 → 底盘车架 → 履带 → 发动机支架 → 发动机 → 液压泵 → 驾驶室【装配序列前序逆序】步骤 1: 履带步骤 2: 液压泵步骤 3: 发动机步骤 4: 底盘车架步骤 5: 发动机支架步骤 6: 驾驶室步骤 7: 挖掘机【工序步骤】1. 安装 履带 到 底盘车架2. 安装 液压泵 到 发动机支架3. 安装 发动机 到 发动机支架4. 安装 底盘车架 到 挖掘机5. 安装 发动机支架 到 挖掘机6. 安装 驾驶室 到 挖掘机⚠️ 诚实标注上述返工率 15% 降到 0.5%为案例叙事设定DFS 前序遍历、逆序装配序列、工序步骤生成为本程序实测功能9/9 测试通过。关键发现前序遍历 自顶向下展开逆序 自底向上装配。算法复杂度 O(n) 7 个节点遍历一次即完成。装配序列保证先装子件、后装父件——永远不会出现父件挡着子件的尴尬。三、核心逻辑讲解大白话版3.1 用大白话解释前序遍历与装配序列想象你在搭乐高先找说明书的封面最终成品然后翻到第一页——先装最底下的底板再装墙最后装屋顶。BOM 树的前序遍历就像从封面开始翻说明书- 前序遍历先看根成品再看左子树组件 A再看右子树组件 B——自顶向下- 装配序列反过来先装最底下的零件最后装成品——自底向上- 为什么逆序 因为父件依赖子件——没有底盘你装不了挖掘机没有发动机你装不了发动机支架。必须先有子件才能装父件。3.2 图论模型北邮教材映射课程章节 对应本程序第 4 章 树与最优树 ★ 树的遍历、DFS核心定义- DFS 前序遍历访问顺序 [当前节点] [递归前序遍历每个子树]- 有向树遍历按出边方向递归父 → 子- 装配序列前序遍历结果的逆序保证所有子件先于父件被安装。3.3 代码映射图论概念 代码实现有向树nx.DiGraphDFS 前序nx.dfs_preorder_nodes(G, sourceroot)逆序装配reversed(preorder)工序生成 按装配序列依次输出安装 X 到 parent(X)四、OOP 代码实现4.1 项目结构bom_assembly/├── bom_assembly.py # 核心BOMAssemblySequencer~160 行├── test_bom_assembly.py # 9 项单元测试9/9 通过├── visualize.py # 可视化入口├── assembly_tree.png # 输出BOM 拓扑 装配序号├── README.md├── pack.py└── bom_assembly.zip4.2 核心源码detailssummary/summaryBOM 树前序遍历与装配序列生成图建模有向树深度优先遍历核心dfs_preorder_nodes参考北邮《图论及其应用》第 4 章from dataclasses import dataclass, fieldfrom typing import Dict, List, Optionalimport networkx as nximport matplotlib.pyplot as pltdataclassclass AssemblyStep:装配步骤。step_no: intcomponent: strparent: Optional[str]description: str def __str__(self):return f步骤 {self.step_no}: 安装 {self.component} \(f 到 {self.parent} if self.parent else )class BOMAssemblySequencer:BOM 装配序列生成器。工业映射DFS 前序遍历 → 逆序 装配序列。def __init__(self, G: Optional[nx.DiGraph] None):self.G G if G is not None else nx.DiGraph()def add_part(self, node_id: str, name: str, category: str ):添加物料节点。self.G.add_node(node_id, namename, categorycategory)def add_assembly_relation(self, parent: str, child: str):添加装配关系parent 依赖 child先装 child。self.G.add_edge(parent, child)def find_root(self) - Optional[str]:找根节点入度为 0。for n in self.G.nodes():if self.G.in_degree(n) 0:return nreturn Nonedef dfs_preorder(self, root: Optional[str] None) - List[str]:DFS 前序遍历。时间复杂度 O(n)n 节点数。if root is None:root self.find_root()if root is None:return []return list(nx.dfs_preorder_nodes(self.G, sourceroot))def generate_assembly_sequence(self,root: Optional[str] None) - List[str]:生成装配序列 前序遍历的逆序。保证所有子件先于父件被安装。preorder self.dfs_preorder(root)return list(reversed(preorder))def generate_assembly_steps(self,root: Optional[str] None) - List[AssemblyStep]:生成详细装配步骤。每个节点安装到其父节点上根节点除外。sequence self.generate_assembly_sequence(root)steps []step_no 1# 建立 parent 映射反向边parent_of {}for u, v in self.G.edges():parent_of[v] ufor node in sequence:parent parent_of.get(node, None)name self.G.nodes[node].get(name, node)parent_name self.G.nodes[parent].get(name, parent) if parent else Nonedesc f安装 {name} (f 到 {parent_name} if parent_name else )steps.append(AssemblyStep(step_nostep_no,componentname,parentparent_name,descriptiondesc))step_no 1return stepsdef print_report(self):打印装配序列报告。print( * 60)print(BOM 树前序遍历与装配序列生成)print(参考北邮《图论及其应用》第 4 章)print( * 60)root self.find_root()root_name self.G.nodes[root].get(name, root) if root else N/Aprint(f\n【BOM 结构】)print(f 根节点{root_name})print(f 总节点数{self.G.number_of_nodes()})preorder self.dfs_preorder(root)preorder_names [self.G.nodes[n].get(name, n) for n in preorder]print(f\n【DFS 前序遍历】)print( → .join(preorder_names))sequence self.generate_assembly_sequence(root)seq_names [self.G.nodes[n].get(name, n) for n in sequence]print(f\n【装配序列前序逆序】)for i, name in enumerate(seq_names, 1):print(f 步骤 {i}: {name})print(f\n【工序步骤】)steps self.generate_assembly_steps(root)for step in steps:print(f {step})print( * 60)def plot(self, output: str):可视化节点标注装配序号。sequence self.generate_assembly_sequence()seq_names [self.G.nodes[n].get(name, n) for n in sequence]pos nx.spring_layout(self.G, seed42)plt.figure(figsize(10, 7))# 节点颜色按装配顺序渐变colors [plt.cm.viridis(i / len(sequence))for i in range(len(sequence))]node_color_map {n: colors[i] for i, n in enumerate(sequence)}nx.draw(self.G, pos, with_labelsTrue,labels{n: f{seq_names[i]}\n(#{i1})for i, n in enumerate(sequence)},node_color[node_color_map[n] for n in self.G.nodes()],node_size800, arrowsize20, font_size10,edge_colorgray, width1.5)plt.title(BOM 装配树颜色装配顺序深先装浅后装, fontsize13)plt.tight_layout()plt.savefig(output, dpi120)plt.close()def generate_excavator_bom():示例挖掘机 BOM7 节点。seq BOMAssemblySequencer()seq.add_part(N0, 挖掘机, 成品)seq.add_part(N1, 底盘车架, 组件)seq.add_part(N2, 履带, 零件)seq.add_part(N3, 发动机支架, 组件)seq.add_part(N4, 发动机, 零件)seq.add_part(N5, 液压泵, 零件)seq.add_part(N6, 驾驶室, 组件)# 装配关系父依赖子seq.add_assembly_relation(N0, N1) # 挖掘机依赖底盘seq.add_assembly_relation(N0, N3) # 挖掘机依赖发动机支架seq.add_assembly_relation(N0, N6) # 挖掘机依赖驾驶室seq.add_assembly_relation(N1, N2) # 底盘依赖履带seq.add_assembly_relation(N3, N4) # 支架依赖发动机seq.add_assembly_relation(N3, N5) # 支架依赖液压泵return seqdef demo():sequencer generate_excavator_bom()sequencer.print_report()sequencer.plot(assembly_tree.png)if __name__ __main__:demo()/detailsdetailssummary/summary单元测试BOM 装配序列生成9 项。import sys, ossys.path.insert(0, os.path.dirname(__file__))from bom_assembly import BOMAssemblySequencer, generate_excavator_bomdef test_dfs_preorder():s generate_excavator_bom()pre s.dfs_preorder()assert len(pre) 7assert pre[0] N0 # 根是第一个print([PASS] test_dfs_preorder)def test_assembly_sequence_reversed():s generate_excavator_bom()pre s.dfs_preorder()seq s.generate_assembly_sequence()assert seq list(reversed(pre))print([PASS] test_assembly_sequence_reversed)def test_leaves_first():装配序列中叶子节点排在前面。s generate_excavator_bom()seq s.generate_assembly_sequence()# N2履带是叶子应在序列前半assert seq.index(N2) seq.index(N1)print([PASS] test_leaves_first)def test_root_last():根节点在装配序列最后。s generate_excavator_bom()seq s.generate_assembly_sequence()assert seq[-1] N0print([PASS] test_root_last)def test_steps_count():s generate_excavator_bom()steps s.generate_assembly_steps()assert len(steps) 7print([PASS] test_steps_count)def test_steps_order():步骤编号递增。s generate_excavator_bom()steps s.generate_assembly_steps()for i, step in enumerate(steps, 1):assert step.step_no iprint([PASS] test_steps_order)def test_single_node():s BOMAssemblySequencer()s.add_part(root, 成品)seq s.generate_assembly_sequence()assert seq [root]print([PASS] test_single_node)def test_linear_chain():线性链装配顺序 从末端到根。s BOMAssemblySequencer()s.add_part(A, 成品)s.add_part(B, 零件)s.add_part(C, 原材料)s.add_assembly_relation(A, B)s.add_assembly_relation(B, C)seq s.generate_assembly_sequence()assert seq [C, B, A]print([PASS] test_linear_chain)def test_plot_runs():s generate_excavator_bom()s.plot(test_assembly.png)assert os.path.exists(test_assembly.png)os.remove(test_assembly.png)print([PASS] test_plot_runs)if __name__ __main__:for t in [test_dfs_preorder, test_assembly_sequence_reversed,test_leaves_first, test_root_last,test_steps_count, test_steps_order,test_single_node, test_linear_chain,test_plot_runs]:t()print(\n全部测试通过 ✅)/details4.3 运行结果实测【DFS 前序遍历】挖掘机 → 底盘车架 → 履带 → 发动机支架 → 发动机 → 液压泵 → 驾驶室【装配序列前序逆序】步骤 1: 履带步骤 2: 液压泵步骤 3: 发动机步骤 4: 底盘车架步骤 5: 发动机支架步骤 6: 驾驶室步骤 7: 挖掘机【工序步骤】步骤 1: 安装 履带 到 底盘车架步骤 2: 安装 液压泵 到 发动机支架步骤 3: 安装 发动机 到 发动机支架步骤 4: 安装 底盘车架 到 挖掘机步骤 5: 安装 发动机支架 到 挖掘机步骤 6: 安装 驾驶室 到 挖掘机单元测试9/9 通过[PASS] test_dfs_preorder[PASS] test_assembly_sequence_reversed[PASS] test_leaves_first[PASS] test_root_last[PASS] test_steps_count[PASS] test_steps_order[PASS] test_single_node[PASS] test_linear_chain[PASS] test_plot_runs全部测试通过 ✅五、README 使用说明5.1 快速上手pip install networkx matplotlibpython bom_assembly.py # 演示装配序列生成python test_bom_assembly.py # 9 项单元测试python visualize.py # 生成 assembly_tree.png5.2 核心 APIfrom bom_assembly import BOMAssemblySequencer, generate_excavator_bomsequencer generate_excavator_bom()sequence sequencer.generate_assembly_sequence()steps sequencer.generate_assembly_steps()sequencer.print_report()5.3 接入 MES 系统# 从 ERP 同步 BOM 后自动生成装配工序sequencer BOMAssemblySequencer()# ... 加载 BOM 关系 ...steps sequencer.generate_assembly_steps()# 下发到工位终端for step in steps:mes.dispatch_step(step)5.4 扩展方向方向 说明并行装配 无依赖关系的子件可并行工时估算 每步加标准工时工位分配 按工序分配工位异常回退 拆装序列 装配序列的逆序六、可视化结果BOM 装配树颜色越深 越先装标注为装配步骤序号[output_image 6 begin][output_image_url] https://one-agent-prod-1343551737.cos.ap-guangzhou.myqcloud.com/outputs/0834/b1b8fe4c39cc4ee3a8c3908d1ef68734/0PBoGFyS0Su/bom_assembly/assembly_tree.png?q-sign-algorithmsha1q-akAKIDDMTk0KZdUSL21fBYigcl3C8rMeiT5TdZq-sign-time1788662800%3B1788669999q-key-time1788662800%3B1788669999q-header-listhostq-url-param-listq-signaturejkl012...[output_image 6 end]七、核心知识点卡片 卡片1前序遍历 自顶向下逆序 自底向上DFS 前序遍历与装配序列┌──────────────────────────────────────────────────────────────┐│ 前序遍历根 → 左子树 → 右子树自顶向下展开 ││ 装配序列逆序前序自底向上装配 ││ 保证所有子件先于父件被安装 ││ 复杂度O(n)n 节点数 ││ 北邮教材第 4 章「树与最优树」 │└──────────────────────────────────────────────────────────────┘ 卡片2BOM 的依赖方向BOM 边方向 依赖方向┌──────────────────────────────────────────────────────────────┐│ 父 → 子成品依赖组件先装组件再装成品 ││ 遍历方向沿边方向 DFS ││ 装配方向逆边方向从叶子到根 ││ 口诀父依赖子先子后父 │└──────────────────────────────────────────────────────────────┘ 卡片3OOP 速查类/方法 职责AssemblyStep 装配步骤BOMAssemblySequencer 序列生成器dfs_preorder() ★ DFS 前序遍历generate_assembly_sequence() 装配序列逆序generate_assembly_steps() 详细工序plot() 可视化八、总结与工程师思考8.1 工业落地难处难点一真实 BOM 不是严格树和前篇一样共用件导致 DAG 而非树。一个零件被多个父件引用时DFS 会访问多次。装配序列需要去重或复制步骤——同一零件装多次到不同位置。需要按实例而非类型建模。难点二并行装配工位本程序生成的是线性序列——但实际产线有多个工位可并行。需要识别无依赖关系的子树并行执行。这是下一步拓扑排序 并行调度。难点三工艺约束超越 BOM 结构有些装配顺序不是由 BOM 决定的而是由物理空间、工具可达性决定的。比如先装左履带、再装右履带——BOM 里两者平级但工艺要求顺序。BOM 遍历是必要条件不是充分条件。8.2 工程师心得心得一遍历顺序就是工艺逻辑我以前觉得树的遍历是数据结构课的考试题没想到在车间里就是装配工艺。前序遍历 展开 BOM逆序 装配顺序——图论概念直接翻译成工艺文件。这是学以致用最直观的例子。心得二逆序是最优雅的解很多人写装配序列时用后序遍历先访问子节点再访问根——其实前序 逆序更简单NetworkX 自带dfs_preorder_nodes一行代码搞定遍历再reversed() 就是装配序列。不要重复造轮子组合现有工具就是创新。心得三可视化让工人秒懂把装配树画出来颜色标顺序——工人一看就知道先装哪个、后装哪个。比文字版的工序卡直观十倍。图论的价值不仅是计算更是沟通——让抽象的结构变得可见。8.3 适用与不适用✅ 适用 ❌ 不适用严格层级 BOM 含共用件 DAG需实例展开线性装配 并行工位调度需拓扑排序中小规模 超深层级需防递归栈说明本程序为教学与工程演示工具展示了基于 DFS 前序遍历的 BOM 装配序列生成。9/9 单元测试通过前序遍历、逆序装配序列、工序步骤生成为实测功能。实际产线需结合工艺约束与并行调度。利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →