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 }