您的位置:首页 > 其它

算法 - 双调欧几里得旅行商问题

2015-05-15 18:56 316 查看
欧几里德旅行商问题是对平面上给定的n个点的确定一条连接各点的最短闭合旅程的问题。图a给出了一个7个点问题的解。这个问题的一般形式是NP完全的,故其解需要多余多项式的时间。

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

描述一个确定最优双调路线的O(n^2)时间的算法。可以假设任何两点的x坐标都不相同。





一个人从最左点开始,严格地从左到右直至最右点,然后从右到左直至出发点,可以等价为两个人同时从最左点,严格地从左到右经历不同路径到达最右点。

假设这两个人为A和B,且A总是走在B后面。设Pij表示A走到pi、B走到pj时两人所经过的最短双调路径,根据假设,可得i<=j。又设b[i, j]表示最短双调路径Pij的长度,d[i, j]表示点pi到点pj的直线距离,则:

b[1, 2]=d[1, 2]

当i=j时,即A和B处于同一点,b[i, j]=b[i, i]=b[i-1, i]+d[i-1, i]

当i=j-1时,即A在B紧邻的靠后一点,b[i, j]=b[j-1, j]=min(1<=k<j-1){b[k, j-1]+d[k, j]}

当i<j-1时,即A在B后且相隔多个点,b[i, j]=b[i, j-1]+d[j-1, j]

由几何学知识可得,如果中间路径A和B经历了同一点,则这条路径肯定不是最短路径,故i=j的情况只可能用来计算b[n, n]=b[n-1, n]+d[n-1, n]。

定义r[i, j]表示双调路径Pij上,点p[j]的前驱点的下标。

除此之外,还涉及到了对最优解的重构的问题。我们将使用一个r[i][j]数组表示子问题P(i,j)在到达终点P[j]之前经过的一个点P[k]对应的k值(仅挨着点P[j]的点),则子问题的解可以组织为其更小的子问题P(i,k)的解加上点P[k]和点P[j]。由之前的解题思路可知,对于问题P(i,j),当i=j-1时,k<i,当i<j-1时,k=j-1。

其实得到的最优解是个闭合旅程,所以从出发后的第一个点与到达之前的一个点的位置是等价的。如闭合旅程是76431257,也可以是75213467。

构造解的过程如下:

每次加入的点总是在序号大的点下,因为问题P(i,j)总是分解为子问题P(i,k),不管k是等于j-1,还是小于j-1,然后确定点P[k]是到达P[j]之前的一个点,这也是问题每次选择的结果。使用一个数组存放序号,一边从0开始,一边从末尾开始。





模板:

#include <iostream>
#include <cmath>
#include <fstream>
using namespace std;

#define N 7
struct Point{
double x;
double y;
};
struct Point points[N+1];
double b[N+1][N+1];
int r[N+1][N+1];

double distance(int i,int j);//第i,j点的欧式距离
double Euclidean_TSP();//最短闭合旅程长度
void my_print_path();//打印旅程

void main(int argc, char **argv){
ifstream infile;
infile.open("input.txt");//读入一个有各点坐标的文档
if (!infile)
{
cout<<"error!"<<endl;
}
int i=1;
while (infile>>points[i].x>>points[i].y)
{
i++;
}
cout<<"最短双调闭合旅程长度是:"<<Euclidean_TSP()<<endl;
my_print_path();
}

double distance(int i,int j){
return sqrt((points[i].x-points[j].x)*(points[i].x-points[j].x)
+(points[i].y-points[j].y)*(points[i].y-points[j].y));
}

double Euclidean_TSP(){
b[1][2]=distance(1,2);//最小的子问题

for (int j=3;j<=N;j++)
{
//i<j-1且i>=1时的情况
for (int i=1;i<j-1;i++)
{
b[i][j] = b[i][j-1]+distance(j-1,j);
r[i][j] = j-1;
}
//i=j-1的情况
b[j-1][j] = b[1][j-1]+distance(1,j);//先设初值为k=1时的值
r[j-1][j] = 1;
for (int k=1;k<j-1;k++)
{
double q = b[k][j-1]+distance(k,j);
if (q < b[j-1][j])
{
b[j-1][j] = q;
r[j-1][j] = k;
}
}
}
b

= b[N-1]
+distance(N-1,N);
return b

;
}

void my_print_path(){
int string
;
string[0]=N;
string[1]=N-1;
int k=N-1;
int left_hand=N-1,right_hand=N,begin=2,end=N-1;
for (int i=N-1,j=N;k!=1;)
{
k=r[i][j];
if (left_hand>right_hand) //比较那边的点的序号大
{
left_hand=k;
string[begin]=k;
begin++;
}else{
right_hand=k;
string[end]=k;
end--;
}
if (i==j-1)
{
j=i;
i=k;
}else if (i<j-1)
{
j=k;
}
}
cout<<"该旅程是:";
for (int index=0;index<N;index++)
{
cout<<string[index];
}
cout<<endl;
}

input.txt:

 

0 6

1 0

2 3

5 4

6 1

7 5

8 2

 

运行后:

最短双调闭合旅程长度是:25.584

该旅程是:7643125

其实旅程也可以是7521346
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法