旅行商问题:从数学模型到 Python 实战

旅行商问题(Traveling Salesman Problem, TSP)是组合优化领域中一颗璀璨的明珠,也是计算机科学中 NP-hard 问题的典型代表。其问题描述简洁而优雅:给定 nnn 个城市以及两两之间的距离 d(i,j)d(i, j)d(i,j),求解一条从某一城市出发,恰好访问每个城市一次并最终返回起点的最短哈密顿回路 。尽管表述简单,但随着城市数量的增加,其解空间呈阶乘级增长,使得寻找最优解成为一项极具挑战性的任务。

从数学角度看,TSP 可以被形式化为在一个带权完全图中寻找最小权重的哈密顿回路。设城市集合为 V={1,2,...,n}V = \{1, 2, \dots, n\}V={1,2,...,n},距离函数为 d:V×V→R+d: V \times V \to \mathbb{R}^+d:V×V→R+,我们的目标是最小化路径总长度 LLL,即:

L=min⁡∑i=1nd(πi,πi+1) L = \min \sum_{i=1}^{n} d(\pi_i, \pi_{i+1}) L=mini=1∑nd(πi,πi+1)

其中 π\piπ 是城市的一个排列,且 πn+1=π1\pi_{n+1} = \pi_1πn+1=π1。由于 TSP 的复杂度极高(时间复杂度约为 O(n!)O(n!)O(n!)),对于大规模实例,精确算法往往无能为力,因此工程实践中常采用启发式算法寻求近似最优解。

为了将理论落地,我们考虑一个具体的对称 TSP 实例。假设有 4 个城市 A,B,C,DA, B, C, DA,B,C,D,它们之间的距离矩阵 DDD 定义如下:

D=0101520100352515350302025300 D = \begin{bmatrix} 0 & 10 & 15 & 20 \\ 10 & 0 & 35 & 25 \\ 15 & 35 & 0 & 30 \\ 20 & 25 & 30 & 0 \end{bmatrix} D= 0101520100352515350302025300

该矩阵是对称的,即 d(i,j)=d(j,i)d(i, j) = d(j, i)d(i,j)=d(j,i),且对角线为 0,符合欧几里得空间的基本直觉。

针对此问题,我们首先实现暴力枚举法(Brute Force) 。该方法通过遍历所有可能的城市排列(Permutation)来寻找全局最优解。虽然这种方法的时间复杂度为 O(n!)O(n!)O(n!),仅适用于小规模问题,但它保证了结果的绝对精确。以下是 Python 实现:

python 复制代码
import itertools
import math

# 城市节点
cities = ['A', 'B', 'C', 'D']

# 距离字典(邻接表形式)
dist = {
    ('A','A'): 0,   ('A','B'): 10,  ('A','C'): 15,  ('A','D'): 20,
    ('B','A'): 10,  ('B','B'): 0,   ('B','C'): 35,  ('B','D'): 25,
    ('C','A'): 15,  ('C','B'): 35,  ('C','C'): 0,   ('C','D'): 30,
    ('D','A'): 20,  ('D','B'): 25,  ('D','C'): 30,  ('D','D'): 0
}

def calculate_path_length(path):
    """计算路径总长度"""
    total = 0
    for i in range(len(path) - 1):
        total += dist[(path[i], path[i+1])]
    total += dist[(path[-1], path[0])]  # 回到起点
    return total

# 固定起点为 A,对其他城市进行全排列
start_node = 'A'
other_nodes = [city for city in cities if city != start_node]

optimal_path = None
min_distance = math.inf

# 遍历所有排列组合
for perm in itertools.permutations(other_nodes):
    current_path = (start_node,) + perm
    current_dist = calculate_path_length(current_path)
    if current_dist < min_distance:
        min_distance = current_dist
        optimal_path = current_path

print(f"最优路径(精确解): {optimal_path}")
print(f"最短距离: {min_distance}")

运行上述代码,我们可以得到最优路径为 A→B→D→CA \to B \to D \to CA→B→D→C,总距离为 808080。

然而,当城市数量增加到 20 个以上时,暴力枚举将消耗天文数字的时间。此时,我们需要转向启发式算法 。这里我们实现最近邻算法(Nearest Neighbor) ,这是一种贪婪策略:每一步都选择距离当前城市最近的未访问城市。虽然它不能保证最优,但其时间复杂度仅为 O(n2)O(n^2)O(n2),速度极快。

python 复制代码
# 城市列表
cities = {'A', 'B', 'C', 'D'}

# 距离映射(必须包含所有城市对)
dist = {
    ('A', 'B'): 10, ('B', 'A'): 10,
    ('A', 'C'): 15, ('C', 'A'): 15,
    ('A', 'D'): 20, ('D', 'A'): 20,
    ('B', 'C'): 35, ('C', 'B'): 35,
    ('B', 'D'): 25, ('D', 'B'): 25,
    ('C', 'D'): 30, ('D', 'C'): 30,
}

def calculate_path_length(route):
    """计算路径总距离"""
    total = 0
    for i in range(len(route) - 1):
        total += dist[(route[i], route[i + 1])]
    # 可选:回到起点
    total += dist[(route[-1], route[0])]
    return total

def nearest_neighbor_tsp(start, distance_map):
    """最近邻启发式求解 TSP"""
    unvisited = set(cities)
    route = [start]
    unvisited.remove(start)

    current_city = start
    while unvisited:
        next_city = min(
            unvisited,
            key=lambda city: distance_map[(current_city, city)]
        )
        route.append(next_city)
        unvisited.remove(next_city)
        current_city = next_city

    return route


# 执行最近邻算法
nn_route = nearest_neighbor_tsp('A', dist)
nn_distance = calculate_path_length(nn_route)

print(f"最近邻路径: {nn_route}")
print(f"最近邻距离: {nn_distance}")

在本例中,最近邻算法同样输出了 808080 的最优解,但在更复杂的数据集中,结果通常会略逊于最优解。这种权衡体现了算法设计中"时间"与"精度"的经典博弈。对于更大规模的现实问题,我们往往需要引入更复杂的元启发式算法(如遗传算法、模拟退火)或使用 Google OR-Tools 等专业优化库。TSP 不仅是理论的试金石,更是物流配送、电路板钻孔、基因测序等众多实际应用的核心模型。

相关推荐
CC数分1 小时前
数据可视化入门:用Python把一堆数字变成一张图
开发语言·python·信息可视化·数据分析
java_nnnn1 小时前
JavaEE进阶-JavaScript初识
开发语言·前端·javascript·java-ee·ecmascript
打工仔折腾 AI1 小时前
Prometheus接入Pushgateway实战:二进制与Docker部署、指标推送与远程写入
后端·python·docker·容器·性能优化·prometheus·ai agent 实战
YYYing.1 小时前
【设计模式系列 (五) 】原型模式
开发语言·后端·设计模式·原型模式·c/c++
重生之小比特1 小时前
【C++进阶】哈希表实现
开发语言·c++·哈希
m0_380743872 小时前
VisualStudio中OpenCV的创建与配置使用小结
开发语言·c++
FYKJ_20102 小时前
springboot鲜花销售系统91056-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·mysql·django·课程设计
子非鱼eva2 小时前
ONNX模型导出实战:PyTorch 导出 ResNet18 模型
人工智能·pytorch·python·知识图谱·onnx·昇腾知识图谱
凉茶钱2 小时前
【C++】动态内存管理完整解析:从内存划分到 new delete 底层原理
c语言·开发语言·c++·内存管理·动态内存