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

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

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

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

双调欧几里得旅行商问题【转载】 - kevinlee_2010 - 云淡风轻

一个人从最左点开始,严格地从左到右直至最右点,然后从右到左直至出发点,可以等价为两个人同时从最左点,严格地从左到右经历不同路径到达最右点。
假设这两个人为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[N][N] = b[N-1][N]+distance(N-1,N); 
    return b[N][N]; 
} 
 
void my_print_path(){ 
    int string[N]; 
    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


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您解答算法导论第15章的双调欧几里得旅行商问题和整齐打印问题。 1. 双调欧几里得旅行商问题 双调欧几里得旅行商问题是指在平面给定一些点,求一条简单闭合曲线,使得经过每个点恰好一次,且总路程最小。其中,欧几里得距离是指两点之间距离。 该问题可以通过分治算法进行求解,具体步骤如下: 1)将所有的点按照x坐标从小到大排序; 2)将所有的点分成两部分,分别求出每一部分的最小路径,分别记为d1和d2; 3)在两部分的点中,选择一个点p,使得p在上一部分的最后一个点,同时p在下一部分的第一个点; 4)以p为分界点,将所有点分成上下两部分,并分别按照y坐标从小到大排序; 5)分别计算上半部分和下半部分的最小路径,分别记为d3和d4; 6)最终结果为d1+d2+d3+d4。 2. 整齐打印 整齐打印问题是指将一段文本分成若干行,每行不超过给定的宽度,使得每一行的长度尽可能相等,同时在每行末尾添加空格,使得每行的末尾恰好是一个单词的末尾,且每行的空格数最小。 该问题可以通过动态规划算法进行求解,具体步骤如下: 1)定义一个cost数组,其中cost[i][j]表示将第i个单词到第j个单词放在一行的代价; 2)定义一个lc数组,其中lc[i][j]表示将第i个单词到第j个单词放在一行的空格数; 3)计算cost和lc数组,具体方法如下: - 对于任意的i<=j,将第i到第j个单词放在一行,计算该行的空格数; - 如果该行的长度超过给定的宽度,则该方案不可行,否则将该方案的代价和空格数存入cost和lc数组中。 4)定义一个dp数组,其中dp[i]表示将前i个单词分成若干行的最小代价; 5)动态规划求解dp数组,具体方法如下: - 对于任意的1<=i<=n,将前i个单词分成若干行,计算最小代价; - 设最后一行的单词范围为[j+1, i],则dp[i] = min(dp[j] + cost[j+1][i]),其中j的范围为0<=j<i。 6)最终结果为dp[n]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值