本次练习基于EC算法经典的旅行商TSP问题
练习目标:
在所给的架构下编写并运行一个正确的进化算法,并解决经典的旅行商TSP问题。在该练习条件下,当每一次旅行商的旅行距离均低于13500公里时,则代表所编写之算法有效。
有两点额外要求:(1) 算法中人口规模Population必须 ≤100。(2) 迭代次数 ≤1000
实验中可以自由更改超参数,如突变/交叉概率(后续代码中提及)。
Traveling Salesman Problem
在本例中,我们学习如何使用EA来解决旅行推销员的问题。在这个问题中,一个推销员需要完成以最短的方式访问所有城市并返回出发点的任务。此外,我们假设旅行推销员有一个固定的起点和终点(阿姆斯特丹)。因此,有两个约束:
-每个城市都需要被访问到。
-旅行者以家作为起点和终点(本例中以阿姆斯特丹为例)。
在该练习中,我们将使用以下城市,并使用以下编码:
-0:阿姆斯特丹
-1:雅典
-2:柏林
-3:布鲁塞尔
-4:哥本哈根
-5:爱丁堡
-6:里斯本
-7:伦敦
-8:马德里
-9:巴黎
在本例中,我们使用邻接表来表示城市之间的距离,其中Aij表示从城市i到城市j的距离,通过查询我们可以得到上述城市间的距离邻接表,通过该链接可以查到https://www.engineeringtoolbox.com/driving-distances-d_1029.html
adjacency_mat = np.asarray(#Remember that we use the encoding above, i.e. 0 refers to Amsterdam and 10 to Paris! [[0, 3082, 649, 209, 904, 1180, 2300, 494, 1782, 515], # Distance Amsterdam to the other cities[3082, 0, 2552, 3021, 3414, 3768, 4578, 3099, 3940, 3140], # Distance Athens to the other cities[649, 2552, 0, 782, 743, 1727, 3165, 1059, 2527, 1094], # Distance Berlin to the other cities[209, 3021, 782, 0, 1035, 996, 2080, 328, 1562, 294], # Distance Brussels to the other cities[904, 3414, 743, 1035, 0, 1864, 3115, 1196, 2597, 1329], # Distance Copenhagen to the other cities[1180, 3768, 1727, 996, 1864, 0, 2879, 656, 2372, 1082], # Distance Edinburgh to the other cities[2300, 4578, 3165, 2080, 3115, 2879, 0, 2210, 638, 1786], # Distance Lisbon to the other cities[494, 3099, 1059, 328, 1196, 656, 2210, 0, 1704, 414], # Distance London to the other cities[1782, 3940, 2527, 1562, 2597, 2372, 638, 1704, 0, 1268], # Distance Madrid to the other cities[515, 3140, 1094, 294, 1329, 1082, 1786, 414, 1268, 0] # Distance Paris to the other cities ])
对于进化算法,首先我们需要完成Fitness Function(适应度函数)的编写。在本例中,适应度函数将旅行者行驶的总公里数作为适应度的衡量标准是直观的。给定路线覆盖的总公里数越低越好,因此我们编写如下fitness_function
1 def compute_distance(route: list, adjacency_mat: np.ndarray) -> int: 2 ''' 3 Calculates the total number of kilometers for a given route. 4 ''' 5 total_distance = 0 # Initialize the value of total distance 初始化路径的值为0 6 number_city = len(route) # Number of cities in the route 路径中的城市数量 7 ''' 8 ToDo: 9 10 Please complete the function that calculates the total distance for a given route! 11 ''' 12 13 # 我们首先遍历路径中的每对连续城市 14 for i in range(number_city - 1): # 因为终点城市使用i+1,且Python的range()所取值是一个左闭右开区间,所以这样编写函数能保证最后一个城市被涵盖的同时不超出数组边界 15 start_city = route[i] 16 des_city = route[i + 1] 17 total_distance += adjacency_mat[start_city, des_city] 18 # 别忘了从最后一个城市返回到起点 19 total_distance += adjacency_mat[route[-1], route[0]] # Calculate the distance from the last city back to the starting point 20 return total_distance
解释:
-
循环遍历路线中的每对连续城市:
- 通过
for i in range(num_cities - 1)
迭代路线中的每对相邻城市。 - 从
route
中获取起点和终点城市的索引,然后使用adjacency_mat[from_city, to_city]
查找它们之间的距离并将其添加到total_distance
。
- 通过
-
处理回到起点的情况:
- 在旅行商问题中,路径通常是循环的,所以在计算完所有相邻城市之间的距离后,还需要计算从最后一个城市回到起点的距离,并将其添加到总距离中。
-
返回总距离:
- 最后返回计算的
total_distance
。
- 最后返回计算的
教材内容补充:
Evaluation/ fitness function
Role:
- Fitness fuction represents the task to solve, the requirements to adapt to (can be seen as 'the environment') 适应度函数代表了要解决的任务、要适应的要求(可以看作是“环境”)
- Enable Selection 使得后续的算法中可以对样本进行选择
- If some phenotypic traits are advantegous, desirable, then these traits are rewared by more offspring that will expectedly carry the same trait 如果一些表型特征是有利的、可取的,那么这些特征会被更多的后代所认识(表现为适应度高),这些后代有望携带相同的特征
Assgin a single real-valued fitness to each phenotype which forms the basis for selection 为每个表型提供一个单一的实值适应度,作为选择的基础 (所以不同的值越多越好 the more discrimination[different values] the better)
Typically we talk about fitness being maximised 通常我们讨论如何让适应值最大,但某些问题则是适应度越小越好(比如本例)
完成Fitness function的编写后,下一步我要做的是从一代个体中找出表现最佳的个体(于本例而言,距离最短的)
def fittest_solution_TSP(compute_distance, generation: list, adjancency_mat: np.ndarray) -> tuple:'''This function calculates the fitness values of all individuals of a generation.It then returns the best fitness value (integer) and the corresponding individual (list).该函数计算一代中所有个体的适应度值,并返回最佳适应度值(整数)及其对应的个体(列表)。''''''ToDo:Please complete the function!'''#WRITE YOUR CODE HERE!best_fitness_value = float("inf") # 初始化为无限大,表示我们寻找最小值best_individual = None # 初始化最佳路线# Calculate the fitness value of the current individualfor indiv in generation:# 计算当前个体的适应度值(即总距离)用到了上一步编写的适应度函数(计算路径距离)current_fitness_value = compute_distance(indiv,adjacency_mat)# If the current individual's fitness value is better than the best fitness value found so far, update it# 如果当前个体的适应度值比目前找到的最佳适应度值更好,则更新if current_fitness_value <= best_fitness_value:best_fitness_value = current_fitness_valuebest_individual = indivreturn best_fitness_value,best_individual
解释:
-
初始化最佳值:
best_fitness_value
被初始化为float('inf')
,这是一个极大的值。我们要寻找最小的距离,因此需要从一个极大的初始值开始。best_individual
被初始化为None
,以便在找到最佳路径时更新。
-
遍历种群:
- 对于
generation
中的每一个个体(路径),计算其适应度值(即通过调用compute_distance(indiv, adjacency_mat)
计算总距离)。
- 对于
-
更新最佳路径:
- 如果当前个体的总距离小于当前的
best_fitness_value
,则更新best_fitness_value
和best_individual
,记录这个更优的个体。
- 如果当前个体的总距离小于当前的
-
返回值:
- 返回找到的最小总距离
best_fitness_value
和对应的路径best_individual
。
- 返回找到的最小总距离
这个函数的核心是遍历整个种群,找出在这代中最优的解决方案。输出的最佳距离及其路径可用于进一步的算法优化或作为最终结果。
教材内容补充:
Selection
Role:
- Identifies individuals 识别辨别个体 (成为亲本、生存、淘汰等等)
- Pushes population towards higher fitness 推动人口世代向更高的适应度水平拟合
- Usally probabilistic/stochastic 通常为概率/随机
- high quality solution more likely be selected than low quality solutions 高质量解比低质量解更容易被选中
- but no guaranteed 并不一定是高质量的被选中
- even the worst in current population usually has non-zero pobability of being selected 最差的解被选中的概率也不是0
- This stochastic nature can help escape from local optima 随机性帮助摆脱局部最优解
Assgin a single real-valued fitness to each phenotype which forms the basis for selection 为每个表型提供一个单一的实值适应度,作为选择的基础 (所以不同的值越多越好 the more discrimination[different values] the better)
Typically we talk about fitness being maximised 通常我们讨论如何让适应值最大,但某些问题则是适应度越小越好(比如本例)
定义了适应度函数后,我们需要一个函数来初始化(生成)我们的解决方案。
def initialize_population(n_population: int) -> list:'''This returns a randomly initialized list of individual solutions of size n_population.'''population = []# Based on a literature review, the suggested range for population size# is between one and two times the path length. i.e len(route) <= n <= 2*len(route)'''ToDo:Please complete the function!'''for _ in range(n_population):# 创建一个包含城市 1 到 9 的路径fixed_prefix,fixed_suffix = [0],[0]default_list = list(range(1, 10)) # 从城市 1 到 9(排除城市 0)random.shuffle(default_list) # 随机打乱这些城市的顺序individual = fixed_prefix + default_list + fixed_suffix # 为城市列表添加固定的前缀后后缀(即起终点都为阿姆斯特丹)population.append(individual)for individual in population: # 对这些新添加的个体进行检查#Implement some assertion tests for checking if the individual meets criteriaassert (individual[0] == individual[-1]==0), 'Make sure you start and end in Amsterdam' #起终点assert len(set(individual)) == 10, "Individual must contain all unique values from 0 to 9, as is must visit all cities" # 每个城市都访问了#set(individual):#将 individual 列表转换为一个集合 set。集合是一个无序的数据结构,自动去除重复的元素。#因此,如果 individual 列表中有重复的城市,集合会自动去掉这些重复项。assert (len(individual) == 11), "Individual must be length 11" # 长度必须为11 return population
在 initialize_population
函数中,我们的目标是生成一个具有 n_population
个体的初始种群。每个个体代表一个城市的访问顺序,路径的长度需要包括返回起点的步骤(即从城市 0
出发,经过所有其他城市,最后返回到城市 0
)。你需要确保每个个体满足以下条件:
- 路径以城市
0
(阿姆斯特丹)开始并结束。 - 路径包含所有城市,且每个城市只出现一次。
- 路径的长度为 11(包括从起点城市
0
开始,经过所有其他城市,最后返回到城市0
)。
解释:
-
生成城市路径:
deafult_list = list(range(1, 10))
创建了一个包含城市1
到9
的列表(排除起点城市0
)。random.shuffle(deafult_list)
随机打乱这些城市的顺序,以生成不同的访问路径。
-
创建个体路径:
-
individual = fixed_prefix + default_list + fixed_suffix
0
,表示从起点出发并最终返回到起点城市。
-
-
添加到种群:
- 将生成的个体路径添加到
population
列表中。
- 将生成的个体路径添加到
-
断言检查:
- 确保每个个体以城市
0
开始并结束。 - 确保每个个体包含所有城市,并且每个城市只出现一次。
- 确保个体的路径长度为 11。
- 确保每个个体以城市
这个实现会生成一个满足指定条件的初始种群列表,并对其进行验证。
现在我们已经定义了适应度函数和初始化人口的函数,我们需要定义变分算子,即交叉 Cross-Over和突变 Mutation。对于排列问题,一般将问题分为如下两类
顺序先决的问题(生产问题)或元素相邻的问题(邻接)。在我们的TSP问题中,重要的是哪个元素彼此相邻,即邻接。
在这个 mutation
函数中,我们需要实现一个突变操作,以对给定的路径 child
进行修改。突变是遗传算法中的一个关键步骤,可以帮助搜索到新的解空间。这里我们将讨论几种突变操作(如题所述),并在函数中实现其中一个或多个。我们还要确保突变后的路径满足特定条件,比如路径仍然以阿姆斯特丹(城市0)开始和结束,且必须覆盖所有城市。
突变操作选项
-
Swap Operator(交换操作):
- 随机选择路径中的两个城市,并交换它们的位置。
-
Insert Operator(插入操作):
- 随机选择路径中的一个城市,并将其插入到路径中的另一个位置。
-
Scramble Operator(打乱操作):
- 随机选择路径中的一个子序列,并将其打乱。
-
Inversion Operator(逆序操作):
- 随机选择路径中的一个子序列,并将其反转。
def mutation(child:list, p_mutation:float) -> list:'''This applies a mutation operator to a list and returns the mutated list.'''if np.random.uniform() > p_mutation: #np.random.uniform(default low=0.0, high = 1.0) -> generate a float in the interval [0,1)#no mutationreturn childelse:child_mutated = child[:] # Create a copy of the child to mutate# mutation_operator = np.random.choice(['swap', 'insert', 'scramble', 'inversion']) # Using mutation operator randomlymutation_operator = 'swap' # Using 'swap' as example'''ToDo:Please complete the function!'''if mutation_operator == 'swap':i,j = np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace = False)child_mutated[i],child_mutated[j] = child_mutated[j],child_mutated[i] # Swap two random citieselif mutation_operator == 'insert':i = np.random.choice(range(1,len(child_mutated) - 1))insert_city = child_mutated.pop(i) # Pop a city from list randomlyj = np.random.choice(range(1,len(child_mutated) - 1)) # Choose a new position from list to insertchild_mutated.append(j, insert_city)elif mutation_operator == 'scramble':i,j = sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False)) # Choose two numbers(Ascending sorted) as the start and end of the subsequencenp.random.shuffle(child_mutated[i:j+1]) # Shuffle a random subsequence of the path# 为什么是[i:j+1] 因为Python中切片是不包含右侧最大值的即如果我们想要获取的子序列是列表中的第i到第j个值,那么我们切片时# child[i:j+1]:返回从索引 i 到索引 j 的元素,即 包含起点 i 和终点 j。elif mutation_operator == 'inversion':i,j = sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False))# Choose two numbers(Ascending sorted) as the start and end of the subsequencechild_mutated[i:j+1] = child_mutated[i:j+1][::-1] # Reverse the subsequence of the path# child[i:j+1][::-1] 这一表达式中的 [::-1] 是 Python 中的切片语法,用于反转序列(如列表、字符串等)。#Implement some assertion tests for checking if the mutation goes as expectedassert (child_mutated[0] == child_mutated[-1] and child_mutated[0] == 0 ), 'Make sure you start and end in Amsterdam'assert len(set(child_mutated)) == 10, "Individual must contain all unique values from 0 to 9, as is must visit all cities"assert (len(child_mutated) == 11), "Individual must be length 11"return child_mutated
解释
-
突变的概率控制:我们首先使用
np.random.uniform()
来决定是否对个体进行突变。如果生成的随机数大于p_mutation
,则不进行突变,直接返回原始的child
。 -
突变操作选择:我们随机选择一种突变操作,分别为交换、插入、打乱和逆序。
-
操作具体实现:
- 交换:随机选择两个位置并交换它们。
- 插入:将一个城市移除,然后插入到另一个随机位置。
- 打乱:选择一个子序列并随机打乱它。
- 逆序:选择一个子序列并将其顺序反转。
-
断言检查:我们使用断言确保突变后的路径依然满足所有的条件,特别是路径以城市0开始和结束,并且所有城市都被访问一次。
1. Scramble Operator(打乱操作)
目的:打乱路径中的某个子序列,但不改变子序列中各个城市的出现频率。
步骤:
- 随机选择路径中的两个位置(
i
和j
),定义一个子序列。 - 将这个子序列的城市顺序随机打乱。
- 替换原路径中的该子序列。
2. Inversion Operator(逆序操作)
目的:将路径中的某个子序列反转,使其顺序颠倒。
步骤:
- 随机选择路径中的两个位置(
i
和j
),定义一个子序列。 - 将这个子序列的顺序逆转。
- 替换原路径中的该子序列。
sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False))
关于上述代码的解释,使用np.random.choice()函数,从child序列中选出子序列的开头和末尾,因为是获得list的索引,所以使用range(1,len(child_muted))能刚好覆盖除了出发点、结尾点(阿姆斯特丹)以外的其他城市,使用sorted()保证取出的第一个数比第二个小,后续不需要做顺序调整,简化代码
逆序补充
Python 的切片语法是 list[start:end:step]
,它允许我们从列表或其他序列类型中提取特定部分。各部分的含义如下:
start
:切片开始的索引(包含)。end
:切片结束的索引(不包含)。step
:切片的步长,即每次从序列中取元素时的间隔。
[::-1]
的含义
- 当
start
和end
都为空时,表示从头到尾遍历整个序列。 step
为-1
表示以倒序的方式遍历序列,因此[::-1]
表示从序列的最后一个元素到第一个元素,按相反的顺序返回。
swap交换补充
child_mutated[i],child_mutated[j] = child_mutated[j],child_mutated[i] # Swap two random cities
该行代码之所以在Python中不会出错,是因为Python 中的多重赋值(multiple assignment)允许我们在同一行代码中同时更新多个变量的值。在这个过程中,右侧的表达式首先被完全计算,然后再一次性地将计算结果赋值给左侧的变量。左侧的两个值是在同一时刻交换的。右侧的值是在赋值之前就已经计算完成,因此互换的过程中,两个索引对应的值都已经被临时存储下来,不会因为某个赋值操作而改变另一个操作的结果。
*注意,该操作在JAVA中不可行,必须使用临时变量存储中间值
完成了变异算子后,我们可以继续定义交叉算子。同样,我们有多种选择可供选择:
- 单点/多点交叉
- 均匀交叉
- 洗牌交叉
- 部分映射交叉(PMX)
- 边缘重组交叉
由于我们的基因型表示的是具有特定边界的排列,因此我们需要确保保留父代的特定属性(节点之间的连接),并确保边界条件仍然满足(两端为零,且每个数字1-9只出现一次)。
边缘重组交叉非常适合本例的问题,因为它能保留元素之间的相邻关系,并确保每个元素的数量相同。
1. 剥离零点
旅行商问题的路径通常是环形的(起点和终点为同一个城市),在本问题中,城市 0
代表阿姆斯特丹。为了处理中间的城市,我们需要剥离城市 0
,即从两个父代中移除起始和终止的 0
。同样在此处,我们运用到了Python的切片[1:-1]表示取列表索引1到最后一位之前的值(因为切片右侧不计入)完成对起点终点 0的剥离
# Strip the zeros (remove the starting and ending city which is 0)
parent_1_stripped = parent_1[1:-1]
parent_2_stripped = parent_2[1:-1]
2. 创建边缘表
边缘表是一个记录每个城市与之相邻城市的表,它将帮助我们决定如何继承父代中的路径结构。对于每个父代中的每个城市,我们记录其左邻和右邻的城市。
#create an edge table
edge_table = {key: set() for key in parent_1_stripped} #parent_1_stripped 是一个可以迭代的对象(例如列表、字符串等),它的每一个元素将成为 edge_table 字典的键。
#{key: set() for key in parent_1_stripped} 是一个字典推导式(dictionary comprehension),它的作用是:
#遍历 parent_1_stripped 中的每个 key。
#为每个 key 生成一个对应的空集合 set()。# Fill edge table from parent 1
for i in range(len(parent_1_stripped)): # 遍历整个列表city = parent_1_stripped[i] left_neighbor = parent_1_stripped[i-1]right_neighbor = parent_1_stripped[(i+1) % len(parent_1_stripped)] # 此处取余是防止右邻居的索引值超出边界,若超出则环形edge_table[city].update([left_neighbor, right_neighbor])# Fill edge table from parent 2
for i in range(len(parent_2_stripped)):city = parent_2_stripped[i]left_neighbor = parent_2_stripped[i-1]right_neighbor = parent_2_stripped[(i+1) % len(parent_2_stripped)]edge_table[city].update([left_neighbor, right_neighbor])
使用集合的 update()
方法时,它会将这个列表视为一个可迭代对象,并将其所有元素添加到集合中。例如我们update了一个[4,5]到 my_set={1,2,3} 中,那么集合将会被更新为 {1, 2, 3, 4, 5} 因为集合 my_set
会自动处理重复元素,所以即使列表中包含重复的元素,它们也不会在集合中重复出现 注意Update()的传入参数必须是可以迭代的对象如list等等,否则会报TypeError
3. 生成孩子
我们从一个随机城市开始,选择有最少剩余边缘的城市,直到生成完整的路径。如果没有相邻城市可选,则从未访问过的城市中随机选择一个。
#Start with a random city:current = random.choice(parent_1_stripped) # 从列表中随机选择一个城市作为起点child = [current] # 子列表为仅有当前城市的列表#until we build the entire child:while len(child) < len(parent_1_stripped): # 重复操作直到子列表和原列表长度相同#remove the current city from the others' adjacency lists#WRITE YOUR CODE HERE!for cities in edge_table.values():cities.discard(current)#在交叉过程中,每次选择一个城市后,我们需要将这个城市从所有其他城市的邻接表中移除,#确保它不会被重复选择。edge_table 是一个记录每个城市邻接城市的表,#通过 discard() 方法,我们从每个城市的邻接表中删除当前城市 current。#if current city has neighbors, choose the one with the fewest connections left - this helps priroritizing a structure that preserves the parents' structures.#检查当前城市 current 是否还有剩余的邻接城市。如果 edge_table[current] 还有邻居,则继续执行。if edge_table[current]:next_city = min(edge_table[current], key=lambda city: len(edge_table[city]))#if no neighbors left, choose a random unvisited city#WRITE YOUR CODE HERE!else:#如果当前城市没有剩余的邻接城市(即邻接表为空),则需要随机选择一个未访问的城市。other_city = list(set(parent_1_stripped) - set(child)) #从亲代减去已经访问过的城市列表来得到剩下的城市next_city = random.choice(other_city)#将选择的下一个城市 next_city 添加到孩子路径中child.append(next_city)#更新当前城市为刚刚选择的下一个城市,准备下一次循环。current = next_city
补充:
- 为什么使用Discard()而不是remove()?
discard()
是集合(set
)的一个方法,用于从集合中移除指定的元素。如果使用remove()
,而被移除的城市不在集合中,会抛出KeyError
异常。相比之下,discard()
如果发现当前城市不在集合中,它不会报错,这让代码更健壮,可以在不同情况下正常运行。 - 关于lambda函数
lambda arguments: expression lambda:关键字,用于定义一个匿名函数。 arguments:函数的参数,可以有多个,用逗号分隔。 expression:函数的返回值,是一个表达式,不能是语句
# 普通函数定义
def square(x):return x ** 2# 等价的 Lambda 函数
square_lambda = lambda x: x ** 2
# 使用 Lambda 函数
result = square_lambda(4) # 16
Lambda 函数经常用作其他函数(如 sorted()
、map()
、filter()
)的参数。例如,在 sorted()
函数中用 lambda 作为排序的键
在本例中,lambda 函数用于 min()
函数的 key
参数,以确定 edge_table
中每个城市的邻接城市数量。具体来说:
next_city = min(edge_table[current], key=lambda city: len(edge_table[city]))
传入的city从 edge_table[current]中取,然后min()
函数使用这个 lambda 函数来找到邻接城市中拥有最少邻接城市的城市。
*在 min()
函数中使用 lambda 函数可以方便地按特定的标准(如邻接城市数量)来选择最小值,帮助实现复杂的逻辑操作。
4. 处理结果
最后,别忘了将 0
添加回路径的起点和终点,确保孩子个体符合旅行商问题的要求。
# Add zeros back (starting and ending city is Amsterdam)
child = [0] + child + [0]
5. 调用交叉操作
当你调用 crossover
时,它将生成两个新孩子。
def crossover(parent_1: list, parent_2: list, p_crossover:float) -> tuple:"""Performs the Edge Recombination crossover twice on the same pair of parents, returns a pair of children."""if np.random.uniform() > p_crossover:# Do not perform crossover uniform用于生成均匀分布的随机数 默认区间是[0,1) 当小于超参数p_crossover时 则不发生变异return parent_1, parent_2else:# Create two childrenchild_1 = create_offrping_edge_recombination(parent_1, parent_2)child_2 = create_offrping_edge_recombination(parent_1, parent_2)return child_1, child_2
在实现了初始化种群函数(initialize_population)、适应度函数(compute_distance)、突变操作(变异mutation())、父代选择函数(fittest_solution_TSP)和生存选择函数后。我们将使用锦标赛选拔,并实施代际生存机制,即所有孩子都取代父母。
def tournament_selection(generation: list,compute_distance, adjacency_mat: np.ndarray, k: int) -> int:'''Implements the tournament selection algorithm.It draws randomly with replacement k individuals and returns the index of the fittest individual.''''''ToDo:Complete the building blocks below!'''current_winner = None # 初始化当前胜者winner_distance = float('inf')# 初始化胜者的距离为无穷大,表示我们要找最小的# 从代际中取样k个样本, 分为带放回抽样和不带放回抽样#chosen_individuals = np.random.choice(len(generation),size = k,replace = False) # Sampling without replacementchosen_individuals = np.random.choice(len(generation),size = k,replace = True) # Sampling with replacementfor individuals in chosen_individuals:current_distance = compute_distance(generation[individuals],adjacency_mat)if current_distance < winner_distance:winner_distance = current_distancecurrent_winner = individualsreturn current_winner