遗传算法(GA算法)求解实例---旅行商问题 (TSP)
目录
- 一、采用GA求解 (TSP)
- 二、 旅行商问题
- 2.1 旅行商问题简介
- 2.2 使用遗传算法解决 TSP
- 2.2.1 遗传算法求解 TSP 的基本步骤
- 2.3 实际例子:求解 6 个城市的 TSP
- 1. 初始化种群
- 2. 计算适应度
- 3. 选择操作
- 4. 交叉操作
- 5. 变异操作
- 6. 生成新种群
- 7. 迭代与终止
- 三、 ==**采用GA求解TSP**==
- 3.1 求解该问题的代码
- 3.2 代码运行过程截屏
- 3.3 代码运行结果截屏(后续和其他算法进行对比)
- 四、 ==如何修改代码?==
- 4.1 减少城市坐标,如下:
- 4.2 增加城市坐标,如下:
- 五、 遗传算法 (Genetic Algorithm, GA) 原理
- 5.1 GA算法定义
- 5.2 GA算法的基本思想
- 5.3 GA算法的工作流程
- 5.4 GA算法的操作
- 1. 初始化种群
- 2. 适应度函数
- 3. 选择操作
- 4. 交叉操作
- 5. 变异操作
- 5.5 GA算法的优缺点
- 5.5.1 优点
- 5.5.2 缺点
- 5.6 GA算法的应用场景
一、采用GA求解 (TSP)
求解代码在文中,后续会出其他算法求解TSP问题,你们参加数学建模竞赛只需要会改代码即可。
我知道大家对原理性的东西不感兴趣,我把原理性的东西放在后面,大家如果需要写数模论文可以拿去,但是记得需要改一改,要不然查重过不去。
二、 旅行商问题
2.1 旅行商问题简介
旅行商问题(Travelling Salesman Problem, TSP) 是一个经典的组合优化问题,问题描述如下:
给定一组城市以及每对城市之间的距离,一个旅行商需要从某个城市出发,经过每个城市恰好一次,并最终回到起点城市。目标是找到一条总距离最短的旅行路线。
2.2 使用遗传算法解决 TSP
遗传算法是解决 TSP 问题的有效方法之一。它通过模拟自然进化过程(如选择、交叉、变异等)来逐步优化解,寻找最短路径。
2.2.1 遗传算法求解 TSP 的基本步骤
-
定义编码方式:
- 一个个体(即解)表示为一个城市序列。例如,假设有 5 个城市,用数字
0, 1, 2, 3, 4
表示,一个可能的解为[0, 2, 4, 3, 1]
,表示从城市 0 出发,依次经过城市 2, 4, 3, 1,最后回到城市 0。
- 一个个体(即解)表示为一个城市序列。例如,假设有 5 个城市,用数字
-
定义适应度函数:
- 适应度函数用于评估每个个体的优劣。在 TSP 中,适应度通常定义为旅行总距离的倒数(因为我们希望最小化距离)。例如,个体的路径为
[0, 2, 4, 3, 1]
,则其适应度为该路径总距离的倒数。
- 适应度函数用于评估每个个体的优劣。在 TSP 中,适应度通常定义为旅行总距离的倒数(因为我们希望最小化距离)。例如,个体的路径为
-
初始化种群:
- 随机生成一组解(路径),构成初始种群。种群中的每个个体表示一个可能的路径。
-
选择操作:
- 根据个体的适应度值,以一定的概率选择个体作为父代,用于生成下一代种群。适应度高的个体有更大的概率被选择。
-
交叉操作:
- 从父代中选取两个个体进行交叉操作,生成新的子代。例如,采用部分映射交叉(Partially Mapped Crossover, PMX)。
-
变异操作:
- 对新生成的个体(子代)进行随机变异,以增加解的多样性。例如,随机交换两个城市的位置。
-
生成新种群:
- 用子代替换父代,形成新的种群。
-
迭代和终止:
- 重复上述过程,直到达到预定的迭代次数或找到一个满意的解。
2.3 实际例子:求解 6 个城市的 TSP
假设有 6 个城市,其坐标如下:
城市 | X 坐标 | Y 坐标 |
---|---|---|
0 | 10 | 20 |
1 | 30 | 40 |
2 | 20 | 10 |
3 | 40 | 30 |
4 | 10 | 10 |
5 | 50 | 20 |
目标是找到一个经过所有城市且总距离最短的路径。
1. 初始化种群
初始种群由一组随机生成的城市序列组成,例如:
- 个体 1:
[0, 2, 4, 1, 5, 3]
- 个体 2:
[1, 0, 3, 5, 4, 2]
- 个体 3:
[3, 1, 0, 2, 4, 5]
- …(假设种群大小为 100)
2. 计算适应度
对每个个体计算其路径总距离,例如:
- 个体 1 的路径
[0, 2, 4, 1, 5, 3]
:- 路径总距离 =
距离(0, 2) + 距离(2, 4) + 距离(4, 1) + 距离(1, 5) + 距离(5, 3) + 距离(3, 0)
- 假设计算得到路径总距离为
100
,则适应度为1/100 = 0.01
。
- 路径总距离 =
3. 选择操作
采用轮盘赌选择法:根据适应度值的大小,随机选择适应度高的个体作为父代。
4. 交叉操作
采用部分映射交叉(PMX):
- 父代 1:
[0, 2, 4, 1, 5, 3]
- 父代 2:
[1, 0, 3, 5, 4, 2]
选取交叉点(例如 2
和 4
),交换基因片段后得到子代:
- 子代 1:
[0, 2, 3, 5, 4, 3]
- 子代 2:
[1, 0, 4, 1, 5, 2]
再根据父代保持路径唯一性调整得到合法子代:
- 调整后子代 1:
[0, 2, 3, 5, 1, 4]
- 调整后子代 2:
[1, 0, 3, 2, 5, 4]
5. 变异操作
对子代进行变异,例如随机交换子代 1 的城市位置 [5, 1]
:
- 变异后子代 1:
[0, 2, 3, 1, 5, 4]
6. 生成新种群
用子代替换种群中的劣质个体,形成新的种群。
7. 迭代与终止
重复上述步骤,迭代进行进化,直到达到预定的迭代次数或找到最短路径。
三、 采用GA求解TSP
3.1 求解该问题的代码
import numpy as np
import random# 定义城市坐标
cities = np.array([[10, 20],[30, 40],[20, 10],[40, 30],[10, 10],[50, 20]
])# 计算两城市之间的欧几里得距离
def calculate_distance(city1, city2):return np.sqrt(np.sum((city1 - city2) ** 2))# 计算总旅行距离
def total_distance(path):distance = 0for i in range(len(path) - 1):distance += calculate_distance(cities[path[i]], cities[path[i + 1]])distance += calculate_distance(cities[path[-1]], cities[path[0]]) # 回到起点return distance# 初始化种群
def initialize_population(pop_size, num_cities):population = []for _ in range(pop_size):individual = list(np.random.permutation(num_cities))population.append(individual)return population# 计算种群的适应度
def fitness(population):fitness_scores = []for individual in population:fitness_scores.append(1 / total_distance(individual))return fitness_scores# 选择操作:轮盘赌选择法
def selection(population, fitness_scores):selected = []total_fit = sum(fitness_scores)prob = [f / total_fit for f in fitness_scores]for _ in range(len(population)):selected.append(population[np.random.choice(len(population), p=prob)])return selected# 交叉操作:部分映射交叉(PMX)
def crossover(parent1, parent2):size = len(parent1)child = [-1] * size# 随机选择交叉点start, end = sorted(random.sample(range(size), 2))# 将父代1的片段复制到子代中child[start:end] = parent1[start:end]# 填充子代其余部分for i in range(size):if parent2[i] not in child:for j in range(size):if child[j] == -1:child[j] = parent2[i]break# 修复可能的重复问题for i in range(size):if child[i] == -1:for city in parent2:if city not in child:child[i] = citybreakreturn child# 变异操作:交换变异
def mutation(individual, mutation_rate=0.01):for i in range(len(individual)):if random.random() < mutation_rate:j = random.randint(0, len(individual) - 1)# 交换两个城市的位置individual[i], individual[j] = individual[j], individual[i]return individual# 遗传算法主函数
def genetic_algorithm(cities, pop_size=100, generations=500, mutation_rate=0.01):num_cities = len(cities)population = initialize_population(pop_size, num_cities)for generation in range(generations):fitness_scores = fitness(population)print(f"Generation {generation} Best distance: {1 / max(fitness_scores):.2f}")# 选择selected = selection(population, fitness_scores)# 交叉next_generation = []for i in range(0, len(selected), 2):parent1 = selected[i]parent2 = selected[(i + 1) % len(selected)]child1 = crossover(parent1, parent2)child2 = crossover(parent2, parent1)next_generation.extend([child1, child2])# 变异population = [mutation(ind, mutation_rate) for ind in next_generation]# 最佳路径best_individual = min(population, key=lambda ind: total_distance(ind))return best_individual, total_distance(best_individual)# 运行遗传算法
best_path, best_distance = genetic_algorithm(cities)
print("Best path:", best_path)
print("Best distance:", best_distance)
3.2 代码运行过程截屏
3.3 代码运行结果截屏(后续和其他算法进行对比)
四、 如何修改代码?
这一部分是重中之重,大家参加数学建模肯定是想跑出自己的结果,所以大家只需要把自己遇到的数学问题,抽象成TSP问题,然后修改代码的城市坐标,然后运行即可。
# 定义城市坐标
cities = np.array([[10, 20],[30, 40],[20, 10],[40, 30],[10, 10],[50, 20]
])
4.1 减少城市坐标,如下:
# 定义城市坐标
cities = np.array([[10, 20],[30, 40],[20, 10],[40, 30]
])
4.2 增加城市坐标,如下:
# 定义城市坐标
cities = np.array([[10, 20],[30, 40],[20, 10],[40, 30],[30, 40],[20, 10],[10, 10],[50, 20]
])
五、 遗传算法 (Genetic Algorithm, GA) 原理
5.1 GA算法定义
遗传算法 (Genetic Algorithm, GA) 是一种基于自然选择和遗传机制的随机搜索和优化方法,由 John Holland 在 20 世纪 70 年代提出。遗传算法通过模拟自然界生物的进化过程(如遗传、选择、交叉、变异等)来搜索解空间,从而找到问题的最优解。它常用于复杂的优化问题和求解计算难题。
5.2 GA算法的基本思想
遗传算法的核心思想是模拟达尔文进化论的“自然选择,适者生存”原理。在遗传算法中,候选解被表示为“个体”,多个个体组成一个“种群”。通过对种群中的个体进行复制、交叉、变异等操作,算法在搜索空间内不断迭代,逐步进化出接近最优解的结果。
遗传算法主要包括以下几个步骤:
- 初始化种群:随机生成一定数量的个体作为初始种群,每个个体表示一个可能的解。
- 适应度评估:根据适应度函数(目标函数)评估每个个体的优劣,适应度值越高表示个体越优。
- 选择操作:根据个体的适应度值,以一定的概率选择个体作为父代,以生成下一代种群。适应度高的个体有更大的概率被选择。
- 交叉操作:通过交换两个个体(父母)的部分基因(染色体片段),生成新的个体(子代)。这模拟了生物的繁殖过程。
- 变异操作:随机改变个体的部分基因,以增加种群的多样性,避免陷入局部最优解。
- 生成新种群:用选择、交叉和变异操作产生的新个体替代旧种群,并重复步骤 2 到 5,直到满足终止条件(如达到最大迭代次数或找到最优解)。
5.3 GA算法的工作流程
遗传算法的典型工作流程如下:
- 初始化种群:随机生成种群内的所有个体。
- 迭代进化:
- 计算适应度:评估种群中每个个体的适应度。
- 选择:根据适应度选择部分个体作为父代。
- 交叉:对子代进行交叉操作,生成新个体。
- 变异:对部分新个体进行变异操作。
- 更新种群:用新个体替代旧种群。
- 终止条件:如果满足终止条件(如达到最大迭代次数或找到满意解),输出结果;否则,返回第 2 步继续迭代。
5.4 GA算法的操作
1. 初始化种群
种群中的每个个体通常用一个二进制串、实数串或其他编码方式表示。初始种群的大小通常根据问题的规模和复杂度来决定。
2. 适应度函数
适应度函数(Fitness Function)用于评估每个个体的质量。适应度函数的设计应能够正确反映目标问题的优化目标,适应度值越高表示个体越优。
3. 选择操作
常见的选择方法有:
- 轮盘赌选择法(Roulette Wheel Selection):根据个体的适应度值,以概率比例进行选择,适应度高的个体被选择的概率更大。
- 锦标赛选择法(Tournament Selection):随机挑选若干个体进行竞赛,选择其中适应度最高的个体。
- 排序选择法(Rank Selection):将个体按适应度排序,赋予选择概率。
4. 交叉操作
交叉(Crossover)是模拟生物遗传过程中的基因重组操作。常见的交叉方法有:
- 单点交叉(Single-Point Crossover):在染色体中随机选择一个位置,交换两个父代的基因片段。
- 多点交叉(Multi-Point Crossover):在染色体中选择多个位置进行基因片段的交换。
- 均匀交叉(Uniform Crossover):以固定的概率在每个位置选择父代的基因进行交换。
5. 变异操作
变异(Mutation)是对个体基因的随机改变,以增加种群的多样性。常见的变异方法有:
- 位变异(Bit Mutation):随机改变个体二进制串的某个位(0 变 1 或 1 变 0)。
- 实数变异(Real-Value Mutation):对实数编码的基因加入随机扰动。
5.5 GA算法的优缺点
5.5.1 优点
- 全局搜索能力强:遗传算法通过并行搜索和种群进化,能有效避免局部最优解,具有较强的全局搜索能力。
- 不依赖梯度信息:遗传算法不需要目标函数的导数或梯度信息,适用于不可微分、非线性、多峰值等复杂优化问题。
- 适应性强:能够处理不同类型的优化问题(如离散和连续优化问题)。
5.5.2 缺点
- 计算成本高:遗传算法需要大量计算资源,尤其是在种群规模大和问题规模大时,计算效率较低。
- 收敛速度慢:相对于一些局部搜索方法,遗传算法的收敛速度较慢。
- 参数敏感:算法性能对参数(如种群规模、交叉率、变异率等)比较敏感,需要进行调优。
5.6 GA算法的应用场景
- 函数优化:在非线性、多峰值函数优化问题中,寻找全局最优解。
- 组合优化:如旅行商问题(TSP)、背包问题、调度问题等。
- 机器学习:用于特征选择、超参数优化、神经网络结构优化等。
- 工程优化:如结构优化、控制参数调优、交通流量优化等。
- 生物信息学:如 DNA 序列比对、基因表达数据分析等。