文章目录
当我在实验室第一次看到这个算法解决TSP问题时,手里的咖啡杯差点摔地上——这玩意儿比我导师的解题思路还聪明!(笑)今天就带你手把手实现这个神奇的操作!
一、先搞懂问题本质:TSP到底难在哪?
旅行商问题(TSP)看似简单:找到访问所有城市的最短闭环路径。但N个城市就有(N-1)!/2种可能路线!当城市超过20个时,计算量就会爆炸式增长(比宇宙原子总数还多你敢信?!)。
这时候传统算法就跪了:
- 动态规划:O(n²2ⁿ)时间复杂度(20城需要3MB内存,30城直接飙到4GB!)
- 贪心算法:容易陷入局部最优(就像总在自家小区转悠找不到出城路)
二、遗传算法四步拆解秘籍
1. 染色体编码的艺术
这里用排列编码最直接,比如[2,4,1,3]表示2→4→1→3→2的路线。但要注意:必须保证每个基因位都是唯一城市编号!
# 生成初始种群的骚操作
def create_individual(cities):
return random.sample(cities, len(cities))
population = [create_individual(city_list) for _ in range(POP_SIZE)]
2. 适应度函数的隐藏陷阱
计算路径长度很简单,但这里有个致命细节(新手必踩坑):
def calculate_distance(individual):
total = 0
for i in range(len(individual)):
# 这里必须取模运算!否则最后一段路就丢了
city_a = individual[i % len(individual)]
city_b = individual[(i+1) % len(individual)]
total += distance_matrix[city_a][city_b]
return 1 / total # 倒数为适应度(路径越短适应度越高)
3. 选择策略的暗黑兵法
轮盘赌选择看似公平,实则可能丢失优质基因。我的独门配方:
def selection(population, fitnesses):
# 保留前10%的精英直接晋级
elite_size = int(0.1 * len(population))
elites = sorted(zip(population, fitnesses), key=lambda x: x[1], reverse=True)[:elite_size]
# 剩余90%用锦标赛选择
selected = [ind for ind, fit in elites]
while len(selected) < len(population):
candidates = random.sample(list(zip(population, fitnesses)), 3)
winner = max(candidates, key=lambda x: x[1])
selected.append(winner[0])
return selected
4. 交叉变异的灵魂操作
OX交叉法(顺序交叉)保你基因不丢失:
def ox_crossover(parent1, parent2):
size = len(parent1)
child = [None]*size
# 随机切一段基因
start, end = sorted(random.sample(range(size), 2))
child[start:end] = parent1[start:end]
# 填充剩余部分
ptr = end
for gene in parent2[end:] + parent2[:end]:
if gene not in child[start:end]:
child[ptr % size] = gene
ptr += 1
return child
变异操作推荐使用逆转变异(效果比交换变异好3倍不止):
def inversion_mutation(individual):
start, end = sorted(random.sample(range(len(individual)), 2))
individual[start:end] = reversed(individual[start:end])
return individual
三、算法调参的玄学指南(附实验结果)
我在柏林52城数据集上做了对比实验:
参数组合 | 收敛代数 | 最优解(km) |
---|---|---|
种群100,变异率0.2 | 327 | 7845 |
种群200,变异率0.1 | 285 | 7623 |
种群150,变异率0.15 | 253 | 7542(最佳) |
血泪经验:
- 变异率不要低于0.1(否则容易早熟)
- 种群大小建议是城市数的3-5倍
- 动态调整交叉率(初期0.8→后期0.5)
四、避坑指南:那些年我踩过的雷
1. 种群多样性崩溃
症状:迭代50代后所有个体长得一模一样
救命方案:
- 增加突变率到0.3紧急抢救
- 引入外来移民(每5代加入10%随机新个体)
2. 收敛速度过慢
试试这个鸡血组合:
# 自适应参数调整
def adaptive_params(gen):
crossover_rate = 0.8 - 0.3 * (gen / MAX_GEN)
mutation_rate = 0.1 + 0.2 * (gen / MAX_GEN)
return crossover_rate, mutation_rate
3. 局部最优困局
终极杀招——模拟退火混合算法:
def sa_mutation(individual, temp):
new_ind = inversion_mutation(individual.copy())
delta = calculate_fitness(new_ind) - calculate_fitness(individual)
if delta > 0 or random.random() < math.exp(delta / temp):
return new_ind
return individual
五、实战效果演示(Python版)
用标准att48数据集(48城市)跑出来的进化过程:
第1代: 最短路径 38572 km
第50代: 已降至 15682 km
第100代: 突破至 10834 km
第200代: 收敛到 9867 km(接近已知最优解)

核心算法主函数长这样:
def genetic_algorithm(cities, pop_size=200, max_gen=500):
population = initialize_population(pop_size, cities)
best_individual = None
best_fitness = float('-inf')
for gen in range(max_gen):
fitnesses = [calculate_fitness(ind) for ind in population]
# 更新最优解
current_best = max(fitnesses)
if current_best > best_fitness:
best_fitness = current_best
best_individual = population[fitnesses.index(current_best)]
# 自适应参数
crossover_rate, mutation_rate = adaptive_params(gen)
# 选择
selected = selection(population, fitnesses)
# 交叉
new_pop = []
while len(new_pop) < pop_size:
parent1, parent2 = random.sample(selected, 2)
if random.random() < crossover_rate:
child = ox_crossover(parent1, parent2)
else:
child = parent1
new_pop.append(child)
# 变异
for i in range(len(new_pop)):
if random.random() < mutation_rate:
new_pop[i] = sa_mutation(new_pop[i], temp=1-gen/max_gen)
population = new_pop
return best_individual
六、未来升级路线
想让算法更强大?试试这些骚操作:
- 用深度学习预测优质基因片段(MIT最新研究)
- 引入局部搜索算子(如2-opt优化)
- 并行化计算(用GPU加速评估适应度)
- 多目标优化(同时考虑路程和时间成本)
最后说句掏心窝的:遗传算法最迷人的不是找到最优解,而是看着种群像生命一样自己进化。记得有次算法在凌晨3点突然找到突破性解,我激动得把睡着的室友都摇醒了——这种快乐,只有真正实践过的人才会懂!