凸多邊形最優三角剖分的兩種演算法分析
阿新 • • 發佈:2019-02-16
/* Name: Copyright: Author: 巧若拙 Date: 27-03-17 10:11 Description: 動態規劃--凸多邊形最優三角剖分 題目描述: 用多邊形頂點的逆時針序列表示凸多邊形,即P={v0,v1,…,vn-1}表示具有n條邊的凸多邊形。 給定凸多邊形P,以及定義在由多邊形的邊和絃組成的三角形上的權函式w。要求確定該凸多邊形的三角剖分, 使得即該三角剖分中諸三角形上權之和為最小。 解題思路: 若凸(n+1)邊形P={v0,v1,…,vn}的最優三角剖分T包含三角形v0vkvn,1≤k≤n-1,則T的權為3個部分權的和: 三角形v0vkvn的權,子多邊形{v0,v1,…,vk}和{vk,vk+1,…,vn}的權之和。 可以斷言,由T所確定的這2個子多邊形的三角剖分也是最優的。 因為若有{v0,v1,…,vk}或{vk,vk+1,…,vn}的更小權的三角剖分將導致T不是最優三角剖分的矛盾。 那麼我們定義一個m[i][j],0<=i<=j<N,為凸子多邊形{vi,vi+1,…,vj}的最優三角剖分所對應的權函式值,即其最優值。 據此定義,要計算的凸(n+1)邊形P的最優權值為m[0][n]。 m[i][j]的值可以利用最優子結構性質遞迴地計算。當j-i>1時,凸子多邊形至少有3個頂點。 由最優子結構性質,m[i][j]的值應為m[i][k]的值加上m[k][j]的值,再加上三角形vivkvj的權值,其中i<k<j。 由於在計算時還不知道k的確切位置,而k的所有可能位置只有j-i個, 因此可以在這j-i個位置中選出使m[i][j]值達到最小的位置。 由此,m[i][j]可遞迴地定義為: m[i][j] = 0 (i == j || i+1 == j),只有一個或兩個點,不能構造三角形 m[i][j] = min{m[i][k] + m[k][j] + w(vi,vk,vj)} (i < k < j) 對於要求的m[0][n],可以用通過由下至上的,從鏈長(多邊形的邊)為2開始計算,每次求m[i][j]的最小值, 並且記錄最小值所對應的K值,根據最優子結構的性質,逐步向上就可以求出m[0][n]的最小值。 */ #include<iostream> using namespace std; int MinWeightTriangulation(int i, int j);//自頂向下,使用備忘錄陣列的動態規劃演算法 int MinWeightTriangulation_2(int n);//自底向上的動態規劃演算法 int GetWeight(int i, int k, int j);//計算三角形的周長 void PrintTriangulation(int i, int j); //輸出組成最優解的各個三角形的周長 const int N = 6; int w[N][N] = {{0,2,2,3,1,4}, {2,0,1,5,2,3}, {2,1,0,2,1,4}, {3,5,2,0,6,2}, {1,2,1,6,0,1}, {4,3,4,2,1,0}};//圖的鄰接矩陣 int m[N][N]; //m[i][j]表示多邊形{Vi-1VkVj}的最優權值 int s[N][N]; //s[i][j]記錄Vi-1到Vj最優三角剖分的中間點K int main(int argc, char **argv) { cout << MinWeightTriangulation(0, N-1) << endl; cout << MinWeightTriangulation_2(N-1) << endl; PrintTriangulation(0, N-1); //輸出組成最優解的各個三角形的周長 system("pause"); return 0; } int MinWeightTriangulation(int i, int j)//自頂向下,使用備忘錄陣列的動態規劃演算法 { if (m[i][j] != 0) //預設為0 return m[i][j]; if (i == j || i+1 == j) return 0; //先處理k=i+1的情形,注意我們取頂點vi為起點 m[i][j] = MinWeightTriangulation(i+1, j) + GetWeight(i, i+1, j); //MinWeightTriangulation(i, i)==0,就不寫了 s[i][j] = i+1; //再處理i+1<k<j的情形 for (int k=i+2; k<j; k++) { int t = MinWeightTriangulation(i, k) + MinWeightTriangulation(k, j) + GetWeight(i, k, j); if (t < m[i][j]) { m[i][j] = t; s[i][j] = k; } } return m[i][j]; } int MinWeightTriangulation_2(int n)//自底向上的動態規劃演算法 { m[n][n] = 0; for (int i=0; i<n; i++) //只有1個點或2個點均不能構成凸多邊形 m[i][i] = m[i][i+1] = 0; for (int r=2; r<n; r++)//r為當前計算的鏈長(子問題規模) { for (int i=0; i<=n-r; i++) //n-r為最後一個r鏈的前邊界 { int j = i + r; //計算前邊界為i,鏈長為r的鏈的後邊界j,注意我們取頂點vi為起點 //先處理k=i+1的情形 m[i][j] = m[i][i+1] + m[i+1][j] + GetWeight(i, i+1, j); s[i][j] = i+1; //再處理i+1<k<j的情形 for (int k=i+2; k<j; k++) { int t = m[i][k] + m[k][j] + GetWeight(i, k, j); if (t < m[i][j]) { m[i][j] = t; s[i][j] = k; } } // cout << "m["<<i<<"]["<<j<<"]= " << m[i][j] << " "; } //cout << endl; } return m[0][n]; } int GetWeight(int i, int k, int j)//計算三角形的周長 { return w[i][k] + w[k][j] + w[j][i]; } void PrintTriangulation(int i, int j) //輸出組成最優解的各個三角形的周長 { if (i == j || i+1 == j) return; PrintTriangulation(i, s[i][j]); cout << "V" << i << "-V" << s[i][j] << "-V" << j << " = " << GetWeight(i, s[i][j], j) << endl; PrintTriangulation(s[i][j], j); }
演算法2來自於王曉東老師編著的《計算機演算法設計與分析》,它與演算法1的基本思路是一樣的,但做了一些簡化處理,考慮到頂點數少於3個時是無法構成三角形的,演算法2設定退化的多邊形{vi-1,vi}具有權值為0,那我們在考慮最優解時,就不需要從頂點v0開始,而是從頂點v1開始,這樣要計算的凸(n+1)邊形P的最優權值為m[1][n]。
需要注意的是,我們在求m[i][j]的時候,是取頂點vi-1為起點的,所以計算三角形的權之和(周長)時,我們計算的是GetWeight(i-1, k, j)。這和演算法1是不一樣的。
總的來說,演算法1直觀,演算法2 簡潔。
#include<iostream> using namespace std; int MinWeightTriangulation(int i, int j);//自頂向下,使用備忘錄陣列的動態規劃演算法 int MinWeightTriangulation_2(int n);//自底向上的動態規劃演算法 int GetWeight(int i, int k, int j);//計算三角形的周長 void PrintTriangulation(int i, int j); //輸出組成最優解的各個三角形的周長 const int N = 6; int w[N][N] = {{0,2,2,3,1,4}, {2,0,1,5,2,3}, {2,1,0,2,1,4}, {3,5,2,0,6,2}, {1,2,1,6,0,1}, {4,3,4,2,1,0}};//圖的鄰接矩陣 int m[N][N]; //m[i][j]表示多邊形{Vi-1VkVj}的最優權值 int s[N][N]; //s[i][j]記錄Vi-1到Vj最優三角剖分的中間點K int main(int argc, char **argv) { cout << MinWeightTriangulation(1, N-1) << endl; cout << MinWeightTriangulation_2(N-1) << endl; PrintTriangulation(1, N-1); //輸出組成最優解的各個三角形的周長 system("pause"); return 0; } int MinWeightTriangulation(int i, int j)//自頂向下,使用備忘錄陣列的動態規劃演算法 { if (m[i][j] != 0) //預設為0 return m[i][j]; if (i == j) return 0; //先處理k=i的情形,注意我們取頂點vi-1為起點 m[i][j] = MinWeightTriangulation(i+1, j) + GetWeight(i-1, i, j); //MinWeightTriangulation(i, i)==0,就不寫了 s[i][j] = i; //再處理i<k<j的情形 for (int k=i+1; k<j; k++) { int t = MinWeightTriangulation(i, k) + MinWeightTriangulation(k+1, j) + GetWeight(i-1, k, j); if (t < m[i][j]) { m[i][j] = t; s[i][j] = k; } } return m[i][j]; } int MinWeightTriangulation_2(int n)//自底向上的動態規劃演算法 { for (int i=1; i<=n; i++) m[i][i] = 0; for (int r=2; r<=n; r++)//r為當前計算的鏈長(子問題規模) { for (int i=1; i<=n-r+1; i++) //n-r+1為最後一個r鏈的前邊界 { int j = i + r - 1; //計算前邊界為i,鏈長為r的鏈的後邊界j,注意我們取頂點vi-1為起點 //先處理k=i的情形 m[i][j] = m[i][i] + m[i+1][j] + GetWeight(i-1, i, j); s[i][j] = i; //再處理i<k<j的情形 for (int k=i+1; k<j; k++) { int t = m[i][k] + m[k+1][j] + GetWeight(i-1, k, j); if (t < m[i][j]) { m[i][j] = t; s[i][j] = k; } } } } return m[1][n]; } int GetWeight(int i, int k, int j)//計算三角形的周長 { return w[i][k] + w[k][j] + w[j][i]; } void PrintTriangulation(int i, int j) //輸出組成最優解的各個三角形的周長 { if (i == j) return; PrintTriangulation(i, s[i][j]); cout << "V" << i-1 << "-V" << s[i][j] << "-V" << j << " = " << GetWeight(i-1, s[i][j], j) << endl; PrintTriangulation(s[i][j]+1, j); }