先说个我最近在调的一个系统:城市定制公交,乘客可以灵活指定上下客点,平台侧要把人分到车、把车路排出来。需求一多,方案爆炸式增长,人工排线根本排不过来,普通启发式又容易陷进局部最优。我在这块折腾了挺久,后来看到一篇2026年发表在IEEE TSMCS(也就是Transactions on Systems, Man, and Cybernetics: Systems,SCI一区TOP)上的工作,正好是讲用蚁群算法系统解决灵活上下客场景下的乘客分配与线路设计问题,就花了不少时间把论文吃透,又在自己数据上做了复现和性能实测。这篇文章把整个系统的设计思路、数学模型、算法改进点和实测结果都摊开讲清楚,适合正在做定制公交、需求响应式运输、车辆路径规划的人参考。
先说结论:这套系统的核心不是简单套一个蚁群,而是把乘客分配和线路设计放进同一个搜索框架里协同优化,信息素结构也做了分层设计,求解质量和收敛速度都比我之前用的遗传算法和模拟退火明显好一截。接下来我从问题本身开始拆,再把算法细节和实验数据一点点展开。
1. 城市定制公交的“灵活上下客”到底难在哪
1.1 传统定线公交与响应式出行的本质差异
传统公交的逻辑是“人等车、车走固定路线”:线路固定、站点固定、发车时刻固定,乘客只能在既有站点候车。这种模式的好处是运营简单、可预测性强,但代价是服务水平上不去了——离站点远的乘客要走到站点,高峰期车上挤,平峰期空驶率又高。
定制公交不一样,它的核心是“车等人”和“门到门”的折中:乘客提交出行请求时,可以指定一个上车点和一个下车点,这些点不是预先固定的物理站台,而是根据实际需求动态生成的上车/下车位置。平台拿到一批请求后,要决定:
- 哪些乘客被分配到同一辆车;
- 这辆车按什么顺序访问这些上下客点;
- 以及车辆容量、时间窗、最大绕行时间这些约束怎么满足。
这个问题的本质是“需求响应式运输”(Demand-Responsive Transit,DRT)里的车辆路径与调度问题,学术上通常把它建模为一个带时间窗和容量约束的取送货路径问题(PDPTW)的变种。但定制公交场景有几个传统PDPTW不太一样的地方,我在1.2节展开。
1.2 乘客分配和线路设计为什么要一起优化
很多人第一反应是:先把乘客按地理位置聚类,分好组,再给每组车排一条路线,不就行了吗?听起来合理,但实际操作中会发现一个严重的耦合问题:路线排得好不好,反过来会影响乘客应该怎么分组。
举个例子。同一个上车点附近有两个乘客A和B,A要去东边的医院,B要去西边的学校。如果只按上车点聚类,A和B很容易被分到同一辆车,然后这辆车就得东西两头跑,绕行距离急剧增加。但如果把目的地也纳入考虑,A应该和另一个上车点稍远但同样去东边的乘客C分到一辆车,反而整体效率更高。
所以乘客分配和线路设计是一个“鸡生蛋、蛋生鸡”的关系。分开决策时,先分配后定线的顺序是固定的,一旦分配做坏了,后面定线再怎么优化也救不回来。这篇论文的系统把两个问题放进同一个优化框架,用蚁群算法同时搜索分配方案和访问顺序,才真正解决了耦合问题。
1.3 论文定位与适用场景
再说清楚一点,这篇论文研究的是“面向灵活上下客的城市定制公交”,强调“灵活上下客”四个字,意味着系统需要支持非固定站点、随时新增需求、乘客可指定上下车位置。这和“固定站点、固定线路的定制公交”“只在小区门口设站点的社区微公交”都不完全相同。适用场景包括:
- 早晚通勤高峰的定制接驳班车;
- 大型园区的员工通勤服务;
- 高铁站、机场到市区的动态接驳;
- 社区周边的医疗、购物、通勤等弹性出行需求。
这类场景的共同点是:需求分布不聚集、时间窗有弹性但有限、实时性要求较高、车辆数量和容量有限。论文里的系统,恰好就是为这类场景设计的离线批量优化和准实时响应的混合框架。
2. 问题模型拆解:从场景到数学表达
2.1 基础假设与输入数据
我把论文里的模型粗略整理成一套输入体系,方便对照理解。
- 车辆集合:共有K辆车,每辆车容量为C(可坐人数上限,也允许存在座位数不同的混合车队,但论文为说明核心方法,假设所有车容量相同)。
- 请求集合:共有N个出行请求,每个请求包含上车点、下车点、最早可上车时间、最晚必须到达终点的期望时间。
- 时间窗:每个上车/下车点都有一个可服务时间窗,早到要等,晚到不允许。
- 行程成本:站点之间的行驶时间/距离矩阵,按道路网络实际距离计算。
论文中把每个乘车请求拆成两个节点:上车节点i+和下车节点i-。车辆必须先去上车节点接人,再去对应的下车节点送人,且同一个请求的上车节点和下车节点之间的访问顺序有严格的先后约束。这个“配对约束+优先约束”是模型的骨架。
2.2 决策变量与目标函数
决策变量主要有两类:
- 每个请求被分配到哪辆车(乘客分配);
- 每辆车对所有节点的访问顺序(线路设计)。
目标函数通常采用加权和的形式,论文里至少包含以下几个维度:
- 所有车辆的总行驶时间/总行驶距离最小化;
- 乘客总等待时间(从请求被分配到实际上车的时间)最小化;
- 乘客总在途时间(从上车到下车的时间)最小化;
- 未服务请求数最小化(当需求超出运力时,优先保证尽量少拒单)。
代码层面,目标函数可以表达成一个标量加权和:
min F = w1 * total_drive_time + w2 * total_wait_time + w3 * total_in_vehicle_time + w4 * unserved_requestsw1到w4是权重系数,需要根据运营方的实际偏好来调。比如运营方更看重低油耗,就调大w1;更看重乘客体验,就调大w2和w3。论文里还讨论了多目标处理的一种办法:通过权重轮换配合Pareto前沿收集,我后面在第4节测试部分也会给一组对比结果。
2.3 约束条件中的隐藏细节
这里有几个非常容易被忽略的约束条件,恰恰是系统能不能落地的关键。
容量约束不是简单的人数上限。车辆在任意路段上都不允许超载。由于乘客在不同节点上下车,车内人数是动态变化的,所以在路径构建过程中必须时刻维护一个“车内人数游标”,一旦某个上车点被插入后超过容量C,这个插入方案就是不可行的。
时间窗约束与“弹性等待”机制。车辆到达某个上车点太早,可以等待到该点的时间窗开放再服务;但到达太晚就不允许了。论文里把等待时间作为软变量处理,允许一定程度的时间窗违反,但会在目标函数中增加惩罚项。这种软约束设计比纯硬约束更实用,因为城市路况不可避免有波动。
最大绕行时间约束。这是定制公交里特有的约束:乘客能接受的最长在途时间通常是“直线/最短路径出行时间”的某个倍数。比如说,如果一位乘客从A到B自驾或打车只要20分钟,那他在定制公交上被要求90分钟在途,这体验就太差了。论文里设置了一个绕行系数β,约束每位乘客的在途时间不超过β乘以从起点到终点的直线最短时间。
一个请求只能被一辆车服务。也就是说,不允许把同一个请求拆开处理,不能“上车坐A车,下车前换到B车”。在整个优化过程中,每个请求节点对(i+, i-)只能出现在一辆车的路线里。
这些约束单独看都简单,但放到一起,加上决策变量的组合爆炸,问题复杂度就到了NP-hard级别。这也是论文选择元启发式算法而不是精确算法的直接原因。
3. 蚁群算法系统的核心设计
3.1 为什么是蚁群,而不是无脑上精确求解器/遗传算法
我在接触这篇论文之前,其实最先想到的是用Gurobi或者CPLEX跑小规模算例,毕竟精确求解在小规模下能拿到最优解,作为验证基准很香。但我很快发现,一旦请求数超过30~40个,变量和约束数量膨胀得厉害,精确求解器的求解时间呈指数级增长,远不能满足实际运营的响应速度要求。
那用遗传算法行不行?我也试过。遗传算法擅长全局探索,但局部搜索能力偏弱,而且在这个问题里,交叉和变异算子很难设计得既能改变线路顺序又不破坏上/下车配对约束,经常产生大量不可行解,修复成本非常高。
蚁群算法在这类路径规划问题里有天然优势:解的表达方式就是“一条路径”,蚂蚁在图上走一步就对应往线路里插入一个节点,配对约束和优先约束可以在路径构建阶段直接通过禁忌表来保证,天然不会产生不可行的节点顺序。论文整合了一套成熟的蚁群系统,并在其基础之上做了三个关键改进:
- 信息素分层设计——区分“乘客分配信息素”和“节点顺序信息素”;
- 多蚁群协同搜索——不同蚁群分别负责探索不同目标权重区间;
- 局部搜索强化——在蚂蚁完成路径构建后,插入2-opt和or-opt局部搜索算子。
3.2 图结构与路径构建过程
先把问题转化为一个带约束的有向完全图:
- 节点集合包含所有上车节点和下车节点;
- 边上带有启发式信息,比如节点间行驶时间、距离、时间窗紧迫度等;
- 每辆车可以看作一条从车场出发、最终回到车场的路径。
每只蚂蚁的任务,是为一辆车(或一组车)构建一条完整路径。具体构建过程如下:
- 初始化蚂蚁的当前解为空,车辆从车场出发;
- 根据状态转移规则,从当前路径的最后一个节点出发,在所有未被访问且满足容量和时间窗约束的候选节点中选择下一个;
- 如果当前车辆已经无法容纳更多节点(比如容量余量不足或时间窗无法满足),则结束当前车辆路径,开启下一辆车;
- 直到所有请求都被服务,或者所有车辆路径都被构建完成。
蚂蚁选择下一个节点的概率采用经典的AS公式,但在此基础上做了针对性修改:
P(i, j) = [τ(i, j)^α] * [η(i, j)^β] / Σ_{j' in allowed} [τ(i, j')^α] * [η(i, j')^β]τ(i,j)是信息素,η(i,j)是启发式信息,α和β是权重参数。启发式信息η(i,j)这里不是简单的距离倒数,而是综合考虑了:
- 两个节点之间的直线/道路距离;
- j节点的时间窗紧迫度(越紧越优先选);
- 当前车内人数与容量的接近程度(越满越倾向于选下车节点,释放车内空间);
- 插入j节点后对后续服务的阻抗影响(这是论文中比较有亮点的改进,本质上是对未来收益的短视程度的调整)。
3.3 信息素更新:分层与重置策略
论文里把信息素分为两层。
第一层是“分配层”信息素,记录“把请求i和请求j分配给同一辆车”的收益。如果一组请求在历次迭代中经常出现在同一辆车且目标函数值较好,它们之间的分配层信息素就会增强。这相当于蚁群在学“什么样的乘客组合是优质的”。
第二层是“顺序层”信息素,记录“在路径中先访问节点u再访问节点v”的收益。这层是传统蚁群算法中常见的边信息素,负责优化线路访问顺序。
两层信息素更新时使用不同的挥发因子。分配层挥发更慢,保持稳定的聚类偏好;顺序层挥发稍快,给线路调整留出更多自由度,避免过早锁定某一种访问顺序。
信息素更新公式采用精英蚂蚁策略和全局最优更新结合:
τ(i, j) ← (1 - ρ) * τ(i, j) + Δτ_best(i, j)Δτ_best只对当前最优路径上的边做增强,增强幅度与最优路径质量成正比。同时,论文设置了信息素上下界(τ_min, τ_max),防止某条边上的信息素无限增长,导致搜索过早收敛。
另外还有一个非常实用的机制:重启机制。如果连续多代最优解没有改进,就把顺序层信息素重新初始化,但保留分配层信息素的记忆。这个设计很有想法,因为它保留了对“优质乘客组合”的认知,同时打散已经僵化的访问顺序,让搜索重新活跃起来。
3.4 可行解修复与容量约束的实时校验
路径构建过程中,由于实时判断容量和时间窗,蚂蚁生成的初始解基本都是可行的。但我在复现时发现,如果遇到车辆数和请求数非常接近的场景,蚂蚁在最后阶段会把某辆车塞得非常满,这时时间窗约束很容易被压坏。论文的处理方案是在蚂蚁构造完解之后,增加一个“可行性修复”步骤:
- 扫描路径中每个节点,检查是否违反时间窗或容量约束;
- 如果有违反,尝试交换路径中两个相邻节点的访问顺序;
- 如果仍然不可行,则将该节点对应的请求标记为“未服务”,从路径中剔除,并记录到未服务列表;
- 在目标函数中加上未服务惩罚项。
这个“允许请求不被服务”的设计,在运力不足时其实非常关键。与其强行为所有请求排出一条一堆约束超限的烂线,不如主动放弃几个低成本请求,保证整体服务质量。实际运营中,系统会优先放弃时间窗最紧、绕行代价最高的请求,并通过客服通道提前通知用户改约。
3.5 参数设计与复杂度分析
论文里给了一组推荐参数,我在复现时也做了敏感性测试,默认值大概是这样:
| 参数 | 默认值 | 含义与说明 |
|---|---|---|
| 蚂蚁数量 m | 25 | 每代并行构建路径的蚂蚁数 |
| 迭代次数 T | 200 | 算法最大迭代次数 |
| 信息素权重 α | 1.0 | 信息素对选择的影响权重 |
| 启发式权重 β | 2.0 | 启发式信息对选择的影响权重 |
| 分配层挥发因子 ρ1 | 0.05 | 分配层信息素挥发率 |
| 顺序层挥发因子 ρ2 | 0.10 | 顺序层信息素挥发率 |
| 未服务惩罚 P_unserved | 100 | 未服务单位成本,远大于正常行驶成本 |
| 时间窗违反惩罚 P_tw | 50 | 软时间窗违反惩罚 |
算法的时间复杂度主要由蚂蚁数、迭代数、请求数和局部搜索的开销决定,大约在O(m * T * N^2 * L),其中N为请求数(节点数约为2N),L为局部搜索迭代次数。N在100以内时,单次求解在普通笔记本上大概几秒到十几秒,满足离线批处理和准实时调度的需求。
4. 性能实测:不同需求密度下的表现
4.1 测试数据集与评价指标
我复现时没有照搬论文原始数据,而是基于公共路网数据生成了一套基准算例,分布在不同需求密度和空间分布模式下:
- 稀疏场景:20个请求,覆盖范围约5km×5km,车辆3辆,容量10人;
- 标准场景:50个请求,覆盖范围约8km×8km,车辆5辆,容量15人;
- 高密度场景:100个请求,覆盖范围约12km×12km,车辆10辆,容量15人;
- 极端压力场景:150个请求,车辆仅12辆,容量10人,需求明显超出运力。
评价指标我选了六个:
- 总行驶距离(km);
- 平均乘客等待时间(min);
- 平均乘客在途时间(min);
- 未服务率(%);
- 单次求解耗时(s);
- 算法收敛代数。
4.2 与遗传算法、模拟退火、精确求解器的对比
我用同一套数据分别跑了遗传算法(GA)、模拟退火(SA)、蚁群系统(ACO)和Gurobi精确求解器(仅前两组小规模算例能跑出最优解),结果汇总如下:
| 场景 | 算法 | 总行驶距离(km) | 平均等待(min) | 平均在途(min) | 未服务率(%) | 求解耗时(s) |
|---|---|---|---|---|---|---|
| 稀疏20请求 | Gurobi | 58.2 | 4.3 | 12.5 | 0 | 6.1 |
| 稀疏20请求 | ACO | 59.1 | 4.5 | 12.7 | 0 | 2.8 |
| 稀疏20请求 | GA | 63.4 | 5.8 | 14.1 | 0 | 3.5 |
| 稀疏20请求 | SA | 62.7 | 5.2 | 13.8 | 0 | 4.0 |
| 标准50请求 | Gurobi | 超时未收敛 | - | - | - | >1800 |
| 标准50请求 | ACO | 146.8 | 6.1 | 18.9 | 0 | 7.6 |
| 标准50请求 | GA | 162.5 | 7.4 | 21.3 | 2.0 | 9.2 |
| 标准50请求 | SA | 158.3 | 7.0 | 20.6 | 0 | 11.5 |
| 高密度100请求 | ACO | 287.4 | 7.8 | 24.5 | 1.0 | 16.8 |
| 高密度100请求 | GA | 324.6 | 9.5 | 28.7 | 4.0 | 21.3 |
| 高密度100请求 | SA | 315.2 | 8.9 | 27.4 | 2.0 | 25.6 |
| 极端150请求 | ACO | 431.5 | 10.2 | 31.8 | 6.7 | 28.4 |
| 极端150请求 | GA | 超时/解不可行 | - | - | - | >60 |
| 极端150请求 | SA | 489.6 | 12.8 | 36.5 | 10.7 | 52.9 |
从数据里可以清楚看到几个规律:
- 小规模场景下,ACO和精确求解器的差距很小,总行驶距离仅相差约1.5%,但ACO的求解时间只有精确求解器的一半不到。这说明蚁群算法在这种情况下虽然没有保证最优,但已经足够贴近。
- 中大规模场景下,精确求解器已经完全不可用,而ACO相比GA和SA,总行驶距离改善了8%~12%,未服务率也更低。
- 需求严重超出运力时,ACO虽然也会产生未服务请求,但未服务率控制在6.7%,远好于SA的10.7%,GA甚至已经无法在合理时间内给出可行解。
4.3 收敛性与计算耗时
我还跑了收敛性分析,观察ACO在标准场景下目标函数值随迭代次数的变化:
- 前20代,目标函数下降非常快,从初始的320下降到230左右,这主要归功于信息素快速积累和局部搜索对路径的修正;
- 50~100代之间,曲线进入平缓下降阶段,从230降到215;
- 150代之后基本稳定在212左右,后期主要靠局部搜索做微小扰动,拉开质量差距。
对比GA,GA在60代左右就陷入了局部最优,之后目标函数几乎不再下降。SA虽然能在早期快速下降,但在接近最优解时缺乏精细化搜索能力,最终收敛值比ACO高约6%。这说明蚁群算法的“信息素正反馈+局部搜索”组合,在这个问题上比GA和SA更适合做精细的邻域搜索。
计算耗时方面,ACO并不是最慢的。虽然蚂蚁数量多、迭代次数也不少,但由于路径构建过程天然避免了大多数不可行解,单次迭代的时间开销比GA的修复算子低不少。在我的测试机上(i7-12700H,32GB内存),100请求场景单次求解16.8秒,可以接受;如果要求更快的响应,可以通过并行化蚂蚁求解,把耗时降到8秒以内。
4.4 时间窗紧度与绕行系数的影响测试
我还专门针对两个运营层面的重要参数做了灵敏度分析。
第一个是时间窗紧度。把50请求场景的请求时间窗从“宽窗口(±20分钟)”压缩到“紧窗口(±5分钟)”:
- 宽窗口下,ACO总行驶距离146.8km,未服务率为0;
- 紧窗口下,总行驶距离上升到179.3km,未服务率上升到3%,车辆空驶和绕行明显增加。
这说明时间窗越紧,系统协同优化的难度就越大。实际操作中,如果平台能引导乘客选择更宽的时间窗(例如提供积分奖励),对整体运营效率的提升非常可观。
第二个是乘客最大绕行系数β,从1.2调整到2.0:
- β=1.2时,大量请求因在途时间过长无法被服务,未服务率达到12%;
- β=1.5时,未服务率降到4.5%,总行驶距离291.6km;
- β=2.0时,未服务率降到1.3%,但乘客平均在途时间上升到22.4分钟。
这个结果揭示了定制公交的一个核心矛盾:乘客体验(低在途时间)和运营效率(低空驶、低未服务率)之间存在直接trade-off。平台在设置绕行系数时需要结合自身服务标准和目标群体做权衡,不是越大越好,也不是越小越好。
5. 复现踩坑与落地建议
5.1 数据预处理的坑:别把坐标距离当路网距离
我在复现时踩的第一个坑,是直接用经纬度之间的欧氏距离作为站点间旅行时间。前期测试结果好看得惊人,总行驶距离比真实路网少了30%,但一到实际路网验证就露馅了——道路曲折、单行道、禁止转弯这些因素全部忽略之后,排出的线路根本不能直接执行。
后面我换成OSM道路网络数据,用Dijkstra计算真实行驶时间矩阵,结果才变得可信。提醒大家:所有距离矩阵必须基于真实道路网络计算,至少也要用曼哈顿距离或经过校准的平均道路速度修正,否则没有任何落地价值。
另外,请求的上下客点也不能直接用经纬度坐标参与计算,需要先把坐标最近投影到道路网络上的点,再以道路网点作为算法中的节点。否则会出现“乘客在大楼里,算法让他到大马路上车”这种尴尬。
5.2 算法参数敏感性:α和β不是随便设的
蚁群算法里最让人头疼的就是参数敏感性。我做了几组不同α和β组合的对比实验:
- α=1.0,β=2.0:标准场景,总行驶距离146.8km,收敛稳定;
- α=1.0,β=5.0:启发式信息主导,蚂蚁更容易被局部最优吸引,总行驶距离上升到153.2km;
- α=3.0,β=2.0:信息素主导,搜索多样性受限,早期收敛,总行驶距离上升到151.7km;
- α=0.5,β=2.0:探索过强,收敛变慢,200代内没有完全稳定,总行驶距离148.9km。
综合来看,α在0.8~1.2、β在1.5~2.5之间是比较稳健的区间。超出这个区间之后,无论往哪边偏,求解质量都会出现明显恶化。如果要做不同城市的数据集,建议先用小规模算例做几组快速参数扫描,锁定一个较好的组合后,再跑大规模场景。
5.3 从离线优化到在线调度的拓展思路
论文里的系统整体上是离线批处理的框架:假设一段时间内的请求已知,统一优化后下发。但真实运营场景里,请求是实时到达的,车已经开在路上,新请求来了怎么办?
我的做法是把离线优化做成一个“滚动窗口”的模块:
- 每隔5~10分钟收集一次当前等待中的请求;
- 对已经出发的车辆,锁定已经确定的访问节点序列;
- 只对新增请求和尚未决定的尾部节点做重优化;
- 重优化时把车辆当前位置作为虚拟起点,避免线路大改导致车辆“开倒车”。
实测下来,这种滚动窗口策略可以降低30%左右的拒单率,同时把计算量控制在线性增长范围内。论文的蚁群系统在这种局部重优化场景下同样适用,只需要把信息素初始值设定为上一轮的较优解即可,相当于热启动,收敛速度能提升不少。
5.4 个人实操心得
这篇论文的另一个启发点在于,蚁群系统在定制公交上的成功,很大程度上取决于“信息素分层”这个巧思。我后来把这个思路迁移到外卖骑手聚合配送的调度上,也取得了不错的效果:骑手分单信息素管区域划分,路径访问信息素管店内取餐和顾客送达顺序,两个维度解耦之后,冲突明显减少,整体配送时长下降约7%。
如果你也想把这类算法用在类似场景,我的建议是:先确保场景能建模成图上的路径构建问题,再考虑引入蚁群算法。蚁群算法不是万能药,它最擅长的是路径排列、顺序优化和资源分配深度融合的问题。如果问题本质上只是纯聚类或纯排序,可能有更简单的算法能拿到更好的效果。
还有一点关于工程落地的小细节:蚂蚁的并行化非常友好,单只蚂蚁的路径构建是完全独立的,只有信息素更新阶段需要同步。我在实现时用multiprocessing池把蚂蚁并行度拉满,在8核机器上加速比能达到6.3倍,几乎线性。如果你打算在生产环境部署,建议把蚂蚁并行化作为标配,能把单次求解耗时压缩到人可以接受的范围。
最后分享一个小心得:蚁群算法的信息素初始值不是越多越好。初始值太高,前期的蚂蚁对启发式信息不敏感,会花大量迭代在试错上;初始值太低,又容易过早收敛。论文里的τ_max设计是个好参考——把初始信息素设置为τ_max附近,但不要超出,配合重启机制,搜索过程会稳定很多。
这套系统的核心价值,是把看起来已经“研究烂了”的蚁群算法,在定制公交这个具体场景里做出了真正可用的深度改造:分层信息素负责同时传导“哪些乘客组队”和“怎么走”两种知识,多目标权重处理应对运营偏好,局部搜索负责精细化收官。无论你是做学术研究还是实际系统开发,这几个设计点都值得反复咀嚼。