原博客:http://blog.csdn.net/xiajun07061225/article/details/8092247
雙調歐幾里得旅行商問題是一個經典動態規劃問題。《算法導論(第二版)》思考題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)}.。
下面以北京大學OJ2677 Tour爲例,編程實現(C++):
AC代碼:
- //雙調歐幾里得旅行商問題
- //《算法導論(第二版)》思考題15-1
- //PKU 2677
- #include <iostream>
- #include <cmath>
- #include <iomanip>
- using namespace std;
- const int n = 7;//點的數目
- const int MaxVal = 999999;
- const int MaxLen = 201;
- struct tagPoint{
- double x,y;
- };
- //計算點i和點j之間的直線距離
- double distance(tagPoint *points,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 DP(tagPoint *points,int n)
- {
- double b[MaxLen][MaxLen];//記錄最短路徑的長度
- //計算所有情況下的b[i][j],1 <= i <= j
- //初始化
- b[1][2] = distance(points,1,2);
- for (int j = 3;j <= n;++j)
- {
- //i < j-1
- for (int i = 1;i <= j - 2;++i)
- {
- b[i][j] = b[i][j - 1] + distance(points,j - 1,j);
- }
- //i = j - 1,b[i][j] = min(b[k][j - 1] + distance(k,j));
- b[j - 1][j] = MaxVal;
- for (int k = 1;k <= j - 2;++k)
- {
- double temp = b[k][j - 1] + distance(points,k,j);
- if (temp < b[j - 1][j])
- {
- b[j - 1][j] = temp;
- }
- }
- }
- b[n][n] = b[n - 1][n] + distance(points,n - 1,n);
- return b[n][n];
- }
- int main()
- {
- int NUM;
- while(cin >> NUM)
- {
- tagPoint *points = new tagPoint[NUM + 1];
- for (int i = 1;i <= NUM;++i)
- {
- cin >> points[i].x;
- cin >> points[i].y;
- }
- double minDis = DP(points,NUM);
- //設置輸出格式:精確到小數點後2位
- cout.setf(ios::fixed);
- cout << setprecision(2) << minDis << endl;
- }
- }