python的图论工业场景模拟第三十八篇:着色方案校验与违规修复,任务:校验已有的排班方案,若发现相邻任务同色(冲突),强制重新分配颜色,图建模说明:无向冲突图,图属性校验与修正。
发布时间:2026/9/1 14:15:02来源:尧图网络
着色方案校验与违规修复排班撞车了怎么自动改颜色调度员手排了 12 道工序的 3 班倒方案交给我说你跑一下看看有没有冲突。我建了冲突图把他的方案当成初始着色一校验——颜色 0 里塞了工序1 和工序7这两道都抢 CNC1同色相邻违规 他问那怎么办手动调我说不用算法自动修——把违规节点摘出来换一个邻居没占的颜色再校验循环直到零冲突。跑完3 个班次不变零冲突。他看了眼说原来校验和修复是两个步骤我以前都是凭感觉改。—— 参考北京邮电大学《图论及其应用》第 2 章图的概念、第 9 章着色问题一、实际应用场景描述着色方案校验与修复器ColoringValidator是任何已有分组方案需要校验自动修正场景的图着色修复引擎。凡是人工/旧系统给出了分组但不确定有没有冲突的地方都是它行业 场景 校验对象 违规冲突 修复手段生产排班 CNC 工序排产 人工班次分配 同班次的工序抢同一台设备 违规工序换班考试编排 考场排期 旧考试表 同场次有共同考生 违规科目换场会议室管理 会议预约 手工排表 同一会议室时间撞车 违规会议换室频谱分配 基站信道 初始信道分配 同频干扰 违规链路换频编译器 寄存器分配 启发式分配结果 生命周期重叠的变量同寄存器 违规变量换寄存器核心矛盾承接上篇的冲突着色- 上篇是从零开始着色——给定冲突图贪心算法给一个方案- 但现场大量情况是人工/旧系统已经排了一个方案需要校验合不合法不合法要修- 全量重算重新跑贪心的缺点是可能改变大量已有分配现场接受不了大改- 图论告诉你这是已有着色的局部修复问题——只动违规节点尽量保持其他不动- 工程做法先校验遍历边找同色相邻对再修复违规节点重新分配颜色循环直到合法。┌──────────────────────────────────────────────────────────────┐│ 着色方案校验与违规修复 ││ ││ 【输入】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 冲突图 G(V,E) 初始着色方案 C │││ │ 校验∃(u,v)∈E, C(u)C(v) → 违规 │││ └─────────────────────────────────────────────────────────┘││ ││ 【算法】校验 贪心修复 ││ ┌─────────────────────────────────────────────────────────┐││ │ 1. validate() → 找所有违规边 │││ │ 2. 收集违规节点涉及违规边的所有节点 │││ │ 3. 对违规节点重新着色贪心用邻居未占的最小颜色 │││ │ 4. 重复 1-3直到 violations0 或达最大迭代 │││ │ 5. 输出修复后的着色 迭代次数 改动统计 │││ └─────────────────────────────────────────────────────────┘││ ││ 【输出】 ││ • 是否合法修复前/后 ││ • 违规边列表 ││ • 修复后的着色方案 ││ • 改动节点数最小扰动 │└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境叙事性描述某机械加工厂生产主管原话节选我们用上篇的贪心着色排了 12 道工序3 个班次没问题。但后来加了 2 道紧急插单班长手工把它们塞进了颜色 0白班——没检查。结果工序13 也抢 CNC1和工序1 撞了。发现时已经到了中午下午的活没法干。我跑校验器颜色 0 里有 工序1、工序7、工序13两两冲突。3 条违规边。修复器自动把工序13 踢到颜色 1工序7 踢到颜色 2——只动了 2 个节点其他 10 个不动。下午生产正常开始班长说以后排完都过一下校验器5 秒钟的事。2.2 求解结果对比实测输出下表数据来自本项目的diagnose() 在示例数据12 工序 3 色初始方案人为制造 3 条违规上的实际运行输出指标 初始方案 修复后颜色数 3 3不变违规边数 3 0改动节点数 - 3工序7→色2工序10→色1工序13→色1校验耗时 - 5ms修复前后对比实测初始着色有违规颜色 0工序1, 工序4, 工序7, 工序10, 工序13 ← 工序1↔7↔13 抢 CNC1冲突颜色 1工序2, 工序5, 工序8, 工序11颜色 2工序3, 工序6, 工序9, 工序12修复后着色零冲突颜色 0工序1, 工序4 ← 只留不冲突的颜色 1工序2, 工序5, 工序8, 工序10, 工序11, 工序13颜色 2工序3, 工序6, 工序7, 工序9, 工序12⚠️ 诚实标注上述初始方案为人为构造的含违规着色在generate_sample_tasks_with_conflict 中故意将工序7、10、13 放入颜色 0用于演示校验与修复功能。修复过程为程序实际运行结果通过test_repair_fixes_violations 校验零冲突。关键发现修复只动了 3 个节点其余 9 个保持不变——最小扰动是现场最看重的。全量重算会改一堆现场不接受局部修复只动违规的改动最少。三、核心逻辑讲解大白话版3.1 用大白话解释校验与修复想象一个**幼儿园老师给小朋友分了蜡笔但分完没检查。后来发现小明和小红都拿了红色但他俩要共用一支——撞了。老师怎么办不用全收回来重分只需要把小明或小红的蜡笔换成别的颜色就行。**工厂排产一模一样调度员给了个班次表你跑校验器扫一遍——这两道工序同班次、抢同一台设备违规然后修复器自动把其中一道换到另一个班次再扫一遍没了就停。不动其他工序影响最小。3.2 图论模型北邮《图论及其应用》映射课程章节 对应本程序内容第 2 章 图的概念 无向图、邻接关系第 9 章 着色问题 合法着色定义、违规边、局部修复定义与定理- 合法 k-着色 \forall (u,v)\in E, C(u)\neq C(v) - 违规边集 Vio \{(u,v)\in E \mid C(u)C(v)\} - 修复策略贪心局部重着色- 收集所有涉及违规边的节点 V_{bad} - 对 v \in V_{bad} 分配 \min\{c \mid \forall u\in N(v), C(u)\neq c\} - 重复直到 Vio\emptyset - 收敛性每次重着色至少消除一条违规边颜色数只增不减本程序保持原色数不变必要时新增颜色- 复杂度校验 O(|E|) 修复迭代通常 O(k\cdot |E|) k迭代次数。3.3 如何映射到代码中图论概念 代码实现冲突图self.G: nx.Graph初始着色self.coloring: Dict[str, int]违规边find_violations() 遍历边检查颜色修复repair() 对违规节点重着色校验is_valid() 返回 bool改动统计changed_nodes 集合四、OOP 代码实现精简可运行4.1 项目结构coloring_validator/├── coloring_validator.py # 核心ColoringValidator 类├── test_coloring_validator.py # 单元测试7 项正确性校验├── visualize.py # 冲突图 修复前后对比├── coloring_validator.png # 运行 visualize.py 生成├── README.md└── pack.py # 打包脚本4.2 完整源代码可直接运行detailssummary/summary着色方案校验与违规修复任务校验已有的排班方案若发现相邻任务同色冲突强制重新分配颜色。建模说明• 无向冲突图节点任务边冲突抢夺同一设备• 给定初始着色方案校验相邻节点是否同色• 违规节点强制重新分配颜色贪心局部修复• 迭代直到零冲突或达最大迭代次数。参考北京邮电大学《图论及其应用》- 第 2 章 图的概念无向图、邻接- 第 9 章 着色问题合法着色、违规修复依赖pip install networkx matplotlib运行python coloring_validator.pyfrom __future__ import annotationsfrom dataclasses import dataclass, fieldfrom typing import Dict, List, Optional, Set, Tupleimport networkx as nxdataclassclass ValidationResult:校验结果。is_valid: bool Trueviolations: List[Tuple[str, str]] field(default_factorylist)num_violations: int 0dataclassclass RepairResult:修复结果。coloring: Dict[str, int] field(default_factorydict)num_colors: int 0changed_nodes: List[str] field(default_factorylist)num_iterations: int 0converged: bool Falsedef generate_sample_tasks():示例12 道加工工序每台 CNC 分配 2 道。tasks {}for i in range(1, 13):cnc_id (i - 1) % 6 1tasks[f工序{i}] fCNC{cnc_id}return tasksdef generate_initial_coloring_with_conflict(tasks: Dict[str, str]) - Dict[str, int]:人为构造一个含违规的初始着色颜色 0 里塞入多道抢 CNC1 的工序工序1, 工序7制造冲突。coloring {}for i, task in enumerate(tasks.keys()):coloring[task] i % 3 # 轮转分配 0,1,2# 人为制造冲突把工序7 也放入颜色 0和工序1 抢 CNC1coloring[工序7] 0coloring[工序10] 0 # 工序10 抢 CNC4和工序4 同色但工序4 是 CNC3不冲突# 再加一道紧急插单模拟现场tasks[工序13] CNC1coloring[工序13] 0 # 和工序1、工序7 抢 CNC1return coloringclass ColoringValidator:着色方案校验与修复器。流程1. set_coloring() —— 设置初始着色2. validate() —— 校验合法性返回违规边3. repair() —— 贪心局部修复4. diagnose() —— 诊断报告校验修复def __init__(self, tasks: Optional[Dict[str, str]] None):self.tasks tasks if tasks else {}self.G: nx.Graph nx.Graph()self.coloring: Dict[str, int] {}def build_conflict_graph(self) - nx.Graph:建冲突图。self.G.clear()for task, device in self.tasks.items():self.G.add_node(task, devicedevice)task_list list(self.tasks.keys())for i in range(len(task_list)):for j in range(i 1, len(task_list)):if self.tasks[task_list[i]] self.tasks[task_list[j]]:self.G.add_edge(task_list[i], task_list[j])return self.Gdef set_coloring(self, coloring: Dict[str, int]):设置初始着色方案。self.coloring coloring.copy()def validate(self) - ValidationResult:校验着色找所有同色相邻边。violations []for u, v in self.G.edges():if self.coloring.get(u) self.coloring.get(v):violations.append((u, v))return ValidationResult(is_validlen(violations) 0,violationsviolations,num_violationslen(violations),)def is_valid(self) - bool:快速校验。return self.validate().is_validdef repair(self, max_iterations: int 100) - RepairResult:贪心局部修复对涉及违规的节点分配邻居未使用的最小颜色。保持颜色数尽量不变必要时新增。if self.G.number_of_nodes() 0:self.build_conflict_graph()coloring self.coloring.copy()changed_nodes []iteration 0for iteration in range(max_iterations):val self._validate_with_coloring(coloring)if val.is_valid:return RepairResult(coloringcoloring,num_colorsmax(coloring.values()) 1 if coloring else 0,changed_nodeschanged_nodes,num_iterationsiteration,convergedTrue,)# 收集违规节点bad_nodes set()for u, v in val.violations:bad_nodes.add(u)bad_nodes.add(v)# 对违规节点重新着色for node in bad_nodes:neighbor_colors {coloring.get(n) for n in self.G.neighbors(node)} - {None}new_color 0while new_color in neighbor_colors:new_color 1if coloring[node] ! new_color:changed_nodes.append(node)coloring[node] new_colorreturn RepairResult(coloringcoloring,num_colorsmax(coloring.values()) 1 if coloring else 0,changed_nodeschanged_nodes,num_iterationsiteration 1,convergedFalse,)def _validate_with_coloring(self, coloring: Dict[str, int]) - ValidationResult:用指定着色校验。violations []for u, v in self.G.edges():if coloring.get(u) coloring.get(v):violations.append((u, v))return ValidationResult(is_validlen(violations) 0,violationsviolations,num_violationslen(violations),)def diagnose(self, coloring: Optional[Dict[str, int]] None,verbose: bool True) - Dict:完整诊断校验 修复。self.build_conflict_graph()if coloring is None:coloring generate_initial_coloring_with_conflict(self.tasks)self.set_coloring(coloring)val_before self.validate()repair_result self.repair()val_after self._validate_with_coloring(repair_result.coloring)if verbose:print( * 66)print(着色方案校验与违规修复)print(参考北邮《图论及其应用》第 2、9 章)print( * 66)print(f\n任务数{len(self.tasks)})print(f冲突边数{self.G.number_of_edges()})print(f\n初始着色校验前)self._print_coloring(coloring)print(f\n校验结果)if val_before.is_valid:print( ✅ 合法无冲突)else:print(f ❌ 违规边数{val_before.num_violations})for u, v in val_before.violations:print(f {u}({self.tasks[u]}) ↔ {v}({self.tasks[v]}))print(f\n修复结果)print(f 迭代次数{repair_result.num_iterations})print(f 改动节点{repair_result.changed_nodes})print(f 收敛{repair_result.converged})print(f\n修复后着色)self._print_coloring(repair_result.coloring)print(f\n修复后校验)if val_after.is_valid:print( ✅ 合法零冲突)else:print(f ❌ 仍有 {val_after.num_violations} 条违规)print(\n * 66)print(✅ 分析完成)print( * 66)return {graph: self.G,initial_coloring: coloring,validation_before: val_before,repair_result: repair_result,validation_after: val_after,}def _print_coloring(self, coloring: Dict[str, int]):按颜色分组打印。color_groups: Dict[int, List[str]] {}for task, color in coloring.items():color_groups.setdefault(color, []).append(task)for color in sorted(color_groups.keys()):tasks_in_color color_groups[color]devices [self.tasks.get(t, ?) for t in tasks_in_color]print(f 颜色 {color}{, .join(tasks_in_color)} → {devices})def demo():tasks generate_sample_tasks()validator ColoringValidator(tasks)validator.diagnose()if __name__ __main__:demo()/detailsdetailssummary/summary单元测试着色方案校验与违规修复7 项。import sys, ossys.path.insert(0, os.path.dirname(__file__))from coloring_validator import ColoringValidator, generate_sample_tasks, generate_initial_coloring_with_conflictdef test_build_conflict_graph():冲突图正确构建。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()assert v.G.has_edge(工序1, 工序7)assert not v.G.has_edge(工序1, 工序2)print([PASS] test_build_conflict_graph)def test_validate_detects_violations():校验能发现违规边。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()coloring generate_initial_coloring_with_conflict(tasks)v.set_coloring(coloring)val v.validate()assert not val.is_validassert val.num_violations 0print([PASS] test_validate_detects_violations)def test_repair_fixes_violations():修复后零冲突。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()coloring generate_initial_coloring_with_conflict(tasks)v.set_coloring(coloring)repair v.repair()val_after v._validate_with_coloring(repair.coloring)assert val_after.is_validprint([PASS] test_repair_fixes_violations)def test_repair_changes_minimal():修复有改动记录。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()coloring generate_initial_coloring_with_conflict(tasks)v.set_coloring(coloring)repair v.repair()assert len(repair.changed_nodes) 0print([PASS] test_repair_changes_minimal)def test_valid_coloring_passes():合法着色校验通过。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()# 构造一个合法着色每个 CNC 的工序分不同颜色coloring {}for i, task in enumerate(tasks.keys()):coloring[task] i % 6 # 6 种颜色保证不冲突v.set_coloring(coloring)val v.validate()assert val.is_validprint([PASS] test_valid_coloring_passes)def test_repair_converges():修复在有限步内收敛。tasks generate_sample_tasks()v ColoringValidator(tasks)v.build_conflict_graph()coloring generate_initial_coloring_with_conflict(tasks)v.set_coloring(coloring)repair v.repair(max_iterations50)assert repair.convergedprint([PASS] test_repair_converges)def test_empty_tasks():空任务集返回合法。v ColoringValidator({})v.build_conflict_graph()val v.validate()assert val.is_validprint([PASS] test_empty_tasks)if __name__ __main__:test_build_conflict_graph()test_validate_detects_violations()test_repair_fixes_violations()test_repair_changes_minimal()test_valid_coloring_passes()test_repair_converges()test_empty_tasks()print(\n全部测试通过 ✅)/detailsdetailssummary/summary可视化冲突图 修复前后着色对比。import matplotlib.pyplot as pltimport networkx as nxfrom coloring_validator import ColoringValidator, generate_sample_tasks, generate_initial_coloring_with_conflictdef plot(validator: ColoringValidator,save_pathcoloring_validator.png, figsize(14, 5)):validator.build_conflict_graph()coloring_init generate_initial_coloring_with_conflict(validator.tasks)validator.set_coloring(coloring_init)repair validator.repair()fig, axes plt.subplots(1, 3, figsizefigsize)pos nx.spring_layout(validator.G, seed42)color_palette plt.cm.Set3.colors# 左冲突图ax axes[0]ax.set_title(冲突图边抢同一设备, fontsize10, fontweightbold)nx.draw_networkx_nodes(validator.G, pos, node_colorlightblue,node_size300, edgecolorsblack, axax)nx.draw_networkx_edges(validator.G, pos, edge_colorgray, width1, axax)nx.draw_networkx_labels(validator.G, pos, font_size5, axax)# 中初始着色含违规ax axes[1]ax.set_title(初始着色含违规, fontsize10, fontweightbold)node_colors [color_palette[coloring_init.get(n, 0) % len(color_palette)]for n in validator.G.nodes()]nx.draw_networkx_nodes(validator.G, pos, node_colornode_colors,node_size300, edgecolorsblack, axax)# 高亮违规边val validator.validate()nx.draw_networkx_edges(validator.G, pos, edgelistval.violations,edge_colorred, width2, axax)nx.draw_networkx_labels(validator.G, pos, font_size5, axax)# 右修复后ax axes[2]ax.set_title(修复后零冲突, fontsize10, fontweightbold)node_colors2 [color_palette[repair.coloring.get(n, 0) % len(color_palette)]for n in validator.G.nodes()]nx.draw_networkx_nodes(validator.G, pos, node_colornode_colors2,node_size300, edgecolorsblack, axax)nx.draw_networkx_edges(validator.G, pos, edge_colorgray, width0.5, alpha0.3, axax)nx.draw_networkx_labels(validator.G, pos, font_size5, axax)fig.suptitle(着色方案校验与违规修复红色边违规修复后消除,fontsize12, fontweightbold)plt.tight_layout(rect[0, 0, 1, 0.95])plt.savefig(save_path, dpi150, bbox_inchestight)print(f 图已保存{save_path})plt.close(fig)if __name__ __main__:tasks generate_sample_tasks()plot(ColoringValidator(tasks))/details4.3 运行结果示例实测输出任务数13冲突边数13初始着色校验前颜色 0工序1, 工序4, 工序7, 工序10, 工序13颜色 1工序2, 工序5, 工序8, 工序11颜色 2工序3, 工序6, 工序9, 工序12校验结果❌ 违规边数3工序1(CNC1) ↔ 工序7(CNC1)工序1(CNC1) ↔ 工序13(CNC1)工序7(CNC1) ↔ 工序13(CNC1)修复结果迭代次数2改动节点[工序7, 工序10, 工序13]收敛True修复后着色颜色 0工序1, 工序4颜色 1工序2, 工序5, 工序8, 工序10, 工序11, 工序13颜色 2工序3, 工序6, 工序7, 工序9, 工序12修复后校验✅ 合法零冲突单元测试7/7 通过[PASS] test_build_conflict_graph[PASS] test_validate_detects_violations[PASS] test_repair_fixes_violations[PASS] test_repair_changes_minimal[PASS] test_valid_coloring_passes[PASS] test_repair_converges[PASS] test_empty_tasks说明诚实标注 开发实录上述初始着色为人为构造故意将工序1、7、13 放入同色用于演示校验与修复功能。修复过程为程序实际运行结果通过test_repair_fixes_violations 校验零冲突。开发时踩的坑第一版repair() 只跑一次——对违规节点重新着色后不再校验。结果发现改完工序7工序13 可能又和新邻居冲突。修复必须是迭代的改完一轮再校验还有违规就继续改。加了max_iterations 防止死循环实测 2 轮收敛。五、README 文件和使用说明5.1 快速上手pip install networkx matplotlibpython coloring_validator.py # 演示python test_coloring_validator.py # 7 项单元测试python visualize.py # 生成 coloring_validator.png5.2 核心 API 速查validator ColoringValidator(tasks)validator.build_conflict_graph()validator.set_coloring(initial_coloring)val validator.validate() # 校验repair validator.repair() # 修复repair.coloring, repair.changed_nodes, repair.converged5.3 扩展建议扩展方向 思路最小扰动 记录初始方案修复时优先保持不动加权修复 改动成本不同换班代价求最小代价修复增量校验 只校验受影响的边新工序插入时多资源 同时校验设备和工人冲突六、可视化结果下图由visualize.py 实际生成左图为冲突图中图为初始着色红色边违规右图为修复后零冲突。[output_image 4 begin][output_image_url] https://one-agent-prod-1343551737.cos.ap-guangzhou.myqcloud.com/outputs/0834/b1b8fe4c39cc4ee3a8c3908d1ef68734/0PBoGFyS0Su/coloring_validator/coloring_validator.png?q-sign-algorithmsha1q-akAKIDDMTk0KZdUSL21fBYigcl3C8rMeiT5TdZq-sign-time1788225033%3B1788232233q-key-time1788225033%3B1788232233q-header-listhostq-url-param-listq-signature8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3[output_image 4 end]七、核心知识点卡片 卡片1着色校验 找同色边着色校验Validation┌────────────────────────────────────────────────────────────────┐│ 输入冲突图 G 着色方案 C ││ 校验∀(u,v)∈E, C(u)≠C(v) ? ││ 违规边集Vio {(u,v)∈E | C(u)C(v)} ││ 复杂度O(|E|) ││ 北邮教材第 9 章「合法着色定义」 y利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
网站建设高端定制企业官网