1. 程式人生 > >凸多邊形最優三角剖分的兩種演算法分析

凸多邊形最優三角剖分的兩種演算法分析

/*
  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);
}