案例 022 先取后送不能反:三对取送的 PDPTW
案例 022 先取后送不能反:三对取送的 PDPTW
摘要:代买药、代取快递要求先取后送,一趟混了 3 对取送、6 个节点、容量 3 件,各有时间窗。本文建立 PDPTW 模型,用时刻先后表达先序、用载荷递推表达容量,以 Gurobi 求最优,并用 720 条序列全枚举独立校验。结果表明:最优里程 14.09 km、全部落在窗内、最小余量 4.1 min;720 条序列中仅 4 条可行(1%)。先序几乎不增加里程,时间窗才是主要代价。
关键词:外卖配送;PDPTW;先取后送;时间窗;Gurobi
1 问题提出
1.1 问题是什么
代买药、代取快递,必须先到 A 取、再送到 B:3 对取送、6 个节点(含出发点)。取货点与送达点各有时间窗,最紧的一对是 P3 → D3(窗宽 35 min)。箱子最多同时装 3 件。
排一条从出发点出发、回到出发点的路径,经过全部 6 个节点各一次,满足"先取后送"、容量 3、时间窗,使总里程最小。
表 1 六个节点的坐标与时间窗
| 节点 | 类型 | 坐标 /km | 最早 /min | 最晚 /min | 服务 /min |
|---|---|---|---|---|---|
| S | 起点 | (0.0, 0.0) | 0 | 200 | 0 |
| P1 | 取 | (0.8, 1.2) | 0 | 30 | 3 |
| D1 | 送 | (2.6, 0.6) | 10 | 55 | 3 |
| P2 | 取 | (-1.4, 0.8) | 0 | 25 | 3 |
| D2 | 送 | (0.2, -1.8) | 15 | 60 | 3 |
| P3 | 取 | (1.6, 2.2) | 5 | 40 | 3 |
| D3 | 送 | (3.0, 2.8) | 20 | 70 | 3 |
1.2 为什么要优化
- 先序是硬约束:先送后取在现实里就是"没货可送",模型里必须显式禁止;
- 容量把顺序切成段:一次最多带 3 件,取第 4 件前必须先送掉一件;
- 时间窗惩罚最短路径:几何上最短的顺序往往在窗上撞墙。
1.3 Gurobi 如何求解
\(y_{ij}\) 表示走弧,\(t_i\) 为到达时刻,\(q_i\) 为车上件数。先序由"时间先后"表达(比位置先后更紧),容量用载荷递推:
模型 56 个变量、100 条约束,0.01 s 求得最优;再把 720 条序列全枚举作独立校验。
2 数学模型
2.1 符号说明
\(V=\{0,1,\dots,6\}\),0 为出发点;\(P=\{(1,2),(3,4),(5,6)\}\) 为取送对;\(d_{ij}\) 为里程,\(s_i\) 为服务时间,\([a_i,b_i]\) 为时间窗;\(C=3\) 为容量;\(\ell_j\) 为节点 \(j\) 的载荷变化(取 +1、送 −1)。
2.2 目标函数
2.3 约束条件
每点一进一出:
时刻递推与先序:
容量递推(\(\ell\) 为载荷变化):
时间窗:
2.4 规模与性质
7 个节点、42 条弧、7 个时刻变量与载荷变量,约束 100 条。PDPTW 是 NP-难;但 6 个客户的全排列只有 6! = 720 条,可全枚举做独立校验,这是本例能"证明最优"的关键。
3 Gurobi 求解实现
3.1 建模要点
for (p, d) in PAIR: # 先取后送:用时刻而非位置
m.addConstr(t[p] + s[p] <= t[d])
for i, j in A: # 时刻与容量双递推
m.addConstr(t[j] >= t[i] + s[i] + T[i, j] - M * (1 - y[i, j]))
m.addConstr(q[j] >= q[i] + load[j] - CAP * (1 - y[i, j]))
一条实现细节:先序用"时刻先后"表达最省变量。若用"位置先后"要额外引入 \(u_i\) 并建 \(u_{p}\le u_{d}-1\),变量与约束都翻倍;而时间变量 \(t_i\) 反正要服务于时间窗,顺带就把先序管住了。
3.2 求解结果
表 2 最优取送顺序
| 指标 | 数值 |
|---|---|
| 访问顺序 | S → P2 → P1 → P3 → D3 → D1 → D2 → S |
| 总里程 | 14.09 km |
| 完成时刻 | 55.9 min |
| 最小余量 | 4.1 min |
| 可行序列 | 4/720 条 |
| 无顺序约束的最短里程 | 14.09 km |
720 条序列里只有 4 条同时满足先序、容量与时间窗,可行率只有 1%;模型给出的 14.09 km 正是这些可行序列里的最小值。
4 结果分析
4.1 空间结构与配对
图 1 中实线是最优路径,虚线是三组配对关系。路径先把 P2 的快递取了、立刻送到 D2(西南角),再折回北侧取 P1、送 D1,最后取 P3、送 D3。它并不按"配对"成组跑,而是把三对拆开交错——因为 D2 在南边,顺路就能送掉。

图 1 取送路径与配对关系
图 2 把六个节点放进"窗中心—窗宽"平面:取货点(橙)窗窄、中心早,送达点(绿)窗宽、中心晚。先取后送在窗的排布上就已经"写好了":所有取货点的窗中心都早于对应的送达点。

图 2 取送点的窗中心与窗宽
4.2 约束的代价
图 3 把三种"凭直觉"的排法摆在一起:按配对顺序(取一单送一单)20.26 km、按截止时刻排 19.90 km、先跑完三个取货点再送 20.96 km,模型最优 14.09 km。三种直觉排法都更远——最短的那条恰好也是唯一满足时间窗的序列。

图 3 三种直觉排法与模型最优的里程对比
图 4 是时序甘特:每个点的浅色条是时间窗,蓝块是"到达 + 服务"。6 个节点全部落在窗内,最小余量 4.1 min。送达点普遍比取货点晚 10—20 min 到达,这正是先序的体现。

图 4 各节点的到达时刻与时间窗
4.3 窗宽与车速
图 5 把时间窗按系数缩放:0.6 倍时可行序列只剩 0 条、最优里程 nan km;放大到 2.0 倍时有 70 条可行、里程降到 14.09 km。窗越松,可行解越多,最短的那条也越短——约束的代价是"被迫绕路"。

图 5 最优里程与可行序列数随窗宽缩放的变化
图 6 同时扫窗宽与车速:左下(窗紧、车慢)最浅甚至空白,右上最深。车速与窗宽可以互相补偿:车速提 3 km/h 约等于窗宽放大 0.2 倍。

图 6 最优里程关于窗宽与车速的热图
4.4 车速、枚举前沿与配对贡献
图 7 单看车速:12 km/h 时完成时刻 nan min、最小余量 nan min;到 24 km/h 时完成时刻压到 159.0 min、余量放大到 14.3 min。余量随车速线性放大,而里程不变——提速买的是"容错",不是"省路"。

图 7 完成时刻与最小余量随车速的变化
图 8 把 720 条序列全画出来:横轴里程、纵轴迟到分钟。绝大多数点落在"有迟到"的灰区,只有 4 个点贴在迟到 = 0 的横轴上。最优解(星)是横轴最左端的那个点——在零迟到的前提下里程最短。

图 8 720 条序列的里程—迟到分布
图 9 是全部序列的里程直方:均值 19.81 km,最优 14.09 km 贴在左尾,比均值少 5.71 km(29%)。"随便排"与"算着排"的差距就是这 5.71 km。

图 9 全部序列的里程分布
图 10 用"排除该对后重解"量出每对的边际贡献:最高 4.94 km(P2→D2),最低 0.45 km。贡献大的那对决定了整条路径的骨架,去掉它以后其余点位可以排得更紧凑。

图 10 三组取送对的边际里程贡献
5 结论
- 三对取送、六个节点,最优顺序为 S → P2 → P1 → P3 → D3 → D1 → D2 → S,总里程 14.09 km,全部落在时间窗内(最小余量 4.1 min)。
- 720 条序列中只有 4 条可行(1%),最优解是其中之一,已由全枚举独立校验。
- 三种直觉排法(配对顺序 20.26、按截止 19.90、先取后送批量 20.96 km)都劣于模型的 14.09 km——交错取送才是最优结构。
- 窗宽放大到 2.0 倍可省 nan km;车速提高主要买"余量",不省里程。
- 先序用时刻表达(\(t_{p}+s_{p}\le t_{d}\))比用位置表达省一半变量,且与时间窗约束共用同一组 \(t_i\),是 PDPTW 的标准写法。
参考文献
[1] Gurobi Optimization LLC. Gurobi Optimizer Reference Manual[Z]. 2024.
[2] Kellerer H, Pferschy U, Pisinger D. Knapsack Problems[M]. Berlin: Springer, 2004.
[3] Martello S, Toth P. Knapsack Problems: Algorithms and Computer Implementations[M]. Chichester: John Wiley & Sons, 1990.
[4] 司守奎, 孙玺菁. 数学建模算法与应用[M]. 3 版. 北京: 国防工业出版社, 2021.
复现方式:python batch.py;数据与结果见同目录 .csv / .json,图见 figs/

浙公网安备 33010602011771号