1. 程式人生 > 其它 >動態規劃---例題5.凸多邊形最優三角剖分問題

動態規劃---例題5.凸多邊形最優三角剖分問題

動態規劃---例題5.凸多邊形最優三角剖分問題


一.題目描述

通常,用多邊形頂點的序列來表示一個凸多邊形,即P=<v0 ,v1 ,… ,vn-1>表示具有n條邊v0v1,v1v2,… ,vn-1vn的一個凸多邊形,其中,約定v0 = vn 。
若vi與vj是多邊形上不相鄰的兩個頂點,則線段vivj稱為多邊形的一條弦。弦將多邊形分割成凸的兩個子多邊形<vi ,vi+1 ,… ,vj>和<vj ,vj+1 ,… ,vi>。多邊形的三角剖分是一個將多邊形分割成互不重迭的三角形的弦的集合T

如上圖為一個凸多邊形的兩個不同的三角剖分.n在凸多邊形P的一個三角剖分T中,各弦互不相交且弦數已達到最大,即P的任一不在T中的弦必與T中某一弦相交。

有n個頂點的凸多邊形的三角剖分中,恰好有n-3條弦和n-2個三角形。
凸多邊形最優三角剖分的問題是:給定一個凸多邊形P=<v0,v1,… ,vn-1>以及定義在由多邊形的邊和絃組成的三角形上的權函式ω。要求確定該凸多邊形的一個三角剖分,使得該三角剖分對應的權即剖分中諸三角形上的權之和為最小!

可以定義三角形上各種各樣的權函式W。例如:定義 ω(△vivjvk) = |vivj| + |vivk| + |vkvj|,其中,|vivj|是點vi到vj的歐氏距離。相應於此權函式的最優三角剖分即為最小弦長三角剖分。
注意:解決此問題的演算法必須適用於任意的權函式。

二.解題思路

這是計算幾何學問題,但在本質上與矩陣連乘積的最優計算次序問題極為相似。可用動態規劃演算法。

1. 三角剖分的結構及其相關問題

凸多邊形的三角剖分與表示式的完全加括號方式之間具有十分緊密的聯絡。正如矩陣連乘積的最優計算次序問題等價於矩陣鏈的完全加括號方式.這些問題之間的相關性可從它們所對應的完全二叉樹的同構性看出。所謂完全二叉樹是指葉結點以外的所有結點的度數都為2的二叉樹。

一個表示式的完全加括號方式對應於一棵完全二叉樹,這棵二叉樹叫作表示式的語法樹。
例:與完全加括號的矩陣連乘積((A1(A2A3))(A4(A5A6)))對應的語法樹如圖(a)。

葉結點:表示式中一個原子。在語法樹中,表示式(ELEr)對應一棵子樹,其左子樹表示表示式EL,其右子樹表示表示式ER。有n個原子的完全加括號表示式與一棵有n個葉結點的語法樹一一對應。

凸多邊形<v0 ,v1 ,… ,vn-1>的三角剖分也可以用語法樹來表示。例如,圖3(a)中凸多邊形的三角剖分可用圖(b)所示的語法樹來表示。該語法樹的根結點為邊v0v6,三角剖分中的弦組成其餘的內部結點。

除v0v6以外的邊是葉結點。樹根v0v6是三角形v0v3v6的一條邊,該三角形將原多邊形分為3個部分:一個三角形,兩個凸多邊形.
三角形v0v3v6的另外兩條邊,即弦v3v6和v0v3為根的兩個兒子。以它們為根的子樹分別表示凸多邊形<v0 ,v1 ,… ,v3>和凸多邊形<v3 ,v4 ,… ,v6>的三角剖分

一個凸n邊形的三角剖分與n-1個葉子的語法樹一一對應。由於n個矩陣的完全加括號乘積與n個葉子的語法樹之間存在一一對應關係,因此n個矩陣的完全加括號乘積也與凸(n+1)邊形的三角剖分之間存在一一對應關係。上圖中(a)和(b)表示出了這種對應關係,這時n=6。矩陣連乘積A1A2..A6中的每個矩陣Ai對應於凸(n+1)邊形中的一條邊vi-1vi。三角剖分中的一條弦vivj,i<j-1,對應於矩陣連乘積Ai+1..j 。

n矩陣連乘積的最優計算次序問題是凸多邊形最優三角剖分問題的一個特殊情形。

對於給定的矩陣鏈A1A2..An,定義與之相應的凸(n+1)邊形P=<v0 ,v1 ,… ,vn>,使得矩陣Ai與凸多邊形的邊vi-1vi一一對應。若矩陣Ai的維數為pi-1×pi , i=1,2,…,n,則定義三角形vivjvk上的權函式值為: ω(△vivjvk)=pipjpk。
依此權函式的定義,凸多邊形P的最優三角剖分所對應的語法樹給出矩陣鏈A1A2..An的最優完全加括號方式。

2.最優子結構性質

凸多邊形的最優三角剖分問題有最優子結構性質。

若凸(n+1)邊形P=<v0 ,v1 ,… ,vn>的一個最優三角剖分T包含三角形v0vkvn , 1≤k≤n-1,則T的權為3個部分權的和,即三角形v0vkvn的權,子多邊形<v0 ,v1 ,… ,vk>的權和<vk ,vk+1 ,… ,vn>的權之和。由T所確定的這兩個子多邊形的三角剖分也是最優的,因為若有<v0 ,v1 ,… ,vk>或<vk ,vk+1 ,… ,vn>的更小權的三角剖分,將會導致T不是最優三角剖分的矛盾。

3.最優三角剖分的遞迴結構

定義t[i,j],1≤i<j≤n,為凸子多邊形<vi-1 ,vi ,… ,vj>的最優三角剖分所對應的權值,即最優值。
設退化的多邊形<Vi-1 ,vi>權值為0,那麼凸(n+1)邊形對應的權的最優值為t[1,n]。
t[i,j]的值可以利用最優子結構性質遞迴地計算。由於退化的2頂點多邊形的權值為0,所以t[i,i]=0,i=1,2,…,n 。當j-i≥1時,子多邊形<vi-1 ,vi ,… ,vj>至少有3個頂點。

由最優子結構性質,t[i,j]的值應為t[i,k]的值加上t[k+1,j]的值,再加上△vi-1vkvj的權值,並在i≤k≤j-1的範圍內取最小。由此,t[i,j]可遞迴地定義為:

4.計算最優值

上式與矩陣連乘積的最優計算次序問題中計算m[i,j]的公式(P40)幾乎完全一樣(除了權函式的定義外),因此,對計算m[i,j]的演算法Matrix_Chain略做修改就可用於計算t[i,j]。

計算凸(n+1)邊形P=<v0 ,v1 ,… ,vn>的三角剖分最優權值的動態規劃演算法,輸入是凸多邊形P=<v0 ,v1 ,… ,vn>的權函式ω,輸出是最優值t[i,j]和使得t[i,k]+t[k+1,j]+ω(△vi-1vkvj)達到最優的位置(k=)s[i,j],1≤i≤j≤n 。

5.構造最優三角剖分

對於任意的1≤i≤j≤n ,上述演算法在計算每一個子多邊形<vi-1 ,vi ,… ,vj>的最優三角剖分所對應的權值t[i,j]的同時,還在s[i,j]中記錄了此最優三角剖分中與邊(或弦)vi-1vj構成的三角形的第三個頂點的位置。

因此,利用最優子結構性質並藉助於s[i,j],1≤i≤j≤n ,凸(n+l)邊形P=<v0 ,v1 ,… ,vn>的最優三角剖分可容易地在Ο(n)時間內構造出來

程式碼如下:

// 凸多邊形最優三角剖分
// 與矩陣連乘一樣的思路。矩陣連乘的最優計算次序問題是凸多邊形最優三角剖分問題的特殊情形
#include<bits/stdc++.h>
using namespace std;

const int N = 7; //凸多邊形邊數+1
int weight[][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  MinWeightTriangulation(int n, int **t, int **s);
void Traceback(int i, int j, int **s);  //構造最優解
int Weight(int a, int b, int c);    //權函式

int main()
{
    int **s = new int *[N];
    int **t = new int *[N];
    for(int i=0; i<N; i++)
    {
        s[i] = new int[N];
        t[i] = new int[N];
    }
    cout<<"此多邊形的最有三角剖分值為:"<<MinWeightTriangulation(N-1, t, s)<<endl;
    cout<<"最優三角剖分結構為:"<<endl;
    Traceback(1, 5, s);//s[i][j]記錄了Vi-1和Vj構成三角形的第三個頂點的位置
    system("pause");
    return 0;
}
int MinWeightTriangulation(int n, int **t, int **s)
{
    for(int i=1; i<=n; ++i) t[i][i] = 0;
    for(int r=2; r<=n; ++r)  //r表示凸子多邊形的頂點數
        for(int i=1; i<=n-r+1; ++i)
        {
            int j = i+r-1;
            t[i][j] = t[i+1][j] + Weight(i-1, i, j);
            s[i][j] = i;
            for(int k=i+1; k<i+r-1; k++)
            {
                int u = t[i][k] + t[k+1][j] + Weight(i-1, k, j);
                if(u<t[i][j])
                {
                    t[i][j] = u;
                    s[i][j] = k;
                }
            }
        }
    return  t[1][N-2];
}
void Traceback(int i, int j, int **s)
{
    if(i==j) return;
    Traceback(i, s[i][j], s);
    Traceback(s[i][j]+1, j, s);
    cout<<"三角剖分頂點:V"<<i-1<<",V"<<j<<",V"<<s[i][j]<<endl;
}
int Weight(int a, int b, int c)
{
    return weight[a][b] + weight[b][c] + weight[a][c];
}

執行結果:

本篇文章參考我的老師畢方明《演算法設計與分析》課件.
歡迎大家訪問我的個人部落格 --- 喬治的程式設計小屋,和我一起為大廠offer努力吧!