• 【遗传算法】求解TSP问题


    本专栏将会重点围绕车辆路径规划问题进行讲解,会涵盖车辆路径规划中的很多常见问题,如TSP问题,CVRP问题,VRPTW问题,VRPCTW-MultiDepot问题,EVRPTW问题,PDVRP问题,Mul-ObjVRP问题,M2-2E-VRP双层网络模型,将会逐步为大家进行梳理与整理。本将小节内容将会讲解第一个问题:TSP问题。

    前言:在【01路径规划问题的相关理论】这一小节的内容中,有讲述到TSP问题是车辆路径规划问题的基础模型,我们在系统性的学习VRP问题时,首先来讲解TSP问题,文章将会包含TSP问题的各种算法,会从不同的维度、利用不同的算法来讲解,TSP问题后续也会整理成一个系列文章,分别会从遗传算法、粒子群优化算法、模拟退火算法、蚁群算法、分支定界法以及动态规划等不同算法维度来讲解,目的在于:

    ①为大家铺垫不同的算法模型,为后续更多更复杂的规划问题打基础;

    ②后续将不会重点介绍算法的理论框架,会直接根据问题给出解决的代码模型。

    1、TSP问题介绍

            根据百度百科的解释:

            旅行商问题,即TSP问题(Traveling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

     2、遗传算法介绍

            我们系列文章将会轻理论,重实践,重代码,不会和你长篇大论关于算法的原理内容,若想比较详细的了解关于原理内容,请自行百度学习。

            遗传算法(Genetic Algorithm,GA)起源于达尔文进化论,是一种随机全局搜索优化方法,模拟了自然选择和遗传中发生的复制交叉(crossover)和变异(mutation)等现象,从任一初始种群出发,通过随机选择、交叉和变异操作,产生一群更适合环境的个体,通过一代一代的进化,最后收敛到一群最适应环境的个体,从而求得问题的优质解的一种方法。

    在执行遗传算法前,有两个东西必须提前实现设定好;

    1. 可行解的表示方法,即编码方式,目前路径规划领域中常用的编码方式为实数编码,除此以外,还有二进制编码、浮点编码等,这些不做为我们的重点。
    2. 适应度函数,也就是计算子代适应度的函数,这个函数一般是目标函数,如要最大化某个函数,则将这个函数作为目标函数即可,反之最小化的时候加个负号即可。

    遗传算法是一个迭代算法,在进行初始化后按照 “选择,交叉,变异” 的顺序进行迭代,当满足设定的结束条件(例如达到一定的代数),便将得到的解作为问题的解。其流程如下所示 :
         

     说明:案例中的TSP问题,我们不需要过多的纠结说,车辆有无限制,编码后如何解码的问题,这可能是初学者对这个算法模型最大的疑问,我们后续在讲解VRP问题时,会提及到车辆限制以及遗传算法中如何解码的问题,其中我相信绝大数同学对于整个算法理解不会有问题,但就一直没有弄明白整数编码的情况下,求出的线路,算法是如何解码的。

    我们得明白这样一个问题:

    旅行商问题TSP与装箱问题Packing problem混合问题=车辆路径规划问题VRP

    3、代码实验

    3.1、随即初始化

    1. def random_init(self, num_total, num_city):
    2. tmp = [x for x in range(num_city)]
    3. result = []
    4. for i in range(num_total):
    5. random.shuffle(tmp)
    6. result.append(tmp.copy())
    7. return result

     随即初始化一个个体的方法。其中通过rang函数生成一个所有城市节点数量的序列,然后使用shuffle函数将这个序列打乱,就是我们模拟的一种随即路线。

    3.2、贪心初始化

    1. def greedy_init(self, dis_mat, num_total, num_city):
    2. start_index = 0
    3. result = []
    4. for i in range(num_total):
    5. rest = [x for x in range(0, num_city)]
    6. # 所有起始点都已经生成了
    7. if start_index >= num_city:
    8. start_index = np.random.randint(0, num_city)
    9. result.append(result[start_index].copy())
    10. continue
    11. current = start_index
    12. rest.remove(current)
    13. # 找到一条最近邻路径
    14. result_one = [current]
    15. while len(rest) != 0:
    16. tmp_min = math.inf
    17. tmp_choose = -1
    18. for x in rest:
    19. if dis_mat[current][x] < tmp_min:
    20. tmp_min = dis_mat[current][x]
    21. tmp_choose = x
    22. current = tmp_choose
    23. result_one.append(tmp_choose)
    24. rest.remove(tmp_choose)
    25. result.append(result_one)
    26. start_index += 1
    27. return result

     3.3、计算路径长度

    1. # 计算路径长度
    2. def compute_pathlen(self, path, dis_mat):
    3. try:
    4. a = path[0]
    5. b = path[-1]
    6. except:
    7. import pdb
    8. pdb.set_trace()
    9. result = dis_mat[a][b]
    10. for i in range(len(path) - 1):
    11. a = path[i]
    12. b = path[i + 1]
    13. result += dis_mat[a][b]
    14. return result

     3.4、计算种群的适应度

    1. # 计算种群适应度
    2. def compute_adp(self, fruits):
    3. adp = []
    4. for fruit in fruits:
    5. if isinstance(fruit, int):
    6. import pdb
    7. pdb.set_trace()
    8. length = self.compute_pathlen(fruit, self.dis_mat)
    9. adp.append(1.0 / length)
    10. return np.array(adp)

    3.5、选择操作

    1. def swap_part(self, list1, list2):
    2. index = len(list1)
    3. list = list1 + list2
    4. list = list[::-1]
    5. return list[:index], list[index:]

    3.6、交叉操作

    1. def ga_cross(self, x, y):
    2. len_ = len(x)
    3. assert len(x) == len(y)
    4. path_list = [t for t in range(len_)]
    5. order = list(random.sample(path_list, 2))
    6. order.sort()
    7. start, end = order
    8. # 找到冲突点并存下他们的下标,x中存储的是y中的下标,y中存储x与它冲突的下标
    9. tmp = x[start:end]
    10. x_conflict_index = []
    11. for sub in tmp:
    12. index = y.index(sub)
    13. if not (index >= start and index < end):
    14. x_conflict_index.append(index)
    15. y_confict_index = []
    16. tmp = y[start:end]
    17. for sub in tmp:
    18. index = x.index(sub)
    19. if not (index >= start and index < end):
    20. y_confict_index.append(index)
    21. assert len(x_conflict_index) == len(y_confict_index)
    22. # 交叉
    23. tmp = x[start:end].copy()
    24. x[start:end] = y[start:end]
    25. y[start:end] = tmp
    26. # 解决冲突
    27. for index in range(len(x_conflict_index)):
    28. i = x_conflict_index[index]
    29. j = y_confict_index[index]
    30. y[i], x[j] = x[j], y[i]
    31. assert len(set(x)) == len_ and len(set(y)) == len_
    32. return list(x), list(y)

    3.7、变异操作

    1. def ga_mutate(self, gene):
    2. path_list = [t for t in range(len(gene))]
    3. order = list(random.sample(path_list, 2))
    4. start, end = min(order), max(order)
    5. tmp = gene[start:end]
    6. # np.random.shuffle(tmp)
    7. tmp = tmp[::-1]
    8. gene[start:end] = tmp
    9. return list(gene)

     3.8、程序执行

    1. def run(self):
    2. BEST_LIST = None
    3. best_score = -math.inf
    4. self.best_record = []
    5. for i in range(1, self.iteration + 1):
    6. tmp_best_one, tmp_best_score = self.ga()
    7. self.iter_x.append(i)
    8. self.iter_y.append(1. / tmp_best_score)
    9. if tmp_best_score > best_score:
    10. best_score = tmp_best_score
    11. BEST_LIST = tmp_best_one
    12. self.best_record.append(1./best_score)
    13. print(i,1./best_score)
    14. print(1./best_score)
    15. return self.location[BEST_LIST], 1. / best_score

    3.9、图形展示

    1. fig, axs = plt.subplots(2, 1, sharex=False, sharey=False)
    2. axs[0].scatter(Best_path[:, 0], Best_path[:,1])
    3. Best_path = np.vstack([Best_path, Best_path[0]])
    4. axs[0].plot(Best_path[:, 0], Best_path[:, 1])
    5. axs[0].set_title('规划结果')
    6. iterations = range(model.iteration)
    7. best_record = model.best_record
    8. axs[1].plot(iterations, best_record)
    9. axs[1].set_title('收敛曲线')
    10. plt.show()

     最终运行的结果如下:

    模型收敛图如下:

     

    若读者朋友需要全部的案例数据以及完整的代码,可以自行点击案例链接下载,但其实如果您能看懂上述的代码结构,我相信不用作者提供完整的代码也是可以自行进行测试完成的,也希望朋友们在实在没办法的情况下,才去下载完整资源,这样的学习效果会最好。

  • 相关阅读:
    mindspore-RuntimeError: mindspore/ccsrc/backend/session/ascend_session.cc:
    【华为机试真题 JAVA】寻找相同子串-100
    PTA JAVA02 基础语法1
    python网页爬虫接口和常见反扒
    单元测试与自测
    python 学生编程--3 多彩同心圆
    如何在响应头中防治xss
    《QT实用小工具·六十九》基于QT开发的五子棋AI游戏
    Android中使用Java操作List集合的方法合集,包括判读是否有重复元素等
    Go语言的Json序列化与反序列化、Goto语法、Tcp Socket通信
  • 原文地址:https://blog.csdn.net/qq_20412595/article/details/125469643