2026.05.05
1. 概念介绍
1. 问题定义与背景
📋 问题描述
给定一个凸n边形,将其划分为n-2个互不相交的三角形,使得所有三角形的权重之和最小。
🎯 应用场景
-
计算机图形学中的网格生成
-
有限元分析中的区域划分
-
计算几何中的近似算法
-
游戏开发中的地形分割
2. 算法思路
✅ 第一步:确定状态 ------ 明确 dp[i][j] 的含义
dp[i][j]表示:从顶点 i 到顶点 j 围成的凸多边形(顶点顺序为 i, i+1, ..., j)进行最优三角剖分的最小权重和。
👉 通俗讲:我们要将多边形 i→i+1→...→j 用不相交的对角线分割成三角形,使得所有三角形的权重之和最小。
✅ 第二步:划分阶段 ------ 动态规划的遍历顺序
按"子多边形大小从小到大"的顺序处理。先处理最小的三角形,再处理更大的多边形。
👉 按区间长度从3(三角形)开始,逐步增加到n(整个多边形)。因为大区间的计算依赖于小区间的结果。
✅ 第三步:决策选择 ------ 核心动态规划策略
对于子多边形 [i, j],选择一个中间点 k(i < k < j)进行分割:
-
将多边形分为:子多边形 [i, k] + 子多边形 [k, j] + 三角形 (i, k, j)
-
总权重 = dp[i][k] + dp[k][j] + weight(i,k) + weight(k,j) + weight(i,j)
-
枚举所有可能的 k,取最小值
📌 关键点解释:
为什么是 weight(i,k) + weight(k,j) + weight(i,j)?因为这些是三角形 (i, k, j) 的三条边,每条边都有对应的权重。
✅ 第四步:边界条件 ------ 基础情况的处理
-
当 j = i+1(相邻顶点):
dp[i][j] = 0(没有三角形,不需要剖分) -
当 j = i+2(三角形):
dp[i][j] = weight(i,i+1) + weight(i+1,j) + weight(i,j)(本身就是一个三角形)
✅ 第五步:求解目标 ------ 最终答案
dp[0][n-1]就是整个 n 边形的最优三角剖分最小权重和。通过回溯记录的分割点,可以还原出剖分方案。
🧮 状态转移方程
dp[i][j] =
⎧ 0, j = i+1
⎨ w(i,i+1)+w(i+1,j)+w(i,j), j = i+2
⎩ min_{i<k<j} { dp[i][k] + dp[k][j] + w(i,k) + w(k,j) + w(i,j) }, 其他
📊 时间复杂度分析
-
三重循环:O(n³)
-
空间复杂度:O(n²)
-
对于n=4,时间复杂度为O(1),可即时计算
3.代码实例
求解凸多边形的最优三角剖分。
代码实例1
求解最优三角剖分(多边形剖分后权重之和最小)
/dp_interval_00_triangulation_00_min_val.cc
cpp
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
int Triangulation(vector<vector<int>> & G)
{
int n = G.size() - 1; // [0]不使用,表示占位
// dp[i][j]:顶点 i 到 j 构成的子多边形(连续顶点序列)的最小三角剖分权值和
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
// d = j - i,表示顶点个数差(j - i),至少为2才能构成三角形(3个顶点)
for (int d = 2; d < n; ++d) {
// 遍历三角想剖分的起点
for (int i = 1; i <= n - d; ++i) {
int j = i + d; // 终点
dp[i][j] = INT_MAX; // 求解最小值时需要将dp数组初始化为最大值
// 尝试所有可能的顶点k,将多边形(i...j)划分为(i..k)和(k..j)
for (int k = i + 1; k < j; ++k) {
int cost = dp[i][k] + dp[k][j] + G[i][k]+ G[k][j] + G[i][j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}
// 最终结果为顶点1到n构成的多边形的三角剖分最小权值之和
return dp[1][n];
}
void MatrixPrint(const vector<vector<int>> & matrix)
{
cout << "权重矩阵 : " << endl;
// 列号
cout << "\t" << " ";
for (int j = 1; j < matrix.size(); ++j) {
cout << "顶点" << j << "\t";
}
cout << endl;
// 行号
for (int i = 1; i <= matrix.size() - 1; ++i) {
cout << "顶点 v" << i << " : ";
for (int j = 1; j <= matrix.size() - 1; ++j) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
}
int main()
{
cout << "输入多边形定点数 : ";
int n;
cin >> n;
if (n < 3) {
cout << "输入错误,这不是一个多边形" << endl;
return 1;
}
// 邻接矩阵, 顶点编号从1开始,[0]表示占位,不用
vector<vector<int>> G(n + 1, vector<int>(n + 1, 0));
int u, v, w;
cout << "输入各个顶点之间的权重 (顶点1, 顶点2,权重值) : " << endl;
while (true) {
cin >> u >> v >> w;
if (u <= 0 || v <= 0) {
break;
}
G[u][v] = w;
G[v][u] = w;
}
// 打印邻接矩阵
MatrixPrint(G);
// 最优三角剖分计算最小权重
cout << "最小权重之和 : " << Triangulation(G) << endl;
return 0;
}
// 测试
// 输入多边形定点数 : 4
// 输入各个顶点之间的权重 (顶点1, 顶点2,权重值) :
// 1 2 1
// 1 3 5
// 1 4 1
// 2 3 1
// 2 4 6
// 3 4 1
// 0 0 0
// 权重矩阵 :
// 顶点1 顶点2 顶点3 顶点4
// 顶点 v1 : 0 1 5 1
// 顶点 v2 : 1 0 1 6
// 顶点 v3 : 5 1 0 1
// 顶点 v4 : 1 6 1 0
// 最小权重之和 : 14
// e@e-ubuntu:~/ProjTest$ ./a
// 输入多边形定点数 : 4
// 输入各个顶点之间的权重 (顶点1, 顶点2,权重值) :
// 1 2 2
// 1 3 5
// 1 4 2
// 2 3 2
// 2 4 4
// 3 4 2
// 0 0 0
// 权重矩阵 :
// 顶点1 顶点2 顶点3 顶点4
// 顶点 v1 : 0 2 5 2
// 顶点 v2 : 2 0 2 4
// 顶点 v3 : 5 2 0 2
// 顶点 v4 : 2 4 2 0
// 最小权重之和 : 16
测试:


实例代码2:
求解并打印剖分方案: /dp_interval_00_triangulation_01_min_val_path.cc
cpp
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
// 动态规划求解最小权重和,并记录最优分割点
int Triangulation(vector<vector<int>> &G, vector<vector<int>> &split) {
int n = G.size() - 1; // 顶点编号 1..n
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
split.assign(n + 1, vector<int>(n + 1, -1)); // 初始化分割点
// d = j - i,区间长度(顶点数差)
for (int d = 2; d < n; ++d) { // 至少需要 3 个顶点才能构成三角形
for (int i = 1; i <= n - d; ++i) {
int j = i + d;
dp[i][j] = INT_MAX;
for (int k = i + 1; k < j; ++k) {
int cost = dp[i][k] + dp[k][j] + G[i][k] + G[k][j] + G[i][j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
split[i][j] = k; // 记录最优分割点
}
}
}
}
return dp[1][n];
}
// 递归打印多边形 (i..j) 的剖分对角线
void printPartition(int i, int j, const vector<vector<int>> &split) {
if (j - i <= 1) return; // 少于 3 个顶点,无内部对角线
if (j - i == 2) return; // 恰好 3 个顶点,构成三角形,无内部对角线
int k = split[i][j];
// 对角线 (i, k) 如果不是多边形的边,则输出
if (k - i > 1) {
cout << "Diagonal: (" << i << ", " << k << ")" << endl;
}
// 对角线 (k, j) 如果不是多边形的边,则输出
if (j - k > 1) {
cout << "Diagonal: (" << k << ", " << j << ")" << endl;
}
// 递归处理两个子多边形
printPartition(i, k, split);
printPartition(k, j, split);
}
int main() {
cout << "输入多边形顶点个数 : ";
int n;
cin >> n;
// 邻接矩阵,顶点编号 1..n
vector<vector<int>> G(n + 1, vector<int>(n + 1, 0));
int u, v, w;
cout << "输入各边权重 (顶点1 顶点2 权重),以 0 0 0 结束:\n";
while (true) {
cin >> u >> v >> w;
if (u == 0 || v == 0) break;
G[u][v] = G[v][u] = w;
}
vector<vector<int>> split; // 存放最优分割点
int minWeight = Triangulation(G, split);
cout << "\n最小权重之和 : " << minWeight << endl;
if (n >= 3) {
cout << "\n最优剖分方案(内部对角线):" << endl;
printPartition(1, n, split);
}
return 0;
}
// 输入多边形顶点个数 : 5
// 输入各边权重 (顶点1 顶点2 权重),以 0 0 0 结束:
// 1 2 1
// 1 3 2
// 1 4 3
// 1 5 2
// 2 3 1
// 2 4 3
// 2 5 6
// 3 4 1
// 3 5 2
// 4 5 6
// 0 0 0
// 最小权重之和 : 19
// 最优剖分方案(内部对角线):
// Diagonal: (1, 3)
// Diagonal: (3, 5)
测试:

4. 算法验证
✅ 正确性验证
-
数学归纳法:证明对于任意n,算法都能找到最优解
-
四边形验证:手动计算两种方案的权重,与算法结果对比
-
边界测试:测试n=3,4,5的特殊情况
⚡ 性能测试
5. 扩展与优化
🔧 空间优化
-
可以使用滚动数组减少空间复杂度
-
只存储上三角矩阵,节约一半空间
🚀 性能优化
-
使用四边形不等式优化到O(n²)
-
并行化内层循环
-
使用快速数学库优化计算
🔄 功能扩展
-
支持非凸多边形
-
支持3D空间中的三角剖分
-
支持实时动态更新权重
-
可视化剖分结果