动态规划专题(11):区间动态规划之三角剖分问题

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)进行分割:

  1. 将多边形分为:子多边形 [i, k] + 子多边形 [k, j] + 三角形 (i, k, j)

  2. 总权重 = dp[i][k] + dp[k][j] + weight(i,k) + weight(k,j) + weight(i,j)

  3. 枚举所有可能的 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. 算法验证

✅ 正确性验证

  1. 数学归纳法:证明对于任意n,算法都能找到最优解

  2. 四边形验证:手动计算两种方案的权重,与算法结果对比

  3. 边界测试:测试n=3,4,5的特殊情况

⚡ 性能测试

复制代码
复制代码

5. 扩展与优化

🔧 空间优化

  • 可以使用滚动数组减少空间复杂度

  • 只存储上三角矩阵,节约一半空间

🚀 性能优化

  • 使用四边形不等式优化到O(n²)

  • 并行化内层循环

  • 使用快速数学库优化计算

🔄 功能扩展

  1. 支持非凸多边形

  2. 支持3D空间中的三角剖分

  3. 支持实时动态更新权重

  4. 可视化剖分结果

相关推荐
joshchen2151 小时前
强化学习基础(赵世钰)第一章
人工智能·深度学习·算法·机器学习·强化学习
zhangrelay1 小时前
三分钟云课实践速通--C/C++程序设计--
linux·c语言·c++·笔记·学习·ubuntu
小此方1 小时前
Re:从零开始的 C++ STL篇(十二)深度解析哈希函数设计、负载因子调节与两种冲突处理策略
c++·算法·哈希算法
Karle_2 小时前
为AI编辑器准备c++编译环境,onnxruntime、cmake、cl,网上坑太多备份记录后续方便使用。
开发语言·c++·编辑器
lcj25112 小时前
【数据结构精讲】堆与二叉树从底层原理到代码落地:堆的构建 / 调整 / 排序 + 二叉树遍历 / 操作(附完整 C++ 源码 + LeetCode 题解)
数据结构·c++·leetcode
努力努力再努力wz2 小时前
【MySQL 进阶系列】C/C++ 如何通过客户端库访问 MySQL?从连接原理到 API 调用流程详解(附完整demo代码)
服务器·c语言·数据结构·数据库·c++·b树·mysql
xuhaoyu_cpp_java2 小时前
单调栈(算法)
java·数据结构·经验分享·笔记·学习·算法
CSCN新手听安2 小时前
【Qt】Qt窗口(七)QColorDialog颜色对话框,QFileDialog文件对话框的使用
开发语言·c++·qt
A charmer2 小时前
从 C++ 到 Objective-C:零基础平滑转学专栏【总目录】
开发语言·c++·objective-c