程序员必会的40种算法
上QQ阅读APP看书,第一时间看更新

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(用来生成所有城市的排列组合),我们还用复数来表示距离。这意味着以下几点:

  • 计算两个城市ab的距离如计算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算法的设计。