【算法学习】双调欧几里得旅行商问题(动态规划)
双调欧几里得旅行商问题是一个经典动态规划问题。《算法导论(第二版)》思考题15-1和北京大学OJ2677都出现了这个题目。
旅行商问题描述:平面上n个点,确定一条连接各点的最短闭合旅程。这个解的一般形式为NP的(在多项式时间内可以求出)
J.L. Bentley 建议通过只考虑双调旅程(bitonictour)来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了
同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n*n)时间的算法。
上图中,a是最短闭合路线,这个路线不是双调的。b是最短双调闭合路线。
求解过程:
(1)首先将各点按照x坐标从小到大排列,时间复杂度为O(nlgn)。
(2)寻找子结构:定义从Pi到Pj的路径为:从Pi开始,从右到左一直到P1,然后从左到右一直到Pj。在这个路径上,会经过P1到Pmax(i,j)之间的所有点且只经过一次。
在定义d(i,j)为满足这一条件的最短路径。我们只考虑i>=j的情况。
同时,定义dist(i,j)为点Pi到Pj之间的直线距离。
(3)最优解:我们需要求的是d(n,n)。
关于子问题d(i,j)的求解,分三种情况:
A、当j < i - 1时,d(i,j) = d(i-1,j) + dist(i - 1,i)。
由定义可知,点Pi-1一定在路径Pi-Pj上,而且又由于j<i-1,因此Pi的左边的相邻点一定是Pi-1.因此可以得出上述等式。
B、当j = i - 1时,与Pi左相邻的那个点可能是P1到Pi-1总的任何一个。
因此需要递归求出最小的那个路径:
d(i,j) = d(i,i-1) = min{d(k,j) + dist(i,k)},其中1 <= k <= j。
C、当j=i时,路径上最后相连的两个点可能是P1-Pi、P2-Pi...Pi-1-Pi。
因此有:
d(i,i) = min{d(i,1)+dist(1,i),...,d(i,i-1),dist(i-1,i)}.。
for(int i=1;i<=n;i++) scanf("%d%d",&v[i].x,&v[i].y);//输入坐标 sort(v+1,v+1+n,cmp);//排序 pre();//预处理出任意两点间的距离。
f[1][2]=f[2][1]=dis[1][2];//预处理,f[1][2]=f[2][1]=dis[1][2]=dis[2][1]; f[2][2]=2*dis[1][2];//根据原理推得f[2][2]=dis[1][2]*2=dis[2][1]*2; for(int i=3;i<=n;i++){ for(int j=1;j<i-1;j++)//①f[i][j]=f[j][i];②对应学习中的第一种情况j<i-1,那么f[i][j]=f[i-1][j]+dis[i][i-1]; f[i][j]=f[j][i]=f[i-1][j]+dis[i][i-1]; f[i][i-1]=f[i-1][i]=f[i][i]=0x7f7f7f7f;//取小,附为极大值。 for(int j=1;j<=i-1;j++)//对应学习中的第二种情况:j==i-1,此时与i左邻的点肯定在1——i-1中,枚举最短距离。 f[i-1][i]=f[i][i-1]=min(f[i][i-1],f[j][i-1]+dis[j][i]); for(int j=1;j<=i;j++)//对应学习中的第三种情况:i==j,那最后连边的可能是i与i——i-1中的一个点,取这条边的最小值。 f[i][i]=min(f[i][i],f[j][i]+dis[j][i]);
}
//最后的f[n][n]即为所求。