问题相关定义:
(1)凸多边形的三角剖分:将凸多边形分割成互不相交的三角形的弦的集合T。
(2)最优剖分:给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数w。要求确定该凸多边形的三角剖分,使得该三角剖分中诸三角形上权之和为最小。
凸多边形三角剖分如下图所示:
相关性质
在凸多边形P的一个三角形部分T中,各弦互不相交,且弦数已达到最大,即P的任一不在T中的弦必与T中某一弦相交。在一个有n个顶点的凸多边形的三角部分中,恰好有n-3条弦和n-2个三角形。
最优子结构性质
若凸(n+1)边形 的更小权的三角剖分,将导致T不是最优三角剖分的矛盾。因此,凸多边形的三角剖分问题具有最优子结构性质。
子问题重叠性质
子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。
动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
显然凸多边形的最优三角划分具有子问题重叠性质。例如下面的示例:
在求解该六边形的最优三角划分时,会包含子问题的权。
递推关系辅
设t[i][j],1<=i < j<=n 为凸多边形{Vi-1,Vi……Vj}的最优三角剖分所对应的权值函数值,即其最优值。最优剖分包含三角形Vi-1VkVj的权,子多边形{Vi-1,Vi……Vk}的权,子多边形{Vk,Vk+1……Vj}的权之和。
因此,可得递推关系式:
凸(n+1)边形P的最优权值为t[1][n]。
程序清单
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++)//n-r+1为最后一个r链的前边界
{
int j = i+r-1;//计算前边界为r,链长为r的链的后边界
t[i][j] = t[i+1][j] + Weight(i-1,i,j);//将链ij划分为A(i) * ( A[i+1:j] )这里实际上就是k=i
s[i][j] = i;
for(int k=i+1; k<j; k++) {
//将链ij划分为( A[i:k] )* (A[k+1:j])
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]; //(N为凸多边形的边数加1)
}
//时间复杂度为O(n3)
凸多边形的最优三角划分和矩阵链乘法的同构
凸多边形的三角剖分与矩阵链乘法完全加括号方式之间具有十分紧密的联系。正如所看到过的,矩阵连乘积的最优计算次序问题等价于矩阵链的完全加括号方式。这些问题之间的相关性可从它们所对应的完全二叉树的同构性看出。
一个表达式的完全加括号方式对应于一棵完全二叉树,人们称这棵二叉树为表达式的语法树。例如,与完全加括号的矩阵链乘积((A1(A2A3))(A4(A5A6)))相对应的语法树如图2(a)所示。
表达式语法树与三角剖分的对应
图1(a)中凸多边形的三角剖分可用图2(b)所示的语法树来表示。该语法树的根结点为边v0v6,三角剖分中的弦组成其余的内部结点。多边形中除v0v6边外的每一条边是语法树的一个叶结点。树根v0v6是三角形v0v3v6的一条边,该三角形将原多边形分为3个部分:三角形v0v3v6,凸多边形{v0 ,v1 ,… ,v3}和凸多边形{v3 ,v4 ,… ,v6}。三角形v0v3v6的另外两条边,即弦v3v6和v0v3为根的两个儿子。以它们为根的子树分别表示凸多边形{v0 ,v1 ,… ,v3}和凸多边形{v3 ,v4 ,… ,v6}的三角剖分。
举例
Example:{V0,V1,V2,V3,V4,V5}={{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}}//凸多边形的权值
A:i=j(i=1;i<=n;i++) t[i][j]=0;
B:k=i(i=1;i<=n;i++)
(a)r=2 时j=i=r-1
t[1][2]=t[1][1]+t[2][2]+w(0,1,2)=0+0+5=5
t[2][3]=t[2][2]+t[3][3]+w(1,2,3)=8
t[3][4]=t[3][3]+t[4][4]+w(2,3 ,4)=9
t[4][5]=t[4][4]+t[5][5]+w(3,4,5)=9
(b)r=3时j=i+r-1
t[1][3]=t[1][1]+t[2][3]+w(0,1,3)=18
t[2][4]=t[2][2]+t[3][4]+w(1,2,4)=13 (v2v3v4和v1v2v4)
t[3][5]=t[3][3]+t[4][5]+w(2,3,5)=17
(c)r=4时j=i+r-1
t[1][4]=t[1][1]+t[2][4]+w(0,1,4)=18 (v0v1v4和v2v3v4和v1v2v4)
t[2][5]=t[2][2]+t[3][5]+w(1,2,5)=25 (v2v3v4和v2v4v5和v1v2v5)
C:k=i+1
r=3
t[1][3]=t[1][2]+t[3][3]+w(0,2,3)=12 (v0v1v2和v0v2v3)
t[2][4]=t[2][3]+t[4][4]+w(1,3,4)=21
t[3][5]=t[3][4]+t[5][5]+w(2,4,5)=15 (v2v3v4和v2v4v5)
r=4
t[1][4]=t[1][2]+t[3][4]+w(0,2,4)=19
t[2][5]=t[2][3]+t[4][5]+w(1,3,5)=27
当k=i时
t[1][5]=t[1][1]+t[2][5]+w(0,1,5)=34
当k=i+1时
t[1][5]=t[1][2]+t[3][5+w(0,2,5)=30
t[1][5]=t[1][3]+t[4][5]+w(0,3,5)=30
t[1][5]=t[1][4]+t[5][5]+w(0,4,5)=18+6=24 (v0v1v4和v2v3v4和v1v2v4和v0v4v5)
上图:
思想:
其实和矩阵链相乘思想一模一样,都是利用动态规划的最优子结构,即将n个矩阵链相乘,
•将这个计算次序在矩阵A(k)和A(k+1)之间将矩阵链断开,则生成两个矩阵链A1 .. Ak 和Ak+1 .. An 。计算A1 .. An的最优次序所包含的计算矩阵子链 A1 .. Ak和Ak+1 .. An的次序也是最优的。•将这个计算次序在矩阵A(k)和A(k+1)之间将矩阵链断开,则生成两个矩阵链A1 .. Ak 和Ak+1 .. An 。计算A1 .. An的最优次序所包含的计算矩阵子链 A1 .. Ak和Ak+1 .. An的次序也是最优的。
•将矩阵连乘积A(i)A(i+1)…A(j)简记为A[i:j],设计算A[i:j](1 <= i <= j <= n)所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]
•当i = j时,A[i:j]=Ai,因此,m[i,i] = 0,i = 1,2,…,n
•当i < j时,m[i,j] = m[i,k] + m[k+1,j] + p(i-1)p(k)p(j)
•这里A(i)的维数为p(i-1)*p (i)(注:p(i-1)为矩阵A(i)的行数,p(i)为矩阵A[i]的列数)
•可以递归地定义m[i,j]为:
伪代码如下:
Algorithm MATRIX-CHAIN-ORDER(p)
n ← length[p] – 1 //n个矩阵
for i ← 1 to n
m[i, i] ← 0
end for
for l ← 2 to n ▹l is the chain length.
for i ← 1 to n – (l – 1)
j ← i + (l – 1)
m[i, j] ← ∞
for k ← i to j - 1
q ← m[i, k] + m[k + 1, j] + p[i-1] p[k]p[j] //矩阵乘法次数
if q < m[i, j] //最小数乘次数
then m[i, j] ← q, s[i, j] ← k
end if
end for
end for
end for
return m and s
例子:
The length of chain is 5, and the dimmensions is:
A1 5 × 10, A2 10 × 4, A3 4 × 6, A4 6 × 10, A5 10 × 2
代码如下:
1 // MinWeightTriangulation.cpp: 定义控制台应用程序的入口点。
2 //
3
4 //3d5 凸多边形最优三角剖分
5 #include "stdafx.h"
6 #include <iostream>
7 using namespace std;
8
9 const int N = 7;//凸多边形边数+1
10 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 } };//凸多边形的权
11
12 int MinWeightTriangulation(int n, int **t, int **s);
13 void Traceback(int i, int j, int **s);//构造最优解
14 int Weight(int a, int b, int c);//权函数
15
16 int main()
17 {
18 int **s = new int *[N];
19 int **t = new int *[N];
20 for (int i = 0; i<N; i++)
21 {
22 s[i] = new int[N];
23 t[i] = new int[N];
24 }
25
26 cout << "此多边形的最优三角剖分权值为:" << MinWeightTriangulation(N - 1, t, s) << endl;
27 cout << "最优三角剖分结构为:" << endl;
28 Traceback(1, 5, s); //s[i][j]记录了Vi-1和Vj构成最优三角形的第3个顶点的位置
29
30 return 0;
31 }
32
33 int MinWeightTriangulation(int n, int **t, int **s)
34 {
35 for (int i = 1; i <= n; i++)
36 {
37 t[i][i] = 0;
38 }
39 for (int r = 2; r <= n; r++) //r为当前计算的链长(子问题规模),即vi...vj的长度
40 {
41 for (int i = 1; i <= n - r + 1; i++)//n-r+1为最后一个r链的前边界
42 {
43 int j = i + r - 1;//计算前边界为r,链长为r的链的后边界
44
45 t[i][j] = t[i + 1][j] + Weight(i - 1, i, j);//将链ij划分为A(i) , ( A[i+1:j] )这里实际上就是k=i,
46
47 s[i][j] = i;
48
49 for (int k = i + 1; k<j; k++)
50 {
51 //将链ij划分为( A[i:k] ),(A[k+1:j])
52 int u = t[i][k] + t[k + 1][j] + Weight(i - 1, k, j);
53 if (u<t[i][j])
54 {
55 t[i][j] = u;//记录最小的权值
56 s[i][j] = k;//记录划分三角形的顶点,s[i][j]记录了Vi-1和Vj构成最优三角形的第3个顶点的位置
57 }
58 }
59 }
60 }
61 return t[1][N - 2];//t[1][5]
62 }
63
64 void Traceback(int i, int j, int **s)
65 {
66 if (i == j) return;
67 Traceback(i, s[i][j], s);//先回溯vi-1,vk的子问题
68 Traceback(s[i][j] + 1, j, s);//再回溯v(k+1),vj的子问题
69 cout << "三角剖分顶点:V" << i - 1 << ",V" << j << ",V" << s[i][j] << endl;
70 }
71
72 int Weight(int a, int b, int c)
73 {
74 return weight[a][b] + weight[b][c] + weight[a][c];
75 }
参考文章: https://blog.csdn.net/zhao2018/article/details/83189895?utm_source=blogxgwz8