目录

0. 前言

1. 基本LK算法

1.1 基本步骤

1.2 相关知识点

1.3 算法的伪代码

1.4 算法需要满足的准则

1. 准则一(顺序交换准则)

2. 准则二(可行性准则)

3. 准则三(正增益准则)

4. 准则四(不相交准则)

1.5 基本LK算法的改进

5. 准则五

6. 准则六

7. 准则七

8. 准则八

9. 准则九

2. LKH算法

3. Python实现


0. 前言

有关算法的详细信息参考下面这两个网址:

Extreme Algorithmshttp://www.seas.gwu.edu/~simhaweb/champalg/tsp/tsp.html

LKH (Keld Helsgaun)http://www.akira.ruc.dk/~keld/research/LKH/

1. 基本LK算法

LK算法是基于交换转换的实质性概括。假设T是一个非最优但可行的解决方案。那么我们可以说T不是最优的,因为T中有k个元素  是“不合适”;为了使T最优,它们应该被 S-T 的k元素 代替,问题只是识别 k 和 x 和 y 。

由于我们不知道k应该是什么,所以修复k然后考虑这个固定k的所有可能的T的k-subsets似乎是人为的。相反,我们尝试逐个元素找到 k 和  和  作为最好的。因此,我们将首先尝试将  和  识别为“最不合适”的对;然后,选择 和 并暂时搁置,我们发现  和  是其余集合中最不合适的一对;等等。

1.1 基本步骤

1.2 相关知识点

设  表示  和  的长度,定义

LK算法基于一个重要的simple fact:如果一系列序列具有正和,则这些序列存在循环排列,使得每个部分和都是正的。

证明:

设 k 是 使得最小的索引,则

特别是,由于我们正在寻找具有正和的  序列,我们只需要考虑部分和总是正的增益序列。这种增益标准使我们能够大大减少我们需要检查的序列数量;这是算法停止规则的核心。

1.3 算法的伪代码

算法的有关细节如下:

Step1. 随机生成一个初始旅行  ,即随机生成一个可行解。

Step2.  用于记录交换边的数量, 为在当前  状态下,未选择的一个城市节点。

Step3. 在  中选择一条边  。当  确定时, 有两个选择,即  也仅有两个选择。两个选择都应该进行判断,寻找潜在的最优解。

Step4. 当  确定时,则  的一个端点确定,此时 不应该等于 ,此时选择的  应当保证, 如果没有满足条件的应当尝试下一个,如果为当前 下最后一个满足条件的 则尝试下一个  。

Step5. 令  ,进行下一组边的尝试。

Step6. 对于给定的  , 有两种选择,但是两种选择中仅有一种可以闭合整个旅行,其中一个无法闭合,仅当选择 ,即 选择 时,才允许不闭合整个旅行的存在,这可以增大获得全局最优解的可能性。 ≠  ,.  意味着 不能是先前已经断开的边。

Step7. 在 确定时,则 的  已经确定,此时需要确定,且此时 的存在必须确保  也要存在, 存在的条件参考Step6中的(a)、(b)。如果没有满足条件的则更换进行尝试,如果有满足条件的则继续增加交换的边,看是否可以继续增加。

Step8. 步骤8是对所有  的遍历

Step9. 步骤9是对所有  的遍历

Step10. 步骤10是对所有 的遍历

Step11. 步骤11是对所有 的遍历

Step12. 步骤12是对所有 的遍历

Step13. 停止条件。

1.4 算法需要满足的准则

1. 准则一(顺序交换准则)

 与分别共享一个节点。如果  是  其中一个节点,则当  时,,如下图所示

如图, 构成相邻连接链。

集合X与集合Y交换产生旅行的必要(但不充分)条件是连接链被关闭,即。这种交换称为顺序交换。

通常,可以通过适当编号受影响链路作为顺序交换来实现旅行的改进。但是,情况并非总是如此。下图显示了一个无法进行顺序交换的示例。

2. 准则二(可行性准则)

对于选择的 ,当  连接到  时,确保得到的结果是一个可行的旅行,这个准则用于 确保可以关闭旅行,形成一个闭环。

3. 准则三(正增益准则)

要求选择的 始终可以确保所提出的交换集合的增益  为正,假设  是  与的收益,则  。该准则在算法的效率中起着重要作用。

4. 准则四(不相交准则)

最后,要求集合X和Y是不相交的。这简化了编码,也减少了运行时间并提供了有效的停止标准。

1.5 基本LK算法的改进

为了限制搜索,Lin和Kernighan通过引入以下规则来改进算法:

5. 准则五

在寻找一个进入  的边时, 中的  被限制为最近的5个邻居。

6. 准则六

对于i≥4,如果  中的任何一个边  是少数(2-5个)可行解的共同环节,则该环节必须被打破。

7. 准则七

如果当前旅游与之前的解决方案旅游相同,则停止搜索。

规则5和6是启发式规则。它们是基于对哪些链接可能属于最优旅游的预期。它们可以节省运行时间,但有时是以不能获得最佳解决方案为代价的。

规则7也可以节省运行时间,但对找到的解决方案的质量没有影响。如果一个游程与之前的解决方案游程相同,那么试图进一步改进它就没有意义了。因此,检查是否有更多改进可能所需的时间(检查时间)可以被节省。根据Lin和Kernighan的说法,以这种方式节省的时间通常是运行时间的30%到50%。

除了这些主要是为了限制搜索的细化之外,Lin和Kernighan还增加了一些细化,其目的主要是为了指导搜索。在算法可以选择备选方案的情况下,启发式规则被用来给这些备选方案赋予优先权。在只必须选择其中一个替代方案的情况下,选择具有最高优先权的方案。在必须尝试几个备选方案的情况下,这些备选方案按优先级递减的顺序被尝试(使用回溯法)。更具体地说,使用了以下规则。

8. 准则八

当边  被选择时,每个可能的选择都被赋予优先级

9. 准则九

如果  有两种选择,则选择最高的一种。

2. LKH算法

待完成

3. Python实现

此实现仅仅是一个简单版本,还需要进一步优化代码结构

from random import choice
import math
import matplotlib.pyplot as pltdef get_distances(coordinates):"""根据城市坐标获取城市距离,返回关于城市距离的矩阵"""city_distance = []city_number_ = len(coordinates)for i in range(city_number_):city_i = []i_x, i_y = coordinates[i][0], coordinates[i][1]for j in range(city_number_):j_x, j_y = coordinates[j][0], coordinates[j][1]city_i.append(math.sqrt((i_x - j_x) ** 2 + (i_y - j_y) ** 2))city_distance.append(city_i)return city_distancedef get_matrix(city_number_, file_path, row):"""读取.tsp文件,并返回距离矩阵"""f = open(file_path, "r")  # 以只读模式打开文件lines = f.readlines()  # 获取所有行并保存到lines列表中datas = []  # 保存数据for line in lines[row - 1:-1]:  # 读取指定的所有行line.strip('\n')  # 去掉换行符datas.extend(line.split())  # 去掉空格f.close()  # 关闭文件dis_matrix = []# 先获取下三角矩阵line = []for data in datas:value = int(data)line.append(value)if value == 0:dis_matrix.append(line[:])line = []# 再对下三角矩阵进行修改for i in range(city_number_):for j in range(i + 1, city_number_):dis_matrix[i].append(dis_matrix[j][i])return dis_matrixdef initialize(number):"""生成初始解,number为城市数量"""# city_list = [v for v in range(number)]# path = []# for i in range(number):#     city = choice(city_list)#     city_list.remove(city)#     path.append(city)path = list(range(number))return pathdef get_path_fitness(path):"""计算路径path的距离"""path_distance = distance_matrix[path[0]][path[-1]]for i in range(len(path) - 1):path_distance += distance_matrix[path[i]][path[i + 1]]return path_distancedef run_algorithm(N):"""执行算法,N为算法运行次数"""iterate_result = []  # 每次迭代的结果best_value = 99999best_path = []for n in range(N):print(f"========================执行第{n}次==========================")path = LK()fitness = get_path_fitness(path)iterate_result.append(fitness)if fitness < best_value:best_path = pathbest_value = fitnessreturn best_value, best_path, iterate_resultdef transform_path(path):"""将基于位置表示的路径转化为基于边的表示的路径"""T = []for i in range(len(path) - 1):T.append((path[i], path[i + 1]))T.append((path[-1], path[0]))return Tdef transform_T(T):"""将基于边表示的路径转化为基于位置表示的路径"""path = [T[0][0], T[0][1]]length = len(T)del T[0]for i in range(length - 2):for t in T:if t[0] == path[-1]:path.append(t[1])T.remove(t)breakelif t[1] == path[-1]:path.append(t[0])T.remove(t)breakreturn pathdef LK():"""LK算法"""p = False# 初始化路径path = initialize(city_number)T = transform_path(path)n = 0# 第一层循环,遍历所有的t1(注意,当T更改时需要t1要重新开始循环)while n < city_number:t1 = nx1_list = get_x1_list(t1, T)# 第二层循环,遍历t1对应的所有x1,实际x1最多两种可能性for x1 in x1_list:t2 = (set(x1) - {t1}).pop()# 第三层循环,遍历x1对应的所有y1y1_list = get_y1_list(t2, x1, T)if not y1_list:  # 没有满足条件的y1,进行下一个x1判断print(f"x1={x1}时没有满足条件的y1 ")continuefor y1 in y1_list:# 第四层循环,遍历y1对应的所有x2,其中x2最多两种可能性,其中一种选择无法构成回路p, x2_list = get_x2_list(x1, y1, T)if p:  # 已存在较优的回路,更改T重新选择t1breakfor x2 in x2_list:# 第五层循环,遍历x2对应的所有y2y2_list = get_y2_list(y1, [x1, x2], T)if y2_list:  # 存在使增益增加的y2,且x3存在for y2 in y2_list:# 进入r_opt搜索print(f"===========================开始r_opt=======================")print(f"||t1={t1}||x1={x1}||y1={y1}||x2={x2}||y2={y2}")p, p1, T = r_opt(T, [x1, x2], [y1, y2])print("===========================结束r_opt=======================")if p:breakif p1:  # 进入下一个y2的尝试continueelse:  # 不存在满足条件的y2continue  # 直接进入下一循环,重新选择x2if p:breakif p:breakif p:n = 0breakif p:n = -1  # T被重置,因此需要t1也要重新开始p = Falseprint("存在改进,重置t1起点")n += 1return transform_T(T)def r_opt(T, tried_x, tried_y):"""对T进行r_opt优化,tried_x=[x1,x2],tried_y=[y1,y2]"""# 当y2=(t4,t5)确定时,x3则只有两种选择包含t4或者t5# 寻找所有的x3p = False  # 寻找下一个t1的标志p1 = False  # 寻找下一个y2的标志i = 2while True:i += 1print(f"进行第{i}对边搜索")# 选择xip6, xi, T = choose_xi(T, tried_x, tried_y)  # 进入此步意味着x(i+1)必然存在,但不一定使T更好if p6:  # T可以被改善,且在choose_xi()函数中已经对T进行了修改p = Truebreak# 选择yitried_x.append(xi)p7, yi = choose_yi(T, tried_x, tried_y)if not p7:  # 没有满足条件的yi,尝试下一个y2p1 = Truebreaktried_y.append(yi)return p, p1, Tdef choose_xi(T, tried_x, tried_y):"""如果选择的t_2i连接t1后,构成新回路的收益大于旧回路收益则更改旧回路返回True"""# yi和xi+1必须共享一个端点,即yi_1与xi也必须共享一个端点# yi=(t2i,t2i+1)和xi+1=(t2i+1,t2i+2)t1 = (set(tried_x[0]) - set(tried_y[0])).pop()t2i_1 = list(set(tried_y[-1]) - set(tried_x[-1]))[0]xi = (0, 0)p = Falsefor t in T:  # xi为二选一t2i = (set(t) - {t2i_1}).pop()if (t2i_1 in t) and (t not in tried_y) and (t not in tried_x) and (t2i != t1):yi = (t1, t2i)X = tried_x[:]X.append(t)Y = tried_y[:]Y.append(yi)if judge_swap(T, X, Y):  # 可以进行边的交换xi = tif judge_fitness(X, Y):T = list((set(T) - set(X)) | set(Y))print(f"xi存在改进,改进后的T为\n{T}")p = Truebreakreturn p, xi, Tdef choose_yi(T, tried_x, tried_y):"""尝试寻找满足条件的yi,如果找到则返回yi和True"""# xi = (t2i-1,t2i)# yi = (t2i,t2i+1)# 当xi确定时,yi的某个端点则确定t1 = (set(tried_x[0]) - set(tried_y[0])).pop()t2i = (set(tried_x[-1]) - set(tried_y[-1])).pop()for city in range(city_number):  # 遍历所有城市寻找满足条件的yiyi = (t2i, city)X = tried_x[:]Y = tried_y[:]Y.append(yi)G_i = judge_fitness(X, Y)  # 收益值if (G_i > 0) and (yi not in T) and ((city, t2i) not in T) and (city != t2i) and (city != t1):# 接下来判断xi+1是否存在,xi+1=(city,t_)for x in T:if city in x:t_ = (set(x) - {city}).pop()X = tried_x[:]X.append(x)Y = tried_y[:]Y.extend([yi, (t_, t1)])if judge_swap(T, X, Y) and (x not in tried_x):return True, yireturn False, (0, 0)def judge_swap(T, X, Y):"""判断交换能能否构成闭环,可以则返回True"""T_ = (set(T) - set(X)) | set(Y)t = 0i = 0while i <= len(T):for x in T_:if t in x:i += 1x_list = list(x)x_list.remove(t)t = x_list[0]if (t == 0) and (i < city_number - 1):return Falsereturn Truedef judge_fitness(X, Y):p = Falsefitness = 0for i in range(len(X)):fitness += distance_matrix[X[i][0]][X[i][1]] - distance_matrix[Y[i][0]][Y[i][1]]if fitness > 0:p = Truereturn pdef get_T_fitness(T):"""获取T的路径距离"""distance = 0for t in T:distance += distance_matrix[t[0]] + distance_matrix[t[1]]return distancedef get_x1_list(t1, T):"""获取t1对应的所有可能的x1"""x1_list = []for v in T:if t1 in v:x1_list.append(v)return x1_listdef get_y1_list(t2, x1, T):"""获取在x1确定下的所有的y1"""y1_list = []for city in range(city_number):G1 = distance_matrix[x1[0]][x1[1]] - distance_matrix[t2][city]if ((t2, city) not in T) and ((city, t2) not in T) and (G1 > 0) and (t2 != city):y1_list.append((t2, city))return y1_listdef get_x2_list(x1, y1, T):"""x2有两种选择,只有一种可以形成回路"""t1 = (set(x1) - set(y1)).pop()p = Falsex2_list = []t2 = (set(x1) - {t1}).pop()t3 = (set(y1) - {t2}).pop()for x in T:if (t3 in x) and (t1 not in x):x2_list.append(x)t4 = (set(x) - {t3}).pop()X = [x1, x]Y = [y1, (t4, t1)]if judge_swap(T, X, Y) and (judge_fitness(X, Y)):print("!!!")T.remove(x)T.remove(x1)T.extend(Y)print(f"在x2处可改进,改进后的T为:\n{T}")p = Truebreakreturn p, x2_listdef get_y2_list(y1, tried_x, T):"""尝试寻找满足条件的y2,如果找到则返回所有y2列表"""t1 = (set(tried_x[0]) - set(y1)).pop()t4 = (set(tried_x[1]) - set(y1)).pop()y2_list = []# 遍历所有城市寻找满足条件的y2for city in range(city_number):y2 = (t4, city)G_i = judge_fitness(tried_x, [y1, y2])  # 收益值if (G_i > 0) and (y2 not in T) and ((city, t4) not in T) and (t4 != city) and (city != t1):# 接下来判断x3是否存在,x3=(city,t6)for x in T:if city in x:t6 = (set(x) - {city}).pop()X = tried_x[:]X.append(x)Y = [y1, y2, (t6, t1)]if judge_swap(T, X, Y):y2_list.append(y2)return y2_listdef get_neighbors(n):"""获取所有城市的前n个邻居"""neighbors = {}for i in range(len(distance_matrix)):temp_list = sorted(distance_matrix[i])neighbors[i] = [distance_matrix[i].index(temp_list[v + 1]) for v in range(n)]return neighborsif __name__ == '__main__':# 城市距离矩阵# distance_matrix = [#     [0, 64, 378, 519, 434, 200],#     [64, 0, 270, 455, 375, 164],#     [378, 270, 0, 70, 265, 344],#     [519, 455, 70, 0, 223, 428],#     [434, 375, 265, 223, 0, 173],#     [200, 164, 344, 428, 173, 0]# ]  # 6个城市,最优1248distance_matrix = get_matrix(24, "gr24.tsp", 8)city_number = len(distance_matrix)Run_Number = 1  # 迭代次数print("开始执行算法!!!")result = list(run_algorithm(Run_Number))  # 执行算法print("=======================================================")print(f"最优值为:{result[0]},对应路径为:《{result[1]}》")# a = judge_swap([(6, 7), (9, 1), (11, 8), (4, 14), (13, 5), (13, 7), (15, 8), (14, 0), (9, 4), (12, 16), (1, 10),#                 (5, 2), (6, 16), (12, 3), (10, 2), (11, 3), (15, 0)], [(6, 7)], [(6, 7)])# print(a)

目前仅实现基本的LK算法

接下来将进行LKH算法的实现

参考文献:

[1] Helsgaun K . An effective implementation of the Lin–Kernighan traveling salesman heuristic[J]. European Journal of Operational Research, 2000, 126(1):106-130.

国内网站有关该算法介绍的文章见下面的网址:

TSP 问题的 LKH 方法介绍_xlh9718的博客-CSDN博客_lkh算法

LKH求解TSP_yang_rookie的博客-CSDN博客_lkh算法

请问有谁了解旅行商问题的LKH算法?可以交流一下吗? - 知乎

tsp的理论和实践(9)LK简介 - 掘金

LK算法、LKH算法介绍及Python实现相关推荐

  1. KMP算法、AC自动机算法的原理介绍以及Python实现

    KMP算法 要弄懂AC自动机算法,首先弄清楚KMP算法. 这篇文章讲的很好: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E ...

  2. KMeans算法实现步骤介绍及Python代码

    文章目录 一.KMeans算法的步骤 二.KMeans实现过程中需要注意的地方 1.初始聚类中心的确定 2. 常用的距离度量 3. 聚类效果的衡量 SSE 4.迭代结束条件 5.空簇的处理 三.结果展 ...

  3. 【LKH算法体验】Python调用LKH算法求TSP问题

    [LKH算法体验]Python调用LKH算法求TSP问题 一.LKH算法简介 Keld Helsgaun 是丹麦 罗斯基勒大学计算机科学专业的名誉副教授. 他于 1973 年在 哥本哈根大学获得DIK ...

  4. 排序算法基本介绍及python实现(含详细注释)

    对数组排序可以说是编程基础中的基础,本文对八种排序方法做简要介绍并用python实现. 代码中注释很全,适合复习和萌新学习.这是刚入学自己写的,可能难免比不上标准的写法,但是懒得改了. 文末会放和排序 ...

  5. python比较两个列表的重合度_#源代码#超几何分布算法介绍及python下的实现代码...

    原标题:#源代码#超几何分布算法介绍及python下的实现代码 超几何分布是统计学上一种离散概率分布.它描述了由有限个物件中抽出n个物件,成功抽出指定种类的物件的次数(不归还). 在产品质量的不放回抽 ...

  6. python数据结构与算法分析 第2版_题库 | 百度数据结构 / 算法面试题型介绍及解析 第 2 期...

    题目1:分解成质因数 (如 435234=251*17*17*3*2) void prim(int m, int n){ if(m>n){ while(m%n != 0) n++; m /= n ...

  7. SLIC算法介绍与Python实现

    SLIC(simple linear iterative clustering)算法介绍与Python实现 本文转载自:https://blog.csdn.net/shinian1987/articl ...

  8. 关联规则算法——Apriori算法解析及Python实现

    文章目录 关联规则挖掘过程 Apriori算法 1. Apriori算法的基本思想 2. Apriori算法产生频繁项集的过程 3. Apriori算法的主要步骤 4. 举例及代码实现 关联规则挖掘过 ...

  9. python算法书籍-有哪些用 Python 语言讲算法和数据结构的书?

    1.Python数据结构篇 数据结构篇主要是阅读[Problem Solving with Python](Welcome to Problem Solving with Algorithms and ...

最新文章

  1. Bug tracker .net 部署经验(完善中)
  2. 计算机应用房地产,房地产板块弱市吸金 逾30亿撤离计算机应用板块
  3. 3.12 总结-深度学习-Stanford吴恩达教授
  4. oracle executesqlcommand,Oracle Execute Command Sql Script in Win
  5. 详解Java中Map用法
  6. 均值滤波 中值滤波 高斯平滑滤波
  7. sqlserver如何定义一个静态变量_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库...
  8. 软工作业4:词频统计
  9. 学习 服务器部署 hello world
  10. Hive查看表的分区字段
  11. [纯技术讨论]从12306谈海量事务高速处理系统
  12. winform倒计时
  13. 生活中计算机自动控制原理的应用,《自动控制原理》虚拟实验系统在教学中的应用...
  14. JavaScript实现按钮点击上/下一张切换图片
  15. 对报表.FRX文件的全面分析
  16. 计划行为理论(TPB,Theory of Planned Behavior)
  17. 什么叫股指期货,股指期货交易流程是什么
  18. 写商业计划书的十个要点
  19. ios应用跳转小程序Universal Links链接验证不通过
  20. python win32api键盘_pythonwin32api键盘输入教程

热门文章

  1. easyui实现鼠标移到某一行,就会弹出一个提示信息效果
  2. 智联招聘数据爬取准备(1)-智联招聘搜索列表源码解析
  3. 当了12年大学教师,跟大家聊聊嵌入式工程师,硬核单片机编程思想
  4. 写给扎克伯格的一封信:不要让我们失望!
  5. js实现移动端电子签名
  6. 新网站多久被google收录?谷歌当天收录网站靠谱吗?
  7. 中文语法纠错全国大赛获奖分享:基于多轮机制的中文语法纠错
  8. 国泰君安国际携手蔚来与宁德时代,助力新能源汽车电池产业创新发展做优、做强基础上突破发展,以金融支持实体经济
  9. 关于 MDD(度量驱动开发)的一些思考
  10. 走向破产的拜腾汽车,为何无人接盘?