基于组合优化的旅行商问题

910 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

组合优化

对于有限个点,两两之间的距离已知,要找出连接这些点的最短路径,我们为这个任务取名信使问题(因为每一位邮递员都会遇到这个问题,很多旅行者也会遇见这个问题) 组合优化是离散的问题:即在min f(x)中x的域是离散的,离散优化问题,也被称为组合优化问题,我们视之为在候选目标的有限集中找出最有目标:

  min f(x) 其中 x ∈ {x1,x2,...,xn}

在理论上,我们通常可以评价每一个f(xi)来求解上式。这种组合优化的方法被称为穷举搜索或蛮力。然而,组合问题的搜索空间常常很大,因此检查每一个可能的解并不可行。 这里简单介绍了组合优化的背景,下面通过组合优化中经典的TSP问题来帮助读者对组合优化问题,进行进一步细致的理解。

组合优化中的TSP

综述

旅行商问题又称货郎担问题(TSP问题)。这个问题是:给定一系列的城市和它们的坐标,推销员从某一个起点出发,让找到一个最短的回路,使得商人经过所以城市又回到原点。该题在组合优化中属于NP难题。我们在图论的角度上,可以将理解为一个完全图,且边待权重的无向图,寻找一个权值最小的Hamilton回路,随着城市即顶点数的增加,问题的规模会呈指数级别增长,故称为一个NP完全问题。 文章对问题进行了叙述,介绍了暴力算法在TSP问题中的应用;并且拓展了Prim算法和Kruskal算法,在最小生成树的基础上形成环路从而得到TSP问题的局部最优解,我们对这种近似算法进行了详细的讨论,证明了该拓展可以估计最优解。接下来又阐述了深度优先搜索和广度优先搜索。最后应用启发式算法GA,对TSP问题进行建模和求解,计算得到的结果为35.48,其最优路径为:2->1->5->9->10->8->7->6->3->4->2。最后对所有算法和结果进行了对比,得出结论。 本文详细概述了TSP解决的多种算法,并对它们进行分别求解。证明得出了近似算法对TSP问题的估计作用。并将遗传算法、Prim算法、Kruskal算法、暴力算法、深度优先搜索和广度优先搜索进行了结合,对比,具有深刻的意义和生产应用价值。

关键词:TSP问题 遗传算法 Prim、Kruskal算法 暴力算法 深度优先搜索 广度优先搜索

一、问题叙述

已知:一个完全图G=(V,E),边上赋有权值。其中V为顶点(城市)的集合,V={v_1, v_2,…, v_n},E为该完全图的边集,E={e_1, e_2,…, e_n},第i条边上带权ω_i。从一个起点S开始,寻找一条环路,stG中每个顶点都要被访问且访问过一次。 设环路的总长度为L,依次经过的顶点顺序为v_1, v_2,…, v_n,s_1为(v_1, v_2),s2为(v_2, v_3),…,s_n为(v_n, v_1),且s_i∈ω_i,i=1,2,…。故我们的目标函数可以描述为: 在这里插入图片描述 接下来我们讨论的具体问题是:一共有个顶点v_1,v_2,v_3,v_4,v_5,v_6,v_7,v_8,v_9,v_10。它们在图上的坐标分别为(0,0),(2,4),(1,8),(3,5),(,4,1),(5,7),(6,9),(7,8),(9,0),(10,3)。因为是完全图,每个点之间都有路,故可以直接通过坐标来计算除=出每个顶点之间的距离d_ij,其中 在这里插入图片描述 将这些点可视化得: 在这里插入图片描述

二、图在计算机上的表示

首先,我们需要将坐标存入计算机中,以便后续的的使用。我们用矩阵d来录环路上点和边之间的关系,d_ij表示(v_i,v_j),i和j分别表示第i个城市和第j个城市,d_ij的计算公式如上,其中i=1,2,…,10,j=1,2,…,10。D可表示为: 在这里插入图片描述

三、TSP问题的暴力解法

暴力求解:穷举出所以可能旅行路线,即所以城市之间的排列组合,一共有10!种可能的情况,分别计算出其对应的值,最后从中找出最好的解。 设集合M={m_1,m_2,…, m_n},其中m_i是110的向量,比如0123456789,它表示依次旅行商依次经过城市的顺序,并且从最后一个城市回到第一个城市形成回路,n为所有10!可能的排列组合情况。表示n1的向量用于记录每种情况下回路的距离, r_i表示R种的第i个元素。 下面是暴力算法的伪代码:

Initialize M;
For i = 1 to n
	Compute m_i;
	Record r_i;
End
Output min(R);

暴力算法的流程为:初始化数据,穷举所有可能的情况,并将其记录,最后比较出最好的值并输出。穷举完所有情况后所求得的最短路径长度为35.48,路径为8->7->6->3->4->2->1->5->9->10->8。(因为这与后面遗传算法求得的解一致,故省去此处的结果可视化,读者参看第七部分的图即可)

四、Prim算法、Kruskal算法拓展

Prim算法是找出一颗最小生成树,其边权值的和最小,但树并不唯一,可以有多种。考虑到Prim算法能够在不产生闭合环路的情况下,能够找到最短路径使得走过所有城市的距离L最小,我们将其进行拓展,即把最后形成的树连接成环。 把所有城市构成的图视为一个无向完全图G(V,E),其中Q是V的点集,V.key为ω(v,v.Π),v.Π为v的父节点。ω为两点间边的权重。 下面是改进后Prim算法的伪代码:

For each u ∈ V	// 初始化
	V.key = ∞;
	v.Π = null;
end
set x_r.key = ∞;
set Q = G.V;		// G中所有的顶点
while Q ≠ ø
	u = extract min(Q);	//取出key最小的点
	for each v ∈ G && next to u
		if V ∈ Q and ω(u,v) < v.key
			v.Π = u;
			v.key = ω(u,v);
		end
	end
end

Kruskal算法用另一张思想同样能找到一颗最小生成树,就是不断往里面加边,我们对他进行拓展时需要注意加入约束条件,让他在没有遍历所有顶点时不能出现圈。初始条件和申明同Prim算法时一样,这里不在多作申明。 下面时改进后Kruskal算法的伪代码:

Set A = ø;
For each v ∈ G.V
	Make-set(x);	// 连通分支
End
Sort the edge in G.E recorded in queue Q;
For i = 1 to Q.length
	(u,v) = Q.first edge;
	If find set(u) != find set(v)
		A = A ∪ {(u,v)};
		Union(u,v);
end
end
return A;

通过这两种经典算法得到的最小生成树T_min,我们可以在此基础上进一步拓展以求解问题的解。下面是由上述算法生成的最小生成树的结果图,可视化得 在这里插入图片描述 在这里插入图片描述

五、利用最小生成树的结果来找旅行商路径

在Prim算法和Kruskal算法生成T_min,通过使用深度优先搜索和广度优先搜索来寻找旅行商问题的结果。在无向图G(V,E)中,V为点集,E为边集。给所有的点标上颜色以表示查找的状态,black表已探,gray表正在探索,而white表未背探索的结点状态。 基于Prim算法或Kruskal算法得到最小生成树T_min,因为生成的树并不是一个环形的路,但它边上的权重是符合我们寻优的部分要求的,故我们用宽度优先搜索(Breadth First Search)和广度优先搜索(Depth First Search)与去结合,进行TSP问题的求解,下面是两种算法的伪代码:

//宽度优先搜索(BFS)
Set s.color = gray,Q;		// 初始化
s.d = 0; s.Π = null;
for each u ∈ (G.V) – {s}
	u.color = white;
	u.d = ∞;
u.Π = null;
end
enqueue;
while Q ≠ ø
	u ∈ Q ∪ E ∪ E(Q);
	for each edge ∈ E
		if v.color = white
			v.color = gray;
			v.d = u.d + 1;
			v.Π = u;
			update enqueue;
		end
	end
end

//深度优先搜索(DFS)
function DFS visit(G,u)
	time = time + 1;
	u.d = time;
	u.color = gray;
	for each v ∈ G
		if v.color = white
			v.Π = u;
			DFS visit(G,v);
		end
	end
	u.color = black;
	time = time + 1;
	u.f = time;

根据最小生成树的特点,深度优先搜索更加适合于次数路径的寻找。因为最小生成树的分叉少,即子树一般只有左儿子或右儿子,且深度较深;宽度优先搜索的不断回溯会使得出现非最小生成树的边数增多;综上所述,我们优先考虑DFS。以(0,0)为根节点,采用DFS算法生成的环路为1->5->9->10->2->4->6->7->8->3->1,由此我们求得环路长度为47.905738。 在这里插入图片描述

六、基于P算法和K算法拓展的讨论

显然P算法能够找到一条最短的通路st经过所有城市,但在最后连接起点和终点的过程中,若起点和重点的距离是图上对角线最远的距离,或更夸张的说是∞时,该算法产生的结果与最优解best的相差就会很大了。 对于K算法的拓展,根据算法的伪代码并结合图示,可以得到商人经过的每次经过的距离为最小生成树权值的两倍,所以我们可以得到公式 2ω(T_min)≥best 而best一定小于最小生成树权值的总和 ω(T_min)≤best 其中,best表示TSP问题的最优解的值,T_min表示由Prim算法和Kruskal算法生成的最小生成树。 虽然P算法和K算法拓展也许不能找出最短回路,但也算法一个局部最优解。它确能够给我们解存在的大致范围,2倍的T_min是对best的最坏估计,虽然没能求出最优解,但实际生活种这种近似算法的估计意义重大。

七、遗传算法

遗传算法是一种进化算法,其基本原理是仿效生物界中的“物竞天择适者生存”的演化法则。遗传算法是把问题参数编码为染色体,再利用迭代的方式进行选择、交叉以及变异等运算来交换种群中染色体的信息,最终生成符合优化目标的染色体。 1.编码。对于10个城市的TSP问题,染色体分为10段,其中每一段对应城市的编号,比如|1|2|3|4|5|6|7|8|9|10|就是一个合法的染色体。 2.种群初始化。在完成编码后。产生一个初始种群作为起始解,在这里我将种群规模N设置为40。 3.适应度函数。对于种群中的每一个个体,我们需要对其进行适应度评价,并将其结果作为进化的重要指标,很明显评价函数为1/f。L越大的个体适应度越低,L越小的个体适应度越高。 4.选择操作。从就种群中以一定概率选择个体到新种群中,在这里我们使用轮盘赌(适应度大的选中的概率越大)。 5.交叉操作。采用部分映射杂交,将父代两两分组杂交。随机生成交叉位置r,并将两个个体r两边的染色体互换。下面是示意图: 9 5 1|3 7 4 2|10 8 6 10 5 4|6 3 8 7|2 1 9 交叉为 9 5 1|6 3 8 7|10 * * 10 5 |3 7 4 2| 1 9 其中打*号说明部分城市交叉后有重复染色体,但由问题背景知不能有重复的染色体,因为每个城市只能经过一次,再采用部分映射的方法即可生成合理的解的形式 9 5 1|6 3 8 7|10 4 2 10 5 8|3 7 4 2|6 1 9 6.变异操作。选中某个个体,以一定概率将互换其中的两个染色体的位置。 对每个个体进行选择、交叉和变异,然后对其进行适应度评估,在每一代中不断执行这些操作,直到满足循环退出条件,结束遗传操作。 7.逆转录。为了避免进化提前收敛或困在局部最优解里,使用逆转录来打破僵局,即将染色体序列反转。 1|2|3|4|5|6|7|8|9|10 变为 10|9|8|7|6|5|4|3|2|1 8.子父代重组。将子代和父代中较优的个体进行重组进而形成新的子代种群。 在这里插入图片描述 经过运算,最短路的结果为:2->1->5->9->10->8->7->6->3->4->2需要走过的总距离为:35.48。作为对比的某一可行解的路径为:8->10->4->6->3->1->9->7->5->2->8下面是遗传算法迭代收敛过程,某一可行解环路和最优解的环路的图。 在这里插入图片描述在这里插入图片描述

在这里插入图片描述

八、对比

暴力算法求解的结果是35.48,通过Prim、Kruskal算法拓展得到的解为47.905738,遗传算法得到的结果是35.48(在不考虑单位的情况下)。易知,遗传算法求得解为最优解,通过模拟生物进化很好地求出了best,且花费时间远小于暴力算法,这也是遗传算法在需要大量计算时使用的原因之一。而与P算法与K算法拓展得到的解与best的相对误差为35.02%①,前面已经证明,它的最坏结果能够有效估计解的存在范围,这是极有意义的。 在这里插入图片描述

其中resultP、K表由改进的P算法和K算法得到的解。

代码

上面所有图形和结果都有源码,由于篇幅原因,需要的私聊笔者或评论区留言。 喜欢文章或有帮助的,可以点赞、收藏一键三连哦! 在这里插入图片描述