算法导论15-1双调欧几里得旅行商问题 & 15-2整齐打印
CLRS 15-1 双调欧几里得旅行商问题
欧几里得旅行商问题是对平面上给定的n个点确定一条连接各点的最短闭合旅程的问题。如图(a)给出了一个7个点问题的解。这个问题的一般形式是NP完全的,故其解需要多于多项式的时间。
J. L. Bentley建议通过只考虑双调旅程来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n2)时间的算法。
描述一个确定最优双调路线的O(n2)时间的算法。可以假设任何两点的x坐标都不相同。(提示:从左到右扫描,保持路线两部分的最优概率)。
a)最短闭合路线,长度大约是24.89。这个路线不是双调的。 b)相同点的集合上的最短双调闭合路线。长度大约是25.58
解题思路:
1.题目所求的结果就是最左端点到最右端点的两条线路,对于这两条线路,线路上的点的x坐标是递增的(第i个点一定比i-1个点的x坐标大)
2.从左端点开始,有两条线路出发,用d(i, k)表示两条线路分别到达i点和k点的距离之后,这里指的是最短距离之和,两条线路无相同点(除去起点和终点)。在这里,由于两条线在意义上是等价的,因而我们规定i<=k,即一条线路总是领先着。
3.从2可以看出,d(n, n)即为所求。
对于d(i, k),我们可以如下分析:
1)当k < i-1时,有
d(i, k) = d(i-1, k) + |Pi-1Pi|,这里表示d(i, k)必然包含线段|Pi-1Pi| (点Pi-1和点Pi之间的距离)
2)当k = i-1时,有
d(i, k) = d(i-1, u) + |PuPi|,其中1 <= u < i-1,这里遍历u的值,寻找最短距离
3)当k = i时,有
d(i, k) = d(i-1, u) + |Pi-1Pi| + |PuPi|,其中1 <= u < i-1
对于下面的算法,为了在这省略排序算法(时间复杂度为n*lgn),输入必须按照x坐标由小到大进行,在gcc下用
gcc travel.c -lm
命令编译,记得加上-lm参数。
看起来,这个算法好像有三层for循环,时间复杂度为O(n3),但是我们发现:
第41行的for (u = 0; u < i-1; u++)只在k == i-1时才执行,并不是在第34行的循环体中每次执行。
第51行的for循环只在k == i并且i == rows-1的情况下执行,
因而整体的时间复杂度为O(n2)
1 #include <stdio.h> 2 #include <math.h> 3 #include <float.h> 4 #include <stdlib.h> 5 6 //*((int*)array + n*i + j); 7 //typedef int array[2]; 8 9 //坐标 10 typedef struct point_t 11 { 12 int x; 13 int y; 14 } point; 15 16 //计算两点之间的距离 17 double distance(point p1, point p2) 18 { 19 return sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y)); 20 } 21 22 double process(point* a, const int rows) 23 { 24 double d[rows][rows]; 25 d[0][0] = 0.0; 26 d[1][0] = distance(a[1], a[0]); 27 28 int i; 29 int k; 30 int u; 31 //按照对d(i,k)的分析写循环 32 for (i = 2; i < rows; i++) 33 { 34 for (k = 0; k <= i; k++) 35 { 36 if (k < i-1) 37 d[i][k] = d[i-1][k] + distance(a[i-1], a[i]); 38 else if (k == i-1) 39 { 40 d[i][k] = DBL_MAX; 41 for (u = 0; u < i-1; u++) 42 { 43 double tmp = d[i-1][u] + distance(a[u], a[i]); 44 if (tmp < d[i][k]) 45 d[i][k] = tmp; 46 } 47 } 48 else if (k == i && i == rows - 1)//当k==i时,只需计算都等于rows-1的情况,其他没必要 49 { 50 d[i][k] = DBL_MAX; 51 for (u = 0; u < i-1; u++) 52 { 53 double tmp = d[i-1][u] + distance(a[u], a[i]) + distance(a[i-1], a[i]); 54 if (tmp < d[i][k]) 55 d[i][k] = tmp; 56 } 57 } 58 } 59 } 60 return d[rows-1][rows-1]; 61 } 62 63 int main() 64 { 65 int rows; 66 67 scanf("%d", &rows); 68 point* data = (point*)malloc(sizeof(point)*rows); 69 point p; 70 int count = 0; 71 while (rows--) 72 { 73 //为了省略排序算法,这里的输入必须按照x坐标从小到达进行 74 scanf("%d%d", &(p.x), &(p.y)); 75 data[count++] = p; 76 } 77 printf("%f\n", process(data, count)); 78 free(data); 79 80 return 0; 81 }
CLRS 15-2 整齐打印
考虑在一个打印机上整齐地打印一段文章的问题。输入的正文是n个长度分别为L1、L2、……、Ln(以字符个数度量)的单词构成的序列。我们希望将这个段落在一些行上整齐地打印出来,每行至多M个字符。“整齐度”的标准如下:如果某一行包含从i到j的单词(i<j),且单词之间只留一个空格,则在行末多余的空格字符个数为 M - (j-i) - (Li+ …… + Lj),它必须是非负值才能让该行容纳这些单词。我们希望所有行(除最后一行)的行末多余空格字符个数的立方和最小。请给出一个动态规划的算法,来在打印机整齐地打印一段又n个单词的文章。分析所给算法的执行时间和空间需求。
解答:
定义remain[i, j] = M - j + i - ∑lk ,其中k = i, ..., j,表示余下的空格数
定义cube[i, j],表示每行空格数的立方值,MAX表示无穷大
|------>MAX 当remain[i, j] < 0时
cube[i, j] = |------>0 当j == n,且remain[i, j] >= 0 (其实这里表示的就是最后一行)
|------>(remain[i, j])3 非上述两种情况时
定义所有立方之和sum[i],假设sum[j]表示的是1,...,j这j个单词的最优排列(即所求立方和最小),那么在最后一行,假设是i,...,j这些单词,那么sum[j] = sum[i-1] + cube[i, j]。
|------>0 if j == 0
sum[j] = |
|------>min(sum[i - 1] + cube[i - 1, j] if j > 0,其中1 <= i <= j
1 GET-REMAIN() 2 { 3 for (i = 1; i <= n; i++) 4 remain[i, i] = M - li; 5 for (j = i + 1; j <= n; j++) 6 remain[i, j] = remain[i, j-1] - lj - 1; 7 } 8 9 GET-CUBE() 10 { 11 for (i = 1; i <= n; i++) 12 for (j = i; j <= n; j++) 13 { 14 if (remain[i, j] < 0) 15 cube[i, j] = MAX; 16 else if (j==n && remain[i, j]>=0) 17 cube[i, j] = 0; 18 else 19 cube[i, j] = (remain[i, j])3; 20 } 21 } 22 23 GET-SUM() 24 { 25 sum[0] = 0; 26 for (j = 1; j <= n; j++) 27 { 28 sum[j] = MAX; 29 for (i = 1; i <= j; i++) 30 if (sum[i-1] + cube[i, j] < sum[j]) 31 { 32 sum[j] = sum[i-1] + cube[i, j]; 33 p[j] = i;//用数组p来记录换行的位置 34 } 35 } 36 }
可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明