Python实战:用节约里程法优化物流配送路线(附完整代码)
最近在帮一个做社区生鲜配送的朋友优化他们的送货路线,他们每天要向几十个点位送货,司机师傅们经常抱怨路线绕、耗时长。在尝试了几种方法后,我发现运筹学里的节约里程法(Clarke-Wright Savings Algorithm)特别适合解决这类问题。它不是什么高深莫测的黑科技,而是一个思路清晰、计算高效,并且能实实在在降低里程和成本的实用算法。今天,我就把自己用Python实现这个算法,并应用到实际物流调度中的完整过程、踩过的坑以及优化心得分享出来。无论你是物流行业的从业者,还是对算法落地感兴趣的开发者,这篇文章都将提供从原理到代码、从模拟到实战的全程指南。
1. 节约里程法:化繁为简的调度智慧
在物流配送中,我们常面临一个经典问题:有一个中心仓库(配送中心),需要向散布在城市各处的多个客户点送货。每辆车的装载量有限,每个客户的需求必须被满足,目标是规划出若干条行车路线,让所有车辆的总行驶距离最短。这就是车辆路径问题(Vehicle Routing Problem, VRP)。
节约里程法的巧妙之处,在于它用一个非常直观的“节约值”概念来指导路线合并。想象一下最笨的办法:为每个客户单独派一辆车,从仓库出发,送货到客户,然后空车返回仓库。这显然造成了巨大的里程浪费。节约里程法告诉我们,如果把服务于客户A和客户B的两条独立路线合并成一条“仓库 -> A -> B -> 仓库”的路线,那么就能省下一段从仓库到B再折返的路程。
这个“省下”的距离,就是算法的核心——节约值。它的计算公式非常简单:
节约值 S(i, j) = 距离(仓库, i) + 距离(仓库, j) - 距离(i, j)
这个公式的直观理解是:原本访问i和j需要从仓库出发两次,总距离是 D(仓库,i) + D(仓库,j) 的两倍(往返)。如果连接i和j,形成一条环路,那么节省的距离正好就是上述公式的结果。节约值越大,说明将这两个点放在同一条线路上节省的里程越多,优先级也就越高。
算法的主要步骤可以概括为以下四步:
- 初始化:为每个客户创建一条独立的往返路线(仓库 -> 客户 -> 仓库)。
- 计算节约值:为所有可能的客户点对(i, j),计算将它们合并到一条路线所能节约的里程。
- 排序与合并:将所有节约值从大到小排序。按此顺序,逐一尝试合并对应点对所在的路线。合并必须满足约束条件(如车辆载重、最大行驶距离)。
- 迭代与输出:重复尝试合并,直到没有符合条件的点对可以合并为止。最终得到的多条路线就是优化后的配送方案。
与一些复杂的元启发式算法相比,节约里程法计算速度快、结果易于理解,非常适合作为VRP问题的初始解或用于对求解速度要求较高的实时调度场景。当然,它属于启发式算法,不能保证找到数学上的最优解,但通常能找到非常出色的近似解,足以应对大多数实际业务需求。
2. 从零搭建Python实现:核心代码拆解
理解了原理,我们动手用Python把它实现出来。我们将采用面向对象的方式构建一个清晰的程序结构,这样更利于后续的功能扩展和维护。整个项目将包含以下几个核心部分:数据模型、节约值计算、路线合并逻辑和结果输出。
首先,我们定义一个主类 VehicleRoutingProblem,它将封装所有数据和方法。
import csv
from typing import List, Tuple
class VehicleRoutingProblem:
"""
车辆路径问题求解器(基于节约里程法)。
"""
def __init__(self, depot_index: int = 0):
self.depot_idx = depot_index # 配送中心(仓库)的索引,默认为0
self.num_customers = 0 # 客户数量
self.customer_demands = [] # 客户需求量,索引0对应仓库,需求为0
self.distance_matrix = [] # 距离矩阵,distance_matrix[i][j] 表示点i到点j的距离
self.vehicle_capacity = 0 # 单车最大载重
self.max_route_distance = 0 # 单车最大行驶距离
self.routes = [] # 存储优化后的路线,每条路线是客户索引的列表
self.total_distance = 0.0 # 所有路线总行驶距离
self.savings_list = [] # 存储计算出的所有节约值 (i, j, saving)
接下来是数据输入部分。在实际应用中,数据可能来自CSV文件、数据库或API。这里我们实现一个从CSV文件读取距离矩阵和需求数据的方法。假设CSV文件第一行是客户需求量(第一个数为仓库需求,通常为0),其余部分是一个对称的距离矩阵。
def load_data_from_csv(self, filepath: str):
"""
从CSV文件加载数据。
文件格式假设:
第一行:客户需求量(包括仓库,共n+1个值)
后续行:n+1 x n+1 的距离矩阵
"""
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
# 读取第一行作为需求数据
demand_row = next(reader)
self.customer_demands = [float(x) for x in demand_row]
self.num_customers = len(self.customer_demands) - 1 # 减去仓库
# 读取剩余行作为距离矩阵
self.distance_matrix = []
for row in reader:
self.distance_matrix.append([float(x) for x in row])
print(f"数据加载成功。共 {self.num_customers} 个客户。")
print(f"客户需求量: {self.customer_demands}")
print(f"距离矩阵维度: {len(self.distance_matrix)}x{len(self.distanc

&spm=1001.2101.3001.5002&articleId=154110869&d=1&t=3&u=b2d9f8e2034546c6a37d050c39c18a86)
1700

被折叠的 条评论
为什么被折叠?



