4.3 实际应用——求解TSP
我们先看一下TSP问题的定义。TSP是一个著名问题,在20世纪30年代作为一个挑战被提出来。TSP是一个NP难问题。首先,我们可以在不关心最优解的情况下,随机生成一个旅行路线来满足访问所有城市这一条件。然后,我们可以在每一轮迭代中对解进行改进。在迭代过程中生成的每条旅行路线被称为候选解(也可以称为可行解)。证明一个可行解是最优解需要呈指数级增长的时间,取而代之的是使用各种基于启发式规则的解,这些解生成的旅行路线接近最佳路线,但并非最佳路线。
旅行商需要访问所有给定城市构成的列表才能完成工作:
请注意以下两点:
- 列表中各城市之间的距离是已知的。
- 在给定的列表中,每个城市只访问一次。
我们可以为旅行商生成旅行计划吗?什么样的旅行计划才是最小化旅行商所走总路程的最优解呢?
以下是我们用于演示TSP的五个加拿大城市之间的距离:
请注意,我们的目标是得到一条在出发城市开始和结束的旅行路线。例如,一条典型的旅行路线可以是Ottawa-Sudbury-Montreal-Kingston-Toronto-Ottawa,这条路线总的开销是484+680+287+263+450=2164。这是不是旅行商要走的路程最短的旅行路线?能使旅行商所走总路程最短的最优解是什么?我们将把这些问题留给读者思考和计算。
4.3.1 使用蛮力策略
要求解TSP,我们首先想到的求解方案是使用蛮力策略来找出最短路线(任何路线都必须使得旅行商恰好访问每个城市一次,且最后返回出发城市)。蛮力策略的工作原理如下:
1. 评估所有可能的旅行路线。
2. 选择距离最短的一条。
问题是,对于n个城市存在(n-1)!条可能的旅行路线。这意味着5个城市将产生4!=24条可能的旅行路线,我们选择距离最短的那条路线。显然,只有在城市不太多的情况下,该方法才有效。随着城市数量的增加,这种方法产生大量的排列组合,蛮力策略变得不稳定。
我们看一下如何在Python中实现蛮力策略。
首先注意,旅行路线{1, 2, 3}表示从城市1到城市2再到城市3。旅行的总距离是旅行中经过的总距离。我们假设城市之间的距离是它们之间的最短距离(即欧几里得距离)。
我们先定义三个实用函数:
distance_points
:计算两点之间的绝对距离。distance_tour
:计算旅行商在给定的旅行路线中需要经过的总距离。generate_cities
:随机生成位于长500
、宽300
的矩形中的n个城市。
这些函数的代码如下:
在上面的代码中,我们用itertools
包的permutations
函数来实现alltours
(用来生成所有城市的排列组合),我们还用复数来表示距离。这意味着以下几点:
- 计算两个城市a和b的距离如计算
distance(a, b)
一样简单。 - 我们只需调用
generate_cities(n)
就可以创建n个城市。
现在,我们定义一个函数brute_force
,该函数会生成所有可能的城市旅行路线。一旦生成了所有可能的旅行路线,它将选择距离最短的路线:
下面,我们定义实用函数来帮助我们绘制城市,这些函数包括:
visualize_tour
:绘制特定旅行路线中的所有城市及其之间的连接。它还会突出显示旅行开始的城市。visualize_segment
:在visualize_tour
中使用,用于绘制一段路线中的城市和城市之间的连接。
请看下面的代码:
最后,我们实现函数tsp()
,它可以完成以下工作:
1. 根据算法和请求的城市数量生成旅行路线
2. 计算算法运行所需的时间
3. 生成一个图,展示运行结果
一旦定义了tsp()
,我们就可以用它来创建一条旅行路线(如图4-6所示)。请注意,在图4-6中我们使用tsp
函数生成了10个城市的旅行路线。当n=10时,它将生成(10-1)!=362 880种可能的排列组合。如果n增加,排列组合的数量就会急剧增加,这样就不能使用蛮力法了。
图 4-6
4.3.2 使用贪心算法
如果用贪心算法来求解TSP,则在每一步中我们都可以选择一个看起来比较合理的城市,而不是寻找一个可以得到最佳总体路径的城市。因此,每当需要选择一个城市时,我们只需要选择离当前位置最近的城市,而不需要去验证这个选择是否会带来全局最优的路径。
贪心算法的方法很简单:
1. 从任何一个城市出发。
2. 在每一步中,继续访问以前未访问过的下一个最近的相邻城市,以继续旅行。
3. 重复第二步。
我们定义一个名为greedy_algorithm
的函数来实现上述逻辑:
现在,我们用greedy_algorithm
来为2000个城市创建一条旅行路线(如图4-7所示)。
图 4-7
请注意,贪心算法仅花费0.514秒即可为2000个城市生成旅行路线。如果我们使用蛮力法,它将会生成(2000-1)!种排列组合,这几乎是无穷大。
需要注意的是,贪心算法是基于启发式规则的,并不能证明所得到的解就一定是最优的。
下面,我们讨论PageRank算法的设计。