代码改变世界

货郎问题

2007-10-19 10:55  老博客哈  阅读(5111)  评论(15编辑  收藏  举报

    货郎问题(Traveling Salesman Problem,简称“TSP”)也叫货郎担问题,中国邮路问题,旅行商问题等,是计算机算法理论历史上的经典问题。在过去几十年中,它成为许多重要算法思想的测试平台,同时也促使一些新的理论领域的产生,比如多面体理论和复杂性理论。 货郎问题:给定n个结点和任意一对结点{i,j}之间的距离为dist(i,j),要求找出一条闭合的回路,该回路经过每个结点一次且仅一次,并且该回路的费用最小,这里的费用是指每段路径的距离和。 货郎问题求解其精确解是NP难的,并且求解任意常数因子近以度的解也是NP难的。若将问题限定在欧氏平面上,就成为欧氏平面上的货郎问题,也叫欧几里德旅行商问题(Eculid Traveling Salesman Problem)。但是,即使是欧氏平面上的货郎问题也是NP难的。因此通常用来解决TSP问题的解法都是近似算法。其中第一个欧几里德旅行商问题的多项式近似算法是Arora在1996年使用随机平面分割和动态规划方法给出的。

    J.L. Bentley 建议通过只考虑双调旅程(bitonic tour)来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n*n)时间的算法。

 

注:在一个单位栅格上显示的平面上的七个点。 a)最短闭合路线,长度大约是24.89。这个路线不是双调的。b)相同点的集合上的最短双调闭合路线。长度大约是25.58

    下面我以具体的题目为例谈谈双调欧几里德旅行商问题(bitonic tsp):

1. pku 2677 Tour

这个题就是赤裸裸的Bitonic Tour问题了。我的解决方案如下:

将一个人从最左端走到最右端,然后从最右端走到最左端等价成两个人同时从最左端不重复的走过中间的点并且到达最右端。

我们不妨设这两个人为A和B,且总是假定走在前面的人是A。再设函数F(x1, x2)表示A走到x1的位置,B走到x2的位置,并且所有x1,x2之前的位置都被不重复的走过的最短距离之和。根据前面的假设我们可以知道 x1>=x2,且F(xn,xn)即为所得。

下面的过程就是如何推导出状态方程,我把它划分为三种情况:

1) 如果x1=x2,即A和B处在同一点,那么F(x1, x2) = F(x1, x1) = F(x1, x1 - 1) + dist(x1, x1 - 1)

2) 如果x1=x2+1,即B在A的紧邻的靠后一点,那么F(x1, x2) = F(x2, x') + dist(x1, x') (1<= x' <=x2)

3) 如果x1>x2+1,即B离A在后面一个距离的范围以上,那么F(x1, x2) = F(x1 - 1, x2) + dist(x1, x1 - 1)

其实这里不用考虑如果x1,x2走到中间同一个点,因为很容易得到那样不是最优的。

以上算法时间复杂度与空间复杂度均为O(n*n)

2. vijos 1014 旅行商简化版 

这个题和上面的几乎一样,要注意的是,题目并没有说所有的点已经按照x坐标排序,要小心。

3. zju 2096 Door to Secret

这个题目是Bitonic Tour的一个变种,将欧几里德平面的点变成了坐标轴上的点,因此距离函数dist由笛卡尔距离变成了坐标轴距离。另外规则上也有一些变动,只需从左到走到最右,然后反过来从最右把左边所有没走过的走一下。言下之意就是说起点不是固定的。我们不妨假设点的标号为1~n,这里我们虚拟一个起点0,那么问题所求即为 min{F(n,0), F(n,i)+dist(i,n)(1<=i<=n)} 与1不同的是我们的初始条件是F(0,0)=F(1,0)=0,并且在状态转移的过程中要注意从0出发距离是不增加的。

贴下我AC的代码(zju不保存代码:-(,怕以后找不到)

 

#include <stdio.h>
#include 
<stdlib.h>

const int inf = 1000000000;

int n;
int d[128];
int dp[128][128];

int Min(int a, int b)
{
 
return a < b ? a : b;
}


int main()
{
 
int i, j, k;
 
while(scanf("%d"&n) && n)
 
{
  
for(i = 1; i <= n; i++)
   scanf(
"%d", d + i);

  
for(i = 0; i <= n; i++)
   
for(j = 0; j <= n; j++)
    dp[i][j] 
= inf;

  dp[
0][0= dp[1][0= 0;
  
for(i = 2; i <= n; i++)
  
{
   
for(j = i - 1; j >= 0; j--)
    dp[i][i 
- 1= Min(dp[i][i - 1], dp[i - 1][j] + (j == 0 ? 0 : abs(d[j] - d[i])));

   
for(j = 0; j <= i - 1; j++)
   
{
    dp[i][j] 
= Min(dp[i][j], dp[i - 1][j] + abs(d[i] - d[i - 1]));
   }

  }


  
int ans = dp[n][0];
  
for(i = 1; i <= n; i++)
  
{
   ans 
= Min(ans, dp[n][i] + abs(d[i] - d[n]));
  }


  printf(
"%d\n", ans);

 }


}