双调欧几里得旅行商问题是一个经典动态规划问题。《算法导论(第二版)》思考题15-1和北京大学OJ2677都出现了这个题目。
旅行商问题描述:平面上n个点,确定一条连接各点的最短闭合旅程。这个解的一般形式为NP的(在多项式时间内可以求出)
J.L. Bentley 建议通过只考虑双调旅程(bitonictour)来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n*n)时间的算法。
上图中,a是最短闭合路线,这个路线不是双调的。b是最短双调闭合路线。
------------------------------------------------我是分割线------------------------------
思路:1),首先将所有点加上坐标,x轴指向右,y轴指向下。然后将所有点按照x轴坐标从小到大排列。
2)总体思路是依次从排好序的节点取出一个节点,决定该节点应该放在第一条路径上还是第二条路径上。 3)定义一个数组:double b[8][8]; //b[i][j]表示第一条路径搜索到第i各节点,第二条路径搜索到第j个节点后的最短路径长度。如果i==j则说明两条路径汇聚到i点上
如果i==n则说明 搜索到终点
3)求两点距离的方法:double Length(Node x[],int i,int j)
4)m[i][j]存的是编号 i 点与 编号 j 点的最短距离。需要声明的是:b[i][j]=b[j][i];
所以,m矩阵是一个对称矩阵。即 i 点与 j 点的距离跟j点与i点的矩阵相等。所以这里我们只需要求下三角矩阵b就可以。
5)由以上思路得递归公式:(i>j 求的是下三角)
(i==j时): b[i][j]=b[i][j-1]+Length[i][j-1]
(i>j+1时):b[i][j]= b[i-1][j]+Length[i-1][i]
(i=j+1时):b[i][j]=min(1<=k<j)(b[k][j]+Length[k][i]) //j==1时b[i][j]=Length(i,j);
由以上分析可以得到如下代码:
[cpp] view plaincopy
//双调欧几里得旅行商问题
#include<iostream>
#include<math.h>
#define M 65536
using namespace std;
//定义节点坐标
struct Node
{
int x;
int y;
}N[10];
//求节点i和节点j之间的长度
double Length(Node *N,int i,int j)
{
double L;
L=sqrt(double((N[i].x-N[j].x)*(N[i].x-N[j].x)+(N[i].y-N[j].y)*(N[i].y-N[j].y)));
return L;
}
void ShortPath(Node*N ,double (*b)[10],int length)
{
int i,j,k;
double num;
//定义起始节点序号为1
b[1][1]=0;
for(i=2;i<=length;i++)
{
for(j=1;j<=i;j++)
{
//如果两条路径终点都是i,则总路径的长度为一条从1到i与
//一条从1到i-1的路径之和加上从i-1到i的距离。
if(i==j)
{
b[i][j]=b[i][j-1]+Length(N,i,j-1);
}
//如果i与j之间相隔一个点以上,则j点的路径不变,而i点的路径是
//从1到i-1的路径加上i-1到i的路径
if(i>j+1)
{
b[i][j]=b[i-1][j]+Length(N,i-1,i);
}
if(i==j+1)
{
b[i][j]=M;
if(j==1)
{
b[i][j]=Length(N,i,j);
}
for(k=1;k<j;k++)
{
num=b[k][j]+Length(N,k,i);
if(b[i][j]>num)
{
b[i][j]=num;
}
}
}
b[j][i]=b[i][j];
}
}
}
int main()
{
N[1].x=0;
N[1].y=0;
N[2].x=1;
N[2].y=6;
N[3].x=2;
N[3].y=3;
N[4].x=5;
N[4].y=2;
N[5].x=6;
N[5].y=5;
N[6].x=7;
N[6].y=1;
N[7].x=8;
N[7].y=4;
double b[10][10]={0};
ShortPath(N,b,7);
cout<<b[7][7]<<endl;
return 0;
}
ps:为什么在j=i-1的时候,可以实现多个路径,比如:{6,5}--6--4--3--1--2--5--6实现环路,代码中只检查d[k,j]+d[k,i],这明明只有单个点的过度??-----留待思考