大規模 VRP 與啟發式演算法
VRP 是 NP-Hard 問題。當客戶數量超過 100 個時,OR-Tools 的 exact solver 可能需要數小時才能找到最佳解。實務上,我們使用啟發式演算法來找到「夠好」的解。
Clarke-Wright 節省法 (Savings Algorithm)
這是實務上最常用的 VRP 啟發式演算法,由 Clarke 與 Wright 在 1964 年提出。
import numpy as np
def clarke_wright_savings(dist_matrix, demands, capacity, depot=0):
"""
Clarke-Wright 節省法
核心概念:合併兩條路線能節省的距離 = d(0,i) + d(0,j) - d(i,j)
"""
num_nodes = len(dist_matrix)
# 第一階段:每個節點一條獨立路線
routes = [[i] for i in range(num_nodes) if i != depot]
# 計算所有節點對的節省值
savings = []
for i in range(1, num_nodes):
for j in range(i + 1, num_nodes):
saving = dist_matrix[depot][i] + dist_matrix[depot][j] - dist_matrix[i][j]
if saving > 0: # 只保留有節省效果的
savings.append((saving, i, j))
# 按節省值排序(從大到小)
savings.sort(reverse=True, key=lambda x: x[0])
# 第二階段:合併路線
node_to_route = {}
for idx, route in enumerate(routes):
for node in route:
node_to_route[node] = idx
for saving, i, j in savings:
route_i = node_to_route[i]
route_j = node_to_route[j]
if route_i == route_j:
continue # 已經在同一路線
# 檢查容量限制
merged_demand = sum(demands[node] for node in routes[route_i] + routes[route_j])
if merged_demand > capacity:
continue # 超過容量
# 合併路線
# 確保 i 在末端,j 在開端
if routes[route_i][-1] == i and routes[route_j][0] == j:
new_route = routes[route_i] + routes[route_j]
elif routes[route_i][0] == i and routes[route_j][-1] == j:
new_route = routes[route_j] + routes[route_i]
elif routes[route_i][-1] == i and routes[route_j][-1] == j:
new_route = routes[route_i] + list(reversed(routes[route_j]))
elif routes[route_i][0] == i and routes[route_j][0] == j:
new_route = list(reversed(routes[route_j])) + routes[route_i]
else:
continue
# 合併
routes[route_i] = new_route
routes[route_j] = []
# 更新映射
for node in new_route:
node_to_route[node] = route_i
# 過濾空路線
return [r for r in routes if r]
# 測試
np.random.seed(42)
n = 50 # 50 個客戶
locations = np.random.rand(n + 1, 2) * 100
# 距離矩陣
dist = np.zeros((n + 1, n + 1))
for i in range(n + 1):
for j in range(n + 1):
dist[i][j] = np.sqrt((locations[i][0]-locations[j][0])**2 +
(locations[i][1]-locations[j][1])**2)
demands = [0] + list(np.random.randint(50, 200, n))
capacity = 500
print(f"\n=== Clarke-Wright 節省法 ===")
print(f"客戶數: {n}, 車輛容量: {capacity}")
routes = clarke_wright_savings(dist, demands, capacity)
print(f"\n路線數量: {len(routes)}")
for idx, route in enumerate(routes):
route_dist = sum(dist[node][route[(i+1) % len(route)]]
for i, node in enumerate(route))
route_demand = sum(demands[node] for node in route)
print(f"路線 {idx}: {route} (距離: {route_dist:.1f}, 載重: {route_demand})")
車輛路徑問題 VRP
VRP 是物流業的核心最佳化問題。
變體
- CVRP:容量限制
- VRPTW:時間窗限制
- MDVRP:多車場
- VRP with Backhauls
大規模 VRP 的解法策略
當客戶數超過 200、車輛數超過 50 時,OR-Tools 的 Local Search 會因為搜尋空間太大而跑不完。這時需要分群 + 分階段的策略。
分群再路由(Cluster First, Route Second)
1. 用 K-Means 或 Spectral Clustering 把客戶分成 N 群
(N = 車輛數,每群的總需求 ≤ 車輛容量)
2. 對每一群獨立求解 TSP(旅行推銷員問題)
3. 合併所有路線即為最終解
這個策略的優點是:將一個大問題拆成多個小問題,每個小問題的求解速度快很多。
OR-Tools 的參數調整
| 參數 | 預設值 | 大規模建議 | 效果 |
|:----|:-----|:---------|:----|
| time_limit_ms | 1000 | 30000-60000 | 給求解器更多時間 |
| first_solution_strategy | PATH_CHEAPEST_ARC | SAVINGS | 初始解品質更好 |
| local_search_metaheuristic | GUIDED_LOCAL_SEARCH | SIMULATED_ANNEALING | 跳脫局部最佳 |
下一章預告:VRP API
學會了大規模 VRP 的解法之後,下一章把它包成 API——讓物流管理系統可以呼叫最佳化引擎。