39、TSP的几种精确求解方法(DFJ、MTZ、最短边破圈式、行生成式)和启发式方法(插入法、近邻法、2-opt、3-opt、模拟退火)

本文主要是介绍39、TSP的几种精确求解方法(DFJ、MTZ、最短边破圈式、行生成式)和启发式方法(插入法、近邻法、2-opt、3-opt、模拟退火),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0、定义

TSP(Traveling Salesman Problem),旅行商问题,又名旅行推销员问题、货郎担问题

假设一个旅行商要拜访n个城市,每个城市只能经过一次,且最后要回到起点,问如何走路程最短

1、整数模型

设cij为第i个点到第j个点的距离,xij表示是否从走到

1.1、DFJ整数模型

该模型较好理解,前面两个约束保证对每个点入度为1,出度也为1,第三个约束保证每个非空集合到其补集一定有边相连

def TSP_IP_DFJ(Dis, n, time_limit):model = Model("TSP_IP_DFJ")A = [[model.addVar(vtype="B", name="A[%s,%s]" % (i, j)) for j in range(n)] for i in range(n)]# 以总成本最小为目标model.setObjective(quicksum(Dis[i][j] * A[i][j] for i in range(n) for j in range(n)))#一度约束for i in range(n):model.addCons(quicksum(A[i][j] for j in range(n) if i != j) == 1)model.addCons(quicksum(A[j][i] for j in range(n) if i != j) == 1)#无子环约束SS = subset([i for i in range(n)])for S in SS:model.addCons(quicksum(A[i][j] for i in S for j in range(n) if j not in S) >= 1)# 设置求解时间model.setRealParam("limits/time", time_limit)model.optimize()print("\ngap:", model.getGap())A1 = [[round(model.getVal(A[i][j])) for j in range(n)] for i in range(n)]return A1

1.2、MTZ整数模型

两个模型区别在于无子环约束形式的不同,下面证明两者等价

证明

MTZ模型的好处在于其约束相较于DFJ明显变少(2^n到n^2)

def TSP_IP_MTZ(Dis, n, time_limit):model = Model("TSP_IP_MTZ")A = [[model.addVar(vtype="B", name="A[%s,%s]" % (i, j)) for j in range(n)] for i in range(n)]U = [model.addVar(name="U[%s]"%i) for i in range(n)]# 以总成本最小为目标model.setObjective(quicksum(Dis[i][j] * A[i][j] for i in range(n) for j in range(n)))# 一度约束for i in range(n):model.addCons(quicksum(A[i][j] for j in range(n) if i != j) == 1)model.addCons(quicksum(A[j][i] for j in range(n) if i != j) == 1)#MTZ无子环约束for i in range(1, n):for j in range(1, n):if i != j:model.addCons(U[i]-U[j]+(n-1)*A[i][j] <= n-2)# 设置求解时间model.setRealParam("limits/time", time_limit)model.optimize()print("\ngap:", model.getGap())A1 = [[round(model.getVal(A[i][j])) for j in range(n)] for i in range(n)]return A1

1.3、行生成模型

这里先解上面其松弛模型,再判定解是否满足条件,再不断添加不满足的约束,添加约束的方案一般为约束已生成的子环不会发生,下面列举一些方法

让其与补集有边相连,或让其内部连边不够,代码块里通过type区分(精确解)

1.3.1、多次调用模式

  • def sub_TSP_row_generate(Dis, n, L_S, time_limit, type=2):model = Model("sub_TSP_row_generate")A = [[model.addVar(vtype="B", name="A[%s,%s]" % (i, j)) for j in range(n)] for i in range(n)]# 以总成本最小为目标model.setObjective(quicksum(Dis[i][j] * A[i][j] for i in range(n) for j in range(n)))# 一度约束for i in range(n):model.addCons(quicksum(A[i][j] for j in range(n) if i != j) == 1)model.addCons(quicksum(A[j][i] for j in range(n) if i != j) == 1)#两个方向限制不生成已有的子环if type == 1:#该集合与补集有边相连for S in L_S:model.addCons(quicksum(A[i][j] for i in S for j in range(n) if j not in S) >= 1)model.addCons(quicksum(A[j][i] for i in S for j in range(n) if j not in S) >= 1)else:#该集合内部边不够for S in L_S:model.addCons(quicksum(A[i][j] for i in S for j in S if i != j) <= len(S)-1)# 设置求解时间model.setRealParam("limits/time", time_limit)model.optimize()print("\ngap:", model.getGap())A1 = [[round(model.getVal(A[i][j])) for j in range(n)] for i in range(n)]return A1def TSP_IP_row_generate(Dis, n, time_limit):L_S = []ring_sub = Truewhile ring_sub:ring_sub = FalseA = sub_TSP_row_generate(Dis, n, L_S, time_limit)D = {i:j for i in range(n) for j in range(n) if A[i][j] == 1}S = {0}a = 0while len(S) < n:b = D[a]if b in S:L_S.append(S)ring_sub = Truebreakelse:S.add(b)a = bif not ring_sub:return A

1.3.2、freeTransform模式

  • def LL_A_generate(A):LL_A = [[i] for i in range(len(A))]D = {i: i for i in range(len(A))}for i in range(len(A)):for j in range(len(A[i])):if A[i][j] == 1 and D[i] != D[j]:index_min = min(D[i], D[j])index_max = max(D[i], D[j])LL_A[index_min] = LL_A[index_min] + LL_A[index_max]del LL_A[index_max]for k in D:if D[k] > index_max:D[k] -= 1for k in LL_A[index_min]:D[k] = index_minreturn LL_Adef TSP_IP_exact(points, Dis, time_limit=1000, type=2):n = len(Dis)model = Model("TSP_IP_exact")model.hideOutput()A = [[model.addVar(ub=1, name="A[%s,%s]" % (i, j)) for j in range(n)] for i in range(n)]# 以总成本最小为目标model.setObjective(quicksum(Dis[i][j] * A[i][j] for i in range(n) for j in range(n) if i != j), "minimize")for i in range(n):for j in range(n):if i == j:model.addCons(A[i][j] == 0)else:model.addCons(A[i][j] <= 1)model.addCons(A[i][j] >= 0)for i in range(n):model.addCons(quicksum(A[i][j] for j in range(n) if i != j) == 1)model.addCons(quicksum(A[j][i] for j in range(n) if i != j) == 1)# 设置求解时间model.setRealParam("limits/time", time_limit)EPS = 1.e-6loop = 0isMIP = Falsewhile True:loop += 1print("精确求解第", loop, "次迭代")model.optimize()if not isMIP:A1 = [[1 if model.getVal(A[i][j]) > EPS else 0 for j in range(n)] for i in range(n)]else:A1 = [[round(model.getVal(A[i][j])) for j in range(n)] for i in range(n)]LL_A = LL_A_generate(A1)if loop % 10 == 0:A2 = [[A1[i][j] if i < j else 0 for j in range(n)] for i in range(n)]A_plot(points, A2, x_min, y_min, x_max, y_max)if len(LL_A) == 1:if not isMIP:print("转为MIP")model.freeTransform()for i in range(len(A)):for j in range(len(A[i])):model.chgVarType(A[i][j], "B")isMIP = Truecontinueelse:breakmodel.freeTransform()# 已测试过的不能在一起的点,避免再次得到if type == 1:for S in LL_A:model.addCons(quicksum(A[i][j] for i in S for j in range(n) if j not in S) >= 1)else:for S in LL_A:model.addCons(quicksum(A[i][j] for i in S for j in S if i != j) <= len(S) - 1)return A1

1.3.3、freeTransform和networkx模式

  • def solve_tsp(V, c, type=1):def addcut(cut_edges):G = networkx.Graph()G.add_edges_from(cut_edges)Components = list(networkx.connected_components(G))if len(Components) == 1:return Falsemodel.freeTransform()if type == 1:# 该集合与补集有边相连for S in Components:model.addCons(quicksum(x[i, j] for i in S for j in V if j not in S) >= 1)print("cut: %s 与其补集相连" % (S))else:# 该集合内部边不够for S in Components:model.addCons(quicksum(x[i, j] for i in S for j in S if j != i) <= len(S) - 1)print("cut: len(%s) <= %s" % (S, len(S) - 1))return Truedef addcut2(cut_edges):G = networkx.Graph()G.add_edges_from(cut_edges)Components = list(networkx.connected_components(G))if len(Components) == 1:return Falsemodel.freeTransform()for S in Components:T = set(V) - set(S)print("S:", S)print("T:", T)model.addCons(quicksum(x[i, j] for i in S for j in T if j > i) +quicksum(x[i, j] for i in T for j in S if j > i) >= 2)print("cut: %s >= 2" % "+".join([("x[%s,%s]" % (i, j)) for i in S for j in T if j > i]))return Truemodel = Model("tsp")x = {}for i in V:for j in V:x[i, j] = model.addVar(ub=1, name="x(%s,%s)" % (i, j))for i in V:model.addCons(quicksum(x[i, j] for j in V if j != i) == 1, "OutDegree(%s)" % i)model.addCons(quicksum(x[j, i] for j in V if j != i) == 1, "InDegree(%s)" % i)model.setObjective(quicksum(c[i, j] * x[i, j] for i in V for j in V if j != i), "minimize")EPS = 1.e-6isMIP = Falsewhile True:model.optimize()edges = []for (i, j) in x:if model.getVal(x[i, j]) > EPS:edges.append((i, j))if addcut(edges) == False:if isMIP:  # integer variables, components connected: solution foundbreakmodel.freeTransform()for (i, j) in x:  # all components connected, switch to integer modelmodel.chgVarType(x[i, j], "B")isMIP = Truereturn model.getObjVal(), edgesdef TSP_IP_exact_official(points, Dis):nums = len(points)V = range(1, nums + 1)c = {(i, j): Dis[i - 1][j - 1] for i in range(1, nums + 1) for j in range(1, nums + 1)}obj, edges = solve_tsp(V, c)A = [[0 for j in range(nums)] for i in range(nums)]for edge in edges:A[edge[0] - 1][edge[1] - 1] = 1return A

1.3.4、最短的边破除该子环(启发式,效果一般)

  • def sub_TSP_row_generate_heuristic(Dis, n, pairs_must, time_limit):model = Model("sub_TSP_row_generate_heuristic")A = [[model.addVar(vtype="B", name="A[%s,%s]" % (i, j)) for j in range(n)] for i in range(n)]# 以总成本最小为目标model.setObjective(quicksum(Dis[i][j] * A[i][j] for i in range(n) for j in range(n)))# 一度约束for i in range(n):model.addCons(quicksum(A[i][j] for j in range(n) if i != j) == 1)model.addCons(quicksum(A[j][i] for j in range(n) if i != j) == 1)#把要连的边连上for pair in pairs_must:i, j = pair[0], pair[1]model.addCons(A[i][j]+A[j][i] == 1)# 设置求解时间model.setRealParam("limits/time", time_limit)model.optimize()print("\ngap:", model.getGap())A1 = [[round(model.getVal(A[i][j])) for j in range(n)] for i in range(n)]return A1def TSP_IP_row_generate_heuristic(points, Dis, n, time_limit):pairs = pair_points_generate(Dis)pairs_must = set()ring_sub = Trueused = {i:0 for i in range(n)}while ring_sub:ring_sub = FalseA = sub_TSP_row_generate_heuristic(Dis, n, pairs_must, time_limit)D = {i:j for i in range(n) for j in range(n) if A[i][j] == 1}S = {0}a = 0while len(S) < n:b = D[a]#若生成子环,则将最短破圈边加入if b in S:ring_sub = Truefor pair in pairs:i, j = pair[0], pair[1]if ((i in S and j not in S) or (i not in S and j in S)) and used[i] <= 1 and used[j] <= 1:pairs_must.add((i, j))used[i] += 1used[j] += 1breakbreakelse:S.add(b)a = bA = deal_overlap(points, A)A = Undirected_to_directed(A)return A

2、近似算法

2.1、插入法

该方法很直观,从一个点开始,不断的把最近的邻居加进来

#近邻插入
def TSP_nearest(points, Dis):n = len(Dis)S = [0 for i in range(len(Dis))]T = [1 for i in range(len(Dis))]# 加入第一个点route = [0]S[0] = 1T[0] = 0# 开始加点while sum(T) > 0:u = route[-1]nexts = [i for i in range(n) if S[i] == 0]v = nexts[0]for j in range(1, len(nexts)):if Dis[u][v] > Dis[u][nexts[j]]:v = nexts[j]route.append(v)S[v] = 1T[v] = 0A = [[0 for j in range(n)] for i in range(n)]for i in range(len(route) - 1):A[route[i]][route[i + 1]] = 1A[route[i + 1]][route[i]] = 1A[route[0]][route[-1]] = 1A[route[-1]][route[0]] = 1A = deal_overlap(points, A)return A

2.2、凸包插入法

先把所有点的凸包算出来,然后把剩余点以损失最小的方式插入路径中

#先着凸包,再插入
def TSP_insert(points, Dis):# 每个坐标对应的索引D = {}for p in points:D[p[0]] = p[1:]ps = list(D.values())b = boundary_find(ps)route = []for p in b:idex = list(D.keys())[list(D.values()).index(p)]if idex not in route:route.append(idex)S = [i for i in range(len(points)) if i not in route]while len(S) > 0:costs = [[Dis[route[-1]][S[i]] + Dis[S[i]][route[0]] - Dis[route[-1]][route[0]]] + [Dis[route[j]][S[i]] + Dis[S[i]][route[j + 1]] - Dis[route[j]][route[j + 1]] for j in range(len(route) - 1)]for i in range(len(S))]new, idex = 0, 0for i in range(len(S)):for j in range(len(route)):if costs[i][j] < costs[new][idex]:new, idex = i, jroute.insert(idex, S[new])del S[new]n = len(points)A = [[0 for j in range(n)] for i in range(n)]for i in range(len(route) - 1):A[route[i]][route[i + 1]] = 1A[route[i + 1]][route[i]] = 1A[route[0]][route[-1]] = 1A[route[-1]][route[0]] = 1A = deal_overlap(points, A)return A

2.3、2-opt

随机初始化,不断的交换两个点的位置,更短则更新

#2-opt
def TSP_2_opt(points, Dis):route = [i for i in range(len(points))]n = len(points)flag = 1while flag == 1:flag = 0for i in range(n - 1):for j in range(i + 1, n):route1 = copy.deepcopy(route)route1[i], route1[j] = route1[j], route1[i]if sub_k_opt(route, Dis) > sub_k_opt(route1, Dis):route = route1flag = 1breakif flag == 1:breakA = [[0 for j in range(n)] for i in range(n)]for i in range(len(route) - 1):A[route[i]][route[i + 1]] = 1A[route[i + 1]][route[i]] = 1A[route[0]][route[-1]] = 1A[route[-1]][route[0]] = 1A = deal_overlap(points, A)return A

2.4、3-opt

在2-opt的基础上,多加入一个换的点

def TSP_3_opt(points, Dis):route = [i for i in range(len(points))]n = len(points)flag = 1while flag == 1:flag = 0for i in range(n - 2):for j in range(i + 1, n - 1):for k in range(j + 1, n):route1, route2, route3, route4, route5 = copy.deepcopy(route), copy.deepcopy(route), copy.deepcopy(route), copy.deepcopy(route), copy.deepcopy(route)route1[i], route1[j], route1[k] = route1[i], route1[k], route1[j]route2[i], route2[j], route2[k] = route2[j], route2[i], route2[k]route3[i], route3[j], route3[k] = route3[j], route3[k], route3[i]route4[i], route4[j], route4[k] = route4[k], route4[i], route4[j]route5[i], route5[j], route5[k] = route5[k], route5[j], route5[i]if sub_k_opt(route, Dis) > sub_k_opt(route1, Dis):route = route1flag = 1if sub_k_opt(route, Dis) > sub_k_opt(route2, Dis):route = route2flag = 1if sub_k_opt(route, Dis) > sub_k_opt(route3, Dis):route = route3flag = 1if sub_k_opt(route, Dis) > sub_k_opt(route4, Dis):route = route4flag = 1if sub_k_opt(route, Dis) > sub_k_opt(route5, Dis):route = route5flag = 1if flag == 1:breakif flag == 1:breakif flag == 1:breakA = [[0 for j in range(n)] for i in range(n)]for i in range(len(route) - 1):A[route[i]][route[i + 1]] = 1A[route[i + 1]][route[i]] = 1A[route[0]][route[-1]] = 1A[route[-1]][route[0]] = 1A = deal_overlap(points, A)return A

2.5、SA

引入模拟退火思想,一定程度接受质量不高的解

def TSP_SA(points, Dis):route = [i for i in range(len(points))]n = len(points)T = 1000 * sub_k_opt(route, Dis)T0 = 1alpha = 0.99flag = 0limit = n * math.log(n)loop = 0while flag < limit or T <= T0:loop += 1if loop % 10 == 0:print("模拟退火进行至:", loop)i = int(rd.random() * (n - 1))j = int(rd.random() * (n - i - 1)) + i + 1route1 = copy.deepcopy(route)route1[i], route1[j] = route1[j], route1[i]if sub_k_opt(route, Dis) > sub_k_opt(route1, Dis):route = route1T = T * alphaflag = 0else:dc = sub_k_opt(route1, Dis) - sub_k_opt(route, Dis)if math.exp(-dc / T) > rd.random():route = route1T = T * alphaflag = 0else:flag += 1A = [[0 for j in range(n)] for i in range(n)]for i in range(len(route) - 1):A[route[i]][route[i + 1]] = 1A[route[i + 1]][route[i]] = 1A[route[0]][route[-1]] = 1A[route[-1]][route[0]] = 1A = deal_overlap(points, A)return A

3、测试结果

3.1、各方法适用范围

方法

类型

适用规模

DFJ

精确解

0-15

MTZ

精确解

0-80

row_generate

精确解

0-20

row_generate_heuristic

近似解

0-100

exact

精确解

0-150

exact_official

精确解

0-150

nearest

近似解

0-

insert

近似解

0-

2-opt

近似解

0-

3-opt

近似解

0-

SA

近似解

0-

3.2、测试结果

这里采用公共数据集中有最优解的case测试,总体来说凸包插入法在求解效率(秒级别出结果)和求解精度(gap4.43%)上表现较好

测试集

最优解

近邻插入

gap

时间

凸包插入

gap

时间

avg8.44%3.344.43%3.29
gr9654490.0658973.968.23%0.3557667.755.83%0.26
rd1007910.409100.2615.04%0.278430.036.57%0.30
pcb44250783.5558982.0416.14%9.5456156.0110.58%10.91
lin10514383.0018027.8925.34%0.3714632.231.73%0.33
tsp2253859.004275.9310.80%2.024377.0713.42%1.94
gr666321086.46338140.025.31%53.76345173.007.50%50.96
ch1306110.866791.3111.14%0.566656.108.92%0.55
bays292020.002030.000.50%0.022044.001.19%0.03
berlin527544.378244.839.28%0.088069.846.97%0.09
eil51429.98455.946.04%0.06452.175.16%0.09
ulysses168182.208176.38-0.07%0.018179.44-0.03%0.01
kroD10021294.2923441.2310.08%0.4021676.431.79%0.25
gr20260350.2156023.71-7.17%1.3355704.01-7.70%1.51
ch1506532.287531.6315.30%0.766952.566.43%0.73
a2802586.772920.0512.88%2.892812.688.73%3.39
pr76108159.44115930.237.18%0.13113672.765.10%0.17
ulysses228354.998496.751.70%0.028306.20-0.58%0.02
kroC10020750.7623680.5114.12%0.2720871.380.58%0.28
eil76545.39578.426.06%0.13572.825.03%0.13
eil101642.31672.744.74%0.34671.684.57%0.28
att4810628.0011148.004.89%0.0610859.002.17%0.06
st70678.60734.538.24%0.13702.503.52%0.16
bayg291610.001708.006.09%0.031610.000.00%0.03

对比图

 

这篇关于39、TSP的几种精确求解方法(DFJ、MTZ、最短边破圈式、行生成式)和启发式方法(插入法、近邻法、2-opt、3-opt、模拟退火)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/336084

相关文章

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

C#中读取XML文件的四种常用方法

《C#中读取XML文件的四种常用方法》Xml是Internet环境中跨平台的,依赖于内容的技术,是当前处理结构化文档信息的有力工具,下面我们就来看看C#中读取XML文件的方法都有哪些吧... 目录XML简介格式C#读取XML文件方法使用XmlDocument使用XmlTextReader/XmlTextWr