在现代物流体系的庞大网络中,每一个包裹的精准送达背后,都隐藏着一道极其复杂的数学难题------车辆路径问题(Vehicle Routing Problem, VRP)。这不仅仅是将货物从A点运到B点,而是一个涉及多维约束的组合优化噩梦。如果我们将物流比作人体的血液循环,那么VRP就是规划血管网络的最优拓扑结构。其核心挑战在于:给定一组客户点,每一点都有其特定的需求qiq_iqi,如何调度一支容量为QQQ的车队,从配送中心(Depot)出发,在满足所有约束的前提下,以最低的成本完成配送并返回?
从数学角度看,这是一个典型的NP−hardNP-hardNP−hard问题。最基础的模型------带容量约束的车辆路径问题(CVRP),其目标函数旨在最小化总行驶距离,我们可以形式化为:minZ=∑k∈K∑i∈N∪{0}∑j∈N∪{0}cijxijk \min Z = \sum_{k \in K} \sum_{i \in N \cup \{0\}} \sum_{j \in N \cup \{0\}} c_{ij} x_{ijk} minZ=k∈K∑i∈N∪{0}∑j∈N∪{0}∑cijxijk 其中,cijc_{ij}cij代表从节点iii到节点jjj的成本,xijkx_{ijk}xijk是一个二元决策变量,指示车辆kkk是否选择了弧(i,j)(i, j)(i,j)。然而,现实世界远比数学模型骨感。当我们在模型中引入时间维度,VRP便进化为更具挑战性的带时间窗车辆路径问题(VRPTW)。在VRPTW中,每个客户iii不再仅仅是一个坐标点,而是一个时空实体,拥有服务时间窗ei,lie_i, l_iei,li和服务时长sis_isi。这意味着车辆必须在eie_iei之后(含等待)且在lil_ili之前到达,否则将面临违约。
这种时空耦合的约束使得问题的解空间变得异常复杂。为了处理时间递推,我们需要引入时间变量tikt_{ik}tik,并建立如下的时序约束: tjk≥tik+si+dijv−M(1−xijk) t_{jk} \geq t_{ik} + s_i + \frac{d_{ij}}{v} - M(1 - x_{ijk}) tjk≥tik+si+vdij−M(1−xijk) 这里的MMM是一个极大的常数(Big M法),用于逻辑判断。面对这样的复杂度,精确的求解器往往在几十个客户点面前就败下阵来,因此,启发式和元启发式算法成为了工业界的主力军。

为了将理论落地,我们将目光投向具体的算法实现。下面提供了一份完整的Python代码,它融合了数据生成、贪心插入启发式算法(Greedy Insertion Heuristic)以及结果可视化。这段代码不仅仅是打印几行文字,它模拟了车辆在时空中的穿梭,检查每一个潜在动作是否符合物理世界的规则。
python
import math
import random
import matplotlib.pyplot as plt
# ==========================================
# 1. 数据结构与基础定义
# ==========================================
class Customer:
def __init__(self, id, x, y, demand, ready_time, due_time, service_time):
self.id = id
self.x = x
self.y = y
self.demand = demand
self.ready_time = ready_time # $e_i$: 最早开始服务时间
self.due_time = due_time # $l_i$: 最晚开始服务时间
self.service_time = service_time # $s_i$: 服务耗时
self.angle = math.atan2(y, x) # 用于扫描算法的角度辅助
def __repr__(self):
return f"C{self.id}"
def euclidean_distance(c1, c2):
"""计算欧几里得距离 $d_{ij}$"""
return math.sqrt((c1.x - c2.x)**2 + (c1.y - c2.y)**2)
# ==========================================
# 2. 核心逻辑:时空可行性检查
# ==========================================
def check_route_feasibility(route, depot, vehicle_capacity):
"""
模拟车辆沿路径行驶,检查容量与时间窗。
返回: (total_dist, total_demand, is_feasible, schedule)
"""
if not route:
return 0, 0, True, []
current_time = 0
current_load = 0
total_dist = 0
schedule = [(depot, 0)] # 记录轨迹:(节点, 到达时间)
prev_node = depot
for cust in route:
dist = euclidean_distance(prev_node, cust)
total_dist += dist
current_load += cust.demand
# 时间轴推进
arrival = current_time + dist
# 时间窗硬约束检查
if arrival < cust.ready_time:
arrival = cust.ready_time # 早到必须等待
if arrival > cust.due_time:
return total_dist, current_load, False, [] # 迟到,不可行
schedule.append((cust, arrival))
current_time = arrival + cust.service_time
prev_node = cust
# 返回仓库
total_dist += euclidean_distance(prev_node, depot)
# 容量检查
if current_load > vehicle_capacity:
return total_dist, current_load, False, []
return total_dist, current_load, True, schedule
# ==========================================
# 3. 求解器:基于时间紧迫度的贪心算法
# ==========================================
def solve_vrptw_greedy(customers, depot, vehicle_capacity, num_vehicles):
"""
采用类似Solomon算法的启发式策略:按时间窗紧迫度排序,依次插入。
"""
# 按截止时间 $l_i$ 排序,优先服务时间窗紧的客户
unvisited = sorted(list(customers), key=lambda c: c.due_time)
routes = []
used_vehicles = 0
while unvisited and used_vehicles < num_vehicles:
current_route = []
last_node = depot
current_time = 0
current_demand = 0
# 尝试构建一条新路径
to_remove = []
for cust in unvisited:
if cust in current_route: continue
# 模拟插入
temp_route = current_route + [cust]
dist, demand, feasible, _ = check_route_feasibility(temp_route, depot, vehicle_capacity)
if feasible:
current_route.append(cust)
current_demand += cust.demand
last_node = cust
to_remove.append(cust)
# 清理已安排客户
for cust in to_remove:
unvisited.remove(cust)
if current_route:
routes.append(current_route)
used_vehicles += 1
else:
break
return routes, unvisited
# ==========================================
# 4. 主程序:运行与结果展示
# ==========================================
if __name__ == "__main__":
# 模拟Solomon标准数据集的特征
depot = Customer(0, 35, 35, 0, 0, 230, 0)
customers = [
Customer(1, 41, 49, 10, 0, 123, 10),
Customer(2, 35, 17, 7, 0, 114, 10),
Customer(3, 55, 45, 13, 0, 95, 10),
Customer(4, 55, 20, 19, 0, 157, 10),
Customer(5, 15, 30, 26, 0, 185, 10),
Customer(6, 25, 30, 3, 0, 116, 10),
Customer(7, 20, 50, 5, 0, 160, 10),
Customer(8, 10, 43, 9, 0, 130, 10),
Customer(9, 55, 60, 16, 0, 136, 10),
Customer(10, 30, 60, 22, 0, 170, 10),
]
VEHICLE_CAPACITY = 50
NUM_VEHICLES = 5
print("--- 开始求解 VRPTW ---")
print(f"客户数量: {len(customers)}, 车辆容量: {VEHICLE_CAPACITY}, 可用车辆: {NUM_VEHICLES}\n")
final_routes, remaining = solve_vrptw_greedy(customers, depot, VEHICLE_CAPACITY, NUM_VEHICLES)
total_system_cost = 0
if remaining:
print(f"警告: 仍有 {len(remaining)} 个客户未被服务 (可能由于车辆不足或时间冲突)\n")
for i, route in enumerate(final_routes):
dist, demand, feasible, schedule = check_route_feasibility(route, depot, VEHICLE_CAPACITY)
total_system_cost += dist
print(f"路径 {i+1}: 仓库 -> {' -> '.join(map(str, route))} -> 仓库")
print(f" 状态: {'可行 ✓' if feasible else '不可行 ✗'}")
print(f" 总距离: {dist:.2f}")
print(f" 总载重: {demand}/{VEHICLE_CAPACITY}")
print(f" 时间安排: ")
for node, time in schedule:
print(f" - {node} (到达:{time:.1f})")
print("-" * 40)
print(f"\n>>> 系统总行驶距离 (Total Cost $Z$): {total_system_cost:.2f}")
# 可视化
plt.figure(figsize=(10, 10))
plt.scatter(depot.x, depot.y, c='red', marker='s', s=200, label='Depot', edgecolors='black')
plt.text(depot.x + 1, depot.y + 1, "Depot", fontsize=12, fontweight='bold')
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
for i, route in enumerate(final_routes):
color = colors[i % len(colors)]
xs = [depot.x]
ys = [depot.y]
for cust in route:
xs.append(cust.x)
ys.append(cust.y)
plt.text(cust.x + 1, cust.y + 1, f"{cust.id}\n[{cust.ready_time}-{cust.due_time}]", fontsize=8)
xs.append(depot.x)
ys.append(depot.y)
plt.plot(xs, ys, c=color, marker='o', linewidth=2, label=f'Route {i+1}', markersize=8)
plt.title("VRPTW Solution Visualization (Solomon-like Data)", fontsize=16)
plt.xlabel("X Coordinate")
plt.ylabel("Y Coordinate")
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
这段代码的核心在于check_route_feasibility函数,它实际上是在模拟一辆虚拟车辆的行驶过程。它计算了从配送中心出发,经过一系列客户点,最终返回的累积时间和距离。在这个过程中,算法必须严格遵守不等式 tik≤lit_{ik} \leq l_itik≤li。如果车辆到达时间 tikt_{ik}tik 超过了 lil_ili,这条路径就会被判定为不可行。
运行上述代码,您将看到控制台输出的具体路径安排、每个客户的到达时间以及系统总距离。更重要的是,它会弹出一个可视化窗口,清晰地展示不同车辆(不同颜色)在空间中的行驶轨迹。这种视觉化的反馈帮助我们直观理解算法是如何在时间窗的逼迫下,将客户分配给不同的车辆的。
当然,贪心算法虽然快速,但往往陷入局部最优,无法保证全局最优解。在学术界和工业界,为了解决大规模的VRPTW,人们通常会采用自适应大邻域搜索(ALNS)或混合遗传算法,利用"破坏-修复"机制不断跳出局部陷阱。但无论算法多么复杂,其底层逻辑依然离不开对上述数学模型的忠实执行。
如果您希望进一步探讨如何将这段代码扩展为能够处理标准Solomon数据文件(.txt格式)的通用求解器,或者想了解如何利用强化学习(Reinforcement Learning) 来训练一个智能体自动生成路径,我可以为您继续深入展开。