资讯动态

图论建模与Python实现:复杂路口信号配时优化实战

发布时间:2026/10/13 4:04:37 来源:尧图企业网站定制
前几天调一个五岔路口的信号方案逼着自己把“拍脑袋配时”换成了图论建模效果比想象中好。先说明白我在解决什么问题一个路口有多个进口、每个进口又有直行、左转、右转若干股车流它们都想同时通过路口可平面轨迹上互相交叉、合流、分流谁和谁能一起放行谁和谁必须错开本质上是一个“冲突管理”问题。把这个冲突关系画成图用图着色算法去分相位再用 Python 实现配时计算出来的方案比自己凭感觉排相位严谨得多。这篇文章完整讲一遍我的做法从路口几何到冲突图建模从顶点着色生成信号相位到用韦伯斯特公式计算绿灯时长最后用一个五岔路口案例走通全流程。适合正在做交通信号优化项目、毕业设计或者想了解图论怎么落地的朋友。代码基于 NetworkX 和 Python 3思路可以迁移到任意复杂路口。1. 为什么红绿灯问题天生适合用图论表达1.1 一切先从“冲突”说起交叉口的交通流可以简化成三类关系交叉冲突、合流冲突和分流冲突。交叉冲突是两股车流在路口内部轨迹相交比如左转车和对向直行车让它们同时放行就会撞在一起合流冲突是两股车流汇入同一条出口车道比如相邻两个进口的右转车汇到同一条路上同时放行会导致抢道分流冲突是同一进口道上的不同流向要变道分叉比如直行和左转共用一条车道同时让它们走就会互相挡路。红绿灯相位设计核心就是把这些冲突按“时间切片”拆开把不能同时放行的车流分到不同相位把能同时放行的车流尽量合并到一个相位。这个任务听起来像人工观察实际上是一个典型的图着色问题。打个比方就像会议室里一群人分时段发言。有些人不能同时发言比如观点绝对对立有些可以比如互相补充。你不能靠猜得先列一张“谁和谁不能同台”的清单然后安排最少场次让所有人都讲完。红绿灯相位设计就是这个“安排场次”的过程。1.2 把冲突关系变成一张图图论建模的思路很简单把每一股可以独立控制的车流当成一个节点如果两股车流不能同时放行就在它们之间连一条边。这样得到的图叫冲突图。举个直观例子西进口左转车流和北进口直行车流必然在路口中心交叉那么节点“西左转”和节点“北直行”之间就有一条边。如果一个路口有10股需要信号控制的车流建出来的图一般有十几个节点、几十条边人工排相位很容易看漏图建模不会。有了冲突图之后“设计信号相位”就变成了“给图中每个节点涂颜色”规则是相邻节点不能同色。每一种颜色代表一组可以同时放行的车流也就是一个相位。相邻节点不同色就保证了同一个相位内的所有车流两两不冲突这是方案安全性的严格保证。图论里的“着色数”对应最小可能的相位数量。实际项目中我们不一定要追求绝对最小值但着色过程本身就把所有相位组合系统地枚举出来了这是人工经验很难做到的。1.3 经验配时与图论建模的差别很多人一开始会问我平时看车流量拍脑袋排相位也能跑为什么要费这么大劲建图我的体会是经验法在简单十字路口够用一旦车道数多、转向组合复杂问题就开始暴露。比如五岔路口左转车流和对向直行、相邻方向右转、斜向进口的直行都可能冲突漏掉一组冲突就等于埋下一个安全隐患。此外经验法排出的相位方案往往偏保守车流被拆得太散绿灯利用率低排队长度自然压不下来。图论建模的好处有三点第一冲突关系是结构化清单一条一条核对不重不漏第二着色算法能自动给出多种候选相位组合你可以从最小色数、流量均衡、行人过街等多个维度挑方案第三方案可复现换一个人拿到同一套数据能得到同样的结论特别适合做方案评审和后续仿真验证。我用一个表格总结一下差别对比项经验配时图论建模冲突排查方式靠观察容易遗漏按冲突规则逐对生成系统全面相位组合来源按套路套用算法枚举自动生成方案安全性依赖个人经验着色规则保证同相位无冲突调整成本改一处要重新全局拍改节点和边重新跑一次即可可解释性说不出为什么这么排可以展示冲突图和颜色分组2. 建模实操把一个五岔路口搬进程序2.1 第一步盘点车道与流向先把路口几何弄清楚。我拿一个简化的五岔路口举例五个进口分别记为 A、B、C、D、E其中 E 是一个斜向进口道。每个进口的车道渠化情况如下表进口车道配置可通行的信号控制流向A一条左转道、两条直行道、一条右转道A左转、A直行B一条左转道、两条直行道、一条右转道B左转、B直行C一条左转道、一条直行道、一条右转道C左转、C直行D一条左转道、一条直行道、一条右转道D左转、D直行E一条左转道、一条直行道斜向汇入、一条右转道E左转右转车流在这个模型里默认不进入信号控制流程。原因很简单只要路口右转不受专用箭头控制、行人过街也有独立相位保障右转车基本可以长绿放行。如果把右转拉进冲突图会让节点数爆炸最后着色结果多出好几个冗余相位。这个取舍后面单独说。Python 里我直接用字典表示每个流向movements { A_L: {road: A, dir: left}, A_S: {road: A, dir: straight}, B_L: {road: B, dir: left}, B_S: {road: B, dir: straight}, C_L: {road: C, dir: left}, C_S: {road: C, dir: straight}, D_L: {road: D, dir: left}, D_S: {road: D, dir: straight}, E_L: {road: E, dir: left}, }节点编号采用“进口方向”的命名方式后面构建冲突边、读取着色结果、映射到信号机相位表时都不容易乱。2.2 第二步梳理冲突矩阵冲突矩阵是图论的“边集合”。我维护一个生成冲突对的函数规则按以下几条来轨迹相交规则两股车流在路口内部物理轨迹有交叉点视为冲突例如对向直行与左转、相邻进口的左转与直行。同进口合流规则同一进口内除独立右转外所有同时需要放行的流向必须互斥因为车道资源有限。驶出路段相同规则两股车流汇入同一条出口路段且汇入点距离路口过近视为冲突。特殊渠化规则如果某进口是“直左合用车道”那么该进口的直行和左转必须合并成一个节点不能当两个独立车流处理。实际项目里第1条是绝对刚性的第3条则需要结合出口车道长度来判定距离足够长的合流可以适当放宽。但为了建模简单第一次跑通时先把合流也当成冲突后面再优化。下面是生成冲突对的代码片段def build_conflict_edges(movements): edges [] roads {m[road] for m in movements.values()} for i, (name_i, mi) in enumerate(movements.items()): for name_j, mj in movements.items(): if name_i name_j: continue # 同进口不同流向之间默认冲突 if mi[road] mj[road]: edges.append((name_i, name_j)) continue # 对向进口左转与直行冲突直行与直行不冲突 if is_opposite(mi[road], mj[road]): if mi[dir] left and mj[dir] straight: edges.append((name_i, name_j)) elif mi[dir] straight and mj[dir] left: edges.append((name_i, name_j)) continue # 相邻或斜交进口默认左转与其余流向冲突 if mi[dir] left or mj[dir] left: edges.append((name_i, name_j)) return list(set(edges))这套规则不是万能公式但它把常见五岔路口的强冲突都覆盖了。对于更复杂的不规则路口建议直接在地理信息底图上画每一股车流的轨迹线用线段求交来判断是否冲突准确性比经验规则高得多。2.3 第三步用 NetworkX 构建冲突图冲突对生成之后建图就是几行代码的事import networkx as nx G nx.Graph() G.add_nodes_from(movements.keys()) G.add_edges_from(build_conflict_edges(movements)) print(f节点数: {G.number_of_nodes()}) print(f边数: {G.number_of_edges()})建完图之后一定要做一次可视化检查。我习惯把节点按进口分组摆成环形把冲突边画出来对着路口几何看一眼。如果发现某两股明明会交叉的车流之间没有边或者某两股完全不相干的车流之间多了边赶紧回去查冲突规则别急着往下跑。这一步是整个流程里最容易出错的地方也是最值得花时间的环节。图上多一条边最多让方案多一个相位、多浪费一点绿灯少一条边就是实打实的安全事故隐患。3. Python 实现图着色生成相位3.1 选择着色算法为什么我用 DSATUR冲突图的顶点着色可以直接用 NetworkX 的greedy_color函数几行就能跑出结果coloring nx.coloring.greedy_color(G, strategylargest_first)但实际做项目时我更倾向于自己实现 DSATUR 算法。原因很实际greedy_color用固定的节点顺序很多时候会多出不必要的颜色数而 DSATUR 每次都选“饱和度最高”的未着色节点也就是已着色邻居颜色种类最多的节点这样更容易复用已有颜色减少相位数量。相位数量直接影响路口效率。同一个路口4个相位和6个相位每个周期多出两次切换损失一个信号周期120秒的话一天下来就多损失几百次绿灯时间。算法省下的一个颜色可能直接让排队长度下降。DSATUR 的 Python 实现不复杂def dsatur_coloring(G): coloring {} remaining set(G.nodes()) while remaining: # 对每个未着色节点计算饱和度已着色邻居的颜色种类数 def saturation(node): return len({coloring[nb] for nb in G.neighbors(node) if nb in coloring}) # 选择饱和度最高的节点平局时选度数高的 node max(remaining, keylambda n: (saturation(n), G.degree(n))) used_colors {coloring[nb] for nb in G.neighbors(node) if nb in coloring} color 0 while color in used_colors: color 1 coloring[node] color remaining.remove(node) return coloring这个实现虽然简单但工程上足够稳定。节点数量一般不超过20个运行时间可忽略不计。如果路口特别大、车道特别多也可以替换成更复杂的精确算法但日常项目里没什么必要。3.2 从颜色到信号相位着色完成后同一种颜色的节点就构成一个相位phase_groups {} for movement, color in coloring.items(): phase_groups.setdefault(color, []).append(movement)输出看一眼for color, movements_in_phase in sorted(phase_groups.items()): print(f相位 {color}: {sorted(movements_in_phase)})在这个例子中输出大致是相位 0: [A_S, C_S] 相位 1: [A_L, C_L] 相位 2: [B_S, D_S] 相位 3: [B_L, D_L, E_L]看到这个结果不要急着拿去用。我每次都会做两道安全检查第一遍历每个相位手动确认组内没有冲突边。代码可以用is_independent all(not G.has_edge(u, v) for u, v in combinations(phase, 2))来校验但眼睛过一遍仍然必要。第二确认每个流向至少出现一次。如果某个左转方向在整个周期里没有分配到任何一个相位说明冲突边漏了或者数据录入错了必须回头查。3.3 绿灯时长计算韦伯斯特配时公式的简化落地相位结构定了还差每个相位放行多久。这一块我直接采用经典的韦伯斯特配时法。它给最佳周期时长的经验公式是C0 (1.5L 5) / (1 - Y)其中 L 是每个周期的总损失时间包括启动损失和全红时间Y 是各相位关键流量比之和。流量比 y q / sq 是实测流量s 是饱和流量单位都是 pcu/h标准小客车当量。代码实现# 实测流量pcu/h与饱和流量pcu/h flow_data { A_S: 450, A_L: 110, B_S: 500, B_L: 105, C_S: 420, C_L: 100, D_S: 400, D_L: 95, E_L: 100, } saturation_data { A_S: 1800, A_L: 1600, B_S: 1800, B_L: 1600, C_S: 1800, C_L: 1600, D_S: 1800, D_L: 1600, E_L: 1600, } # 每个相位选取流量比最大的流向作为关键流向 critical_y [] for movements_in_phase in phase_groups.values(): y_values [flow_data[m] / saturation_data[m] for m in movements_in_phase] critical_y.append(max(y_values)) Y sum(critical_y) L 3.0 * len(phase_groups) 4 * len(phase_groups) # 启动损失3秒 全红4秒 C0 (1.5 * L 5) / (1 - Y) print(f关键流量比总和 Y: {Y:.3f}) print(f总损失时间 L: {L:.1f} s) print(f最佳周期 C0: {C0:.1f} s)这里 L 我按“每相位启动损失3秒 每相位全红4秒”估算实际项目应根据进口道宽度、车辆加速度重新算。全红时间不是随意拍的它在清空交叉口内的滞留车辆长短和路宽、车速直接相关下面还会细说。分配绿灯时长时用每个相位关键流量比占总Y的比例去分有效绿灯时间effective_green C0 - L green_seconds [] for y_value in critical_y: green_seconds.append(effective_green * y_value / Y)算出来之后必须套两个约束最短绿灯不得小于15秒最长绿灯通常不超过60秒。前者是为了行人过街和驾驶心理后者是为了防止某个方向绿灯过长导致其他方向等待时间失控。低于最小值的相位直接抬到15秒缺失的时间从总周期里补回来得到最终配时。4. 一个完整的配时案例与运行结果4.1 模拟数据准备我用一个中等流量的早高峰场景来跑完整流程。路口是那个五岔演示路口E进口为斜向道路右转全部不参与信号控制。各流向流量数据在上面的代码块里已经给出汇总起来是这样流向流量pcu/h饱和流率pcu/h流量比 yA_S45018000.250A_L11016000.069B_S50018000.278B_L10516000.066C_S42018000.233C_L10016000.063D_S40018000.222D_L9516000.059E_L10016000.063所有数据都是虚构构造的地方不指真实路口数值大小符合多数城市交叉口高峰期的量级。4.2 代码运行与结果解读建图、着色、配时三步跑下来结果如下。冲突图节点数9。边数约30条。DSATUR 着色得到4种颜色对应4个机动车相位相位流向组合关键流量比 y相位0A_S、C_S东西向直行0.250相位1A_L、C_L东西向左转0.069相位2B_S、D_S南北向直行0.278相位3B_L、D_L、E_L南北向与E进口左转0.066总关键流量比 Y 0.663损失时间 L 28秒。韦伯斯特公式给出的最佳周期约128秒取整后按130秒设计。各相位绿灯时长分配如下相位初始计算绿灯s约束调整后绿灯s相位03838相位11016受最小绿灯约束相位24242相位31016受最小绿灯约束加上每相位4秒全红清空时间一个完整周期的结构是东西直行38秒绿4秒全红东西左转16秒绿4秒全红南北直行42秒绿4秒全红南北左转16秒绿4秒全红合计130秒。这个方案里相位3把 E进口的左转和南北方向左转合并在同一相位前提是 E进口左转轨迹与 B、D 左转在路口中心区域不形成交叉点。我这里为了演示做了合理简化实际项目中必须用轨迹线求交验证。4.3 对照检查相位是否安全、高效方案出来不能直接交付我习惯做一组对照检查。先看安全性。逐相位看A_S 和 C_S 是对向直行轨迹平行无交叉A_L 和 C_L 是双向左转各自在中心岛外侧转弯互不干扰B_S 和 D_S 同理B_L、D_L、E_L 需要再确认斜向进口的轨迹。只要冲突图建得准确这个检查是可以被算法保证的。再看效率。总关键流量比 Y 0.663周期130秒。这个周期值在五岔路口属于正常偏大范围原因是有五个流向挤在四个相位里E方向的小股左转流量也占了一个相位中的一个时段。如果一个方向流量特别小可以考虑把它并入相邻相位前提是安全。还要看行人过街。每个机动车相位对应的行人相位绿灯时间必须超过行人过街最短时间。如果行人过街距离是25米按1.2米/秒的步行速度算最短绿灯大约21秒那么相位0和相位2的38秒和42秒没有问题但相位1和相位3的16秒就不够。解决办法是把行人相位独立设置或者把最小绿灯约束提高。这个细节往往在项目评审时被抓出来提前算好省得返工。5. 现实约束与排错实录5.1 常见问题速查表跑完整套流程之后我汇总一下实际项目里踩过的典型坑症状可能原因解决办法着色结果有5个以上相位把不需要信号控制的右转也拉进冲突图右转默认常放仅行人过街或专用右转箭头需要时建节点某流向在所有相位里都没出现冲突规则漏了该方向或数据录入遗漏检查流量表和冲突矩阵逐方向核对绿灯时间计算结果小于10秒该相位流量比过低或关键流向选错提高至最小绿灯15秒或考虑合并相位周期过长超过180秒Y接近1路口过饱和单点配时救不了过饱和路口需渠化改造或分流图上方案合理实操却堵车建模假设和实际车道渠化不一致检查是否存在直左合用车道有则合并节点方案频繁切换、不稳定流量数据波动过大用15分钟滑动平均流量或分时段固定方案5.2 几个容易被忽略的细节右转控制的取舍值得多说两句。一个路口如果有大量右转车且行人过街量很大右转车和行人之间的冲突会变得很严重。这时候不能把右转一股脑设为常放正确的做法是给右转车单独配一个箭头相位或者把右转节点加入冲突图并分配一个“右转行人优先”相位。建模时要先看路口渠化图上有没有右转箭头灯再决定右转节点怎么处理。直左合用车道是另一个高频坑。有些路口为了省车道一个进口道同时画直行和左转箭头一辆车停在车道里等待左转时后面的直行车全被挡住。这个车道在信号控制上只能作为一个整体考虑车辆直行和左转在物理上不可分离强行把它们建成两个独立节点会让着色结果过于乐观相位方案看起来漂亮实际上路就堵。全红时间的计算也不能照搬经验值。全红是为了把已经进入路口的车辆清空时间取决于交叉口宽度、车辆位置和行驶速度。我常用的简化公式是全红秒数 ≈ (交叉口宽度 车辆长度) / 车辆通过速度。一个40米宽的路口按10米/秒速度算全红大概需要5秒。全红时间短了前一个相位的尾巴车还没过去后一个相位的头车就进来了这是严重事故隐患。5.3 从单路口延伸到干线协调单路口方案跑通之后自然往干线协调方向扩展。图论模型在单路口层面解决的是“同一时间谁可以走”到了干线层面还要多一层约束相邻路口的信号周期必须一致才能设计绿波带偏移。干线协调本质上是在多个路口相位方案之上添加相位差约束的优化问题。每个路口作为一个子图相邻路口之间通过路段行程时间建立关联。这种问题用线性规划或者启发式搜索都能解图论模型依然可以复用——每个路口内部还是冲突图着色只是全局优化时要同时考虑路口间相位差。这个方向我建议有项目需要时再深入日常做单点优化时先把本路口的相位和配时做扎实。6. 写在最后我的一点个人体会这个项目做完我最大的感触是图论不是给你一个“自动寻优”的魔法它真正解决的是“把所有约束系统化”的问题。以前人工排相位靠观察、靠经验、靠开会讨论漏掉一组冲突有时候根本发现不了。现在把冲突关系画成图所有约束清清楚楚相位的产生也有了可复现的依据。我自己踩过最大的坑就是最初把所有右转车流都放进冲突图里。结果一个普通十字路口硬生生着色出来6个相位周期冲上160秒排队比原来还长。后来把右转从图中拿掉方案回到4个相位通行效率立刻改善。这提醒我模型一定要忠于物理现实路口渠化图是什么样的图就怎么建别让模型自己发明约束。最后再分享一个小技巧建完冲突图后无论如何都做一次轨迹线交叉校验。我现在的做法是把每个流向在路口内部的转弯轨迹用圆弧近似表示然后做线段求交结果跟手工冲突矩阵逐一比对。这套校验脚本虽然只有几十行但它救过我两次漏掉斜向进口冲突的失误。做交通信号优化方案要能被人质疑、能复现、能验证有了图论模型打底这三件事都变得可控。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑