文章目录
- 习题一:矩阵链乘法问题------动态规划
-
- [📖 问题描述:找到最优的矩阵乘法顺序](#📖 问题描述:找到最优的矩阵乘法顺序)
- 正确思路:动态规划
-
- [1️⃣ 问题分解的逻辑](#1️⃣ 问题分解的逻辑)
- [2️⃣ 递推关系:自底向上计算](#2️⃣ 递推关系:自底向上计算)
- [3️⃣ 计算过程:第n层使用n-1层的结果](#3️⃣ 计算过程:第n层使用n-1层的结果)
- [4️⃣ 两个表格:记录最优值和最优解](#4️⃣ 两个表格:记录最优值和最优解)
-
- [表格1: m i , j mi,j mi,j - 记录最小乘法次数](#表格1: m [ i , j ] m[i,j] m[i,j] - 记录最小乘法次数)
- [表格2: s i , j si,j si,j - 记录最优分割位置](#表格2: s [ i , j ] s[i,j] s[i,j] - 记录最优分割位置)
- [🔍 算法执行过程示例](#🔍 算法执行过程示例)
-
- [步骤1:初始化(长度 l = 1)](#步骤1:初始化(长度 l = 1))
- [步骤2:计算长度为2的链(l = 2)](#步骤2:计算长度为2的链(l = 2))
- [步骤3:计算长度为3的链(l = 3)](#步骤3:计算长度为3的链(l = 3))
- [步骤4:计算长度为4的链(l = 4)](#步骤4:计算长度为4的链(l = 4))
- [步骤5:计算长度为5的链(l = 5)](#步骤5:计算长度为5的链(l = 5))
- 最终表格
- 回溯构造最优解
-
- [第1步:处理整个链 1 , 5 1,5 1,5](#第1步:处理整个链 [ 1 , 5 ] [1,5] [1,5])
- [第2步:递归处理左部分 1 , 4 1,4 1,4](#第2步:递归处理左部分 [ 1 , 4 ] [1,4] [1,4])
- [第3步:递归处理 2 , 4 2,4 2,4](#第3步:递归处理 [ 2 , 4 ] [2,4] [2,4])
- 第4步:简化括号
- [⚠️ 常见错误与注意事项](#⚠️ 常见错误与注意事项)
- [📊 算法复杂度分析](#📊 算法复杂度分析)
- [💡 拓展思考](#💡 拓展思考)
- 习题2:钢条切割问题------动态规划
-
- [📖 问题描述:最大化钢条切割收益](#📖 问题描述:最大化钢条切割收益)
- 正确思路:动态规划
-
- [1️⃣ 问题分解的逻辑](#1️⃣ 问题分解的逻辑)
- [2️⃣ 递推关系:自底向上计算](#2️⃣ 递推关系:自底向上计算)
- [3️⃣ 计算过程:第k层使用1到k-1层的结果](#3️⃣ 计算过程:第k层使用1到k-1层的结果)
- [4️⃣ 两个表格:记录最优值和最优切割策略](#4️⃣ 两个表格:记录最优值和最优切割策略)
-
- [表格1: R k Rk Rk - 记录最大收益](#表格1: R [ k ] R[k] R[k] - 记录最大收益)
- [表格2: C k Ck Ck - 记录最优切割点](#表格2: C [ k ] C[k] C[k] - 记录最优切割点)
- [💻 算法伪代码](#💻 算法伪代码)
- [🔍 算法执行过程示例](#🔍 算法执行过程示例)
-
- [步骤1:初始化(长度 k = 1)](#步骤1:初始化(长度 k = 1))
- [步骤2:计算长度 k = 2](#步骤2:计算长度 k = 2)
- [步骤3:计算长度 k = 3](#步骤3:计算长度 k = 3)
- [步骤4:计算长度 k = 4](#步骤4:计算长度 k = 4)
- [步骤5:计算长度 k = 5](#步骤5:计算长度 k = 5)
- [步骤6:计算长度 k = 6](#步骤6:计算长度 k = 6)
- 最终表格
- 回溯构造最优切割方案
- 回溯算法伪代码
- [⚠️ 常见错误与注意事项](#⚠️ 常见错误与注意事项)
- [📊 算法复杂度分析](#📊 算法复杂度分析)
- [💡 拓展思考](#💡 拓展思考)
习题一:矩阵链乘法问题------动态规划
⏱️ 预计阅读时间 :20-25分钟
🎯 学习目标:掌握如何用动态规划解决矩阵链乘法问题,理解动态规划的两步:计算最优值和记录最优解
📖 问题描述:找到最优的矩阵乘法顺序


注意:
矩阵连乘分为两部分,首先是根据递推公式计算最小乘法次数,然后需要记录最优分割位置。求解过程需要体现以上两部分
正确思路:动态规划
动态规划的核心思想:将大问题分解为重叠子问题,自底向上求解,避免重复计算。
1️⃣ 问题分解的逻辑
核心观察 :计算矩阵链 A i × A i + 1 × ⋯ × A j A_i \times A_{i+1} \times \cdots \times A_j Ai×Ai+1×⋯×Aj 的最优方式,必然在某个位置 k k k 进行分割。
分解思路:
-
选择一个分割点 k k k( i ≤ k < j i \leq k < j i≤k<j),将矩阵链分成两部分:
- 左子链 : A i × A i + 1 × ⋯ × A k A_i \times A_{i+1} \times \cdots \times A_k Ai×Ai+1×⋯×Ak
- 右子链 : A k + 1 × A k + 2 × ⋯ × A j A_{k+1} \times A_{k+2} \times \cdots \times A_j Ak+1×Ak+2×⋯×Aj
-
最优子结构性质:
- 如果整个链的计算方式是最优的,那么左子链和右子链的计算方式也必然是最优的
- 否则,可以用更优的子链计算方式替换,得到更优的整体方案(矛盾)
-
总成本分解 :
总成本 = 左子链成本 + 右子链成本 + 合并成本 \text{总成本} = \text{左子链成本} + \text{右子链成本} + \text{合并成本} 总成本=左子链成本+右子链成本+合并成本其中:
- 左子链成本 :计算 A i × ⋯ × A k A_i \times \cdots \times A_k Ai×⋯×Ak 的最少乘法次数
- 右子链成本 :计算 A k + 1 × ⋯ × A j A_{k+1} \times \cdots \times A_j Ak+1×⋯×Aj 的最少乘法次数
- 合并成本 :将两个结果矩阵相乘的代价 = p i − 1 × p k × p j p_{i-1} \times p_k \times p_j pi−1×pk×pj
- 左子链结果维度: p i − 1 × p k pi-1 \times pk pi−1×pk
- 右子链结果维度: p k × p j pk \times pj pk×pj
- 合并乘法次数: p i − 1 × p k × p j pi-1 \times pk \times pj pi−1×pk×pj
-
尝试所有分割点:
- 由于不知道哪个 k k k 是最优的,需要尝试所有可能的分割点
- 选择总成本最小的那个
p p p 数组说明 : p p p 是维度数组,矩阵 A i A_i Ai 的维度是 p i − 1 × p i pi-1 \times pi pi−1×pi。例如: p = 6 , 11 , 7 , 15 , 3 , 21 p = 6, 11, 7, 15, 3, 21 p=6,11,7,15,3,21 表示 A 1 A_1 A1 是 6 × 11 6 \times 11 6×11, A 2 A_2 A2 是 11 × 7 11 \times 7 11×7,等等。
2️⃣ 递推关系:自底向上计算
状态定义:
- m i , j mi,j mi,j:计算 A i × A i + 1 × ⋯ × A j A_i \times A_{i+1} \times \cdots \times A_j Ai×Ai+1×⋯×Aj 所需的最少标量乘法次数
递推公式 :
m i , j = { 0 , if i = j (单个矩阵,无需乘法) min i ≤ k < j { m i , k + m k + 1 , j + p i − 1 × p k × p j } , if i < j mi,j = \begin{cases} 0, & \text{if } i = j \quad \text{(单个矩阵,无需乘法)} \\ \min_{i \leq k < j} \{mi,k + mk+1,j + p_{i-1} \times p_k \times p_j\}, & \text{if } i < j \end{cases} mi,j={0,mini≤k<j{mi,k+mk+1,j+pi−1×pk×pj},if i=j(单个矩阵,无需乘法)if i<j
关键理解:
- 基础情况 : i = j i = j i=j 时,只有一个矩阵,成本为 0 0 0
- 递归情况 : i < j i < j i<j 时,尝试所有分割点 k k k,选择成本最小的
3️⃣ 计算过程:第n层使用n-1层的结果
动态规划的自底向上特性 :按链的长度 递增计算,确保计算 m i , j mi,j mi,j 时,所有更短的子链已经计算完成。
计算顺序:
-
第1层(长度 l = 1):单个矩阵
- m 1 , 1 , m 2 , 2 , ... , m n , n = 0 m1,1, m2,2, \ldots, mn,n = 0 m1,1,m2,2,...,mn,n=0
- 基础情况,直接得到
-
第2层(长度 l = 2):两个矩阵相乘
- 计算 m 1 , 2 , m 2 , 3 , ... , m n − 1 , n m1,2, m2,3, \ldots, mn-1,n m1,2,m2,3,...,mn−1,n
- 使用第1层的结果: m i , i mi,i mi,i 和 m j , j mj,j mj,j
-
第3层(长度 l = 3):三个矩阵相乘
- 计算 m 1 , 3 , m 2 , 4 , ... , m n − 2 , n m1,3, m2,4, \ldots, mn-2,n m1,3,m2,4,...,mn−2,n
- 使用第1层和第2层的结果: m i , k mi,k mi,k 和 m k + 1 , j mk+1,j mk+1,j(长度 ≤ 2)
-
...
-
第n层(长度 l = n):所有矩阵相乘
- 计算 m 1 , n m1,n m1,n
- 使用所有前面层的结果
关键点:
- 计算长度为 l l l 的链时,需要用到长度 < l < l <l 的链的结果
- 这保证了子问题已经求解,可以直接查表使用
- 避免了重复计算,每个子问题只计算一次
4️⃣ 两个表格:记录最优值和最优解
矩阵链乘法问题需要两个表格来完整解决问题:
表格1: m i , j mi,j mi,j - 记录最小乘法次数
作用 :存储子问题的最优值: m i , j mi,j mi,j = 计算 A i × ⋯ × A j A_i \times \cdots \times A_j Ai×⋯×Aj 的最少乘法次数
为什么需要:
- 避免重复计算相同的子问题
- 在计算 m i , j mi,j mi,j 时,可以直接查表获取 m i , k mi,k mi,k 和 m k + 1 , j mk+1,j mk+1,j 的值
表格2: s i , j si,j si,j - 记录最优分割位置
作用 :存储子问题的最优解(用于回溯)。 s i , j si,j si,j = 在计算 A i × ⋯ × A j A_i \times \cdots \times A_j Ai×⋯×Aj 时的最优分割点 k k k
两个表格的关系:
- 在计算 m i , j mi,j mi,j 时,同时更新 s i , j si,j si,j
- 当找到使 m i , j mi,j mi,j 最小的 k k k 时,记录 s i , j = k si,j = k si,j=k
- 回溯时,根据 s i , j si,j si,j 递归地构造最优括号化方案
回溯过程:
如果 s[i,j] = k,则最优括号化是:
(A_i × ... × A_k) × (A_{k+1} × ... × A_j)
递归地:
- 左部分:根据 s[i,k] 继续分解
- 右部分:根据 s[k+1,j] 继续分解
🔍 算法执行过程示例
让我们用示例数据演示算法执行过程:
数据 :5个矩阵,维度数组 p = 6 , 11 , 7 , 15 , 3 , 21 p = 6, 11, 7, 15, 3, 21 p=6,11,7,15,3,21
步骤1:初始化(长度 l = 1)
单个矩阵的成本为 0:
| m i , j mi,j mi,j | j=1 | j=2 | j=3 | j=4 | j=5 |
|---|---|---|---|---|---|
| i=1 | 0 (6×11) | ||||
| i=2 | 0 (11×7) | ||||
| i=3 | 0 (7×15) | ||||
| i=4 | 0 (15×3) | ||||
| i=5 | 0 (3×21) |
步骤2:计算长度为2的链(l = 2)
计算 m 1 , 2 m1,2 m1,2(矩阵 A×B):
- k = 1 k=1 k=1: m 1 , 1 + m 2 , 2 + p 0 × p 1 × p 2 = 0 + 0 + 6 × 11 × 7 = 462 m1,1 + m2,2 + p_0 \times p_1 \times p_2 = 0 + 0 + 6 \times 11 \times 7 = 462 m1,1+m2,2+p0×p1×p2=0+0+6×11×7=462
- m 1 , 2 = 462 m1,2 = 462 m1,2=462, s 1 , 2 = 1 s1,2 = 1 s1,2=1
计算 m 2 , 3 m2,3 m2,3(矩阵 B×C):
- k = 2 k=2 k=2: 0 + 0 + 11 × 7 × 15 = 1155 0 + 0 + 11 \times 7 \times 15 = 1155 0+0+11×7×15=1155
- m 2 , 3 = 1155 m2,3 = 1155 m2,3=1155, s 2 , 3 = 2 s2,3 = 2 s2,3=2
计算 m 3 , 4 m3,4 m3,4(矩阵 C×D):
- k = 3 k=3 k=3: 0 + 0 + 7 × 15 × 3 = 315 0 + 0 + 7 \times 15 \times 3 = 315 0+0+7×15×3=315
- m 3 , 4 = 315 m3,4 = 315 m3,4=315, s 3 , 4 = 3 s3,4 = 3 s3,4=3
计算 m 4 , 5 m4,5 m4,5(矩阵 D×E):
- k = 4 k=4 k=4: 0 + 0 + 15 × 3 × 21 = 945 0 + 0 + 15 \times 3 \times 21 = 945 0+0+15×3×21=945
- m 4 , 5 = 945 m4,5 = 945 m4,5=945, s 4 , 5 = 4 s4,5 = 4 s4,5=4
步骤3:计算长度为3的链(l = 3)
计算 m 1 , 3 m1,3 m1,3(矩阵 A×B×C):
- k = 1 k=1 k=1: m 1 , 1 + m 2 , 3 + p 0 × p 1 × p 3 = 0 + 1155 + 6 × 11 × 15 = 0 + 1155 + 990 = 2145 m1,1 + m2,3 + p_0 \times p_1 \times p_3 = 0 + 1155 + 6 \times 11 \times 15 = 0 + 1155 + 990 = 2145 m1,1+m2,3+p0×p1×p3=0+1155+6×11×15=0+1155+990=2145
- k = 2 k=2 k=2: m 1 , 2 + m 3 , 3 + p 0 × p 2 × p 3 = 462 + 0 + 6 × 7 × 15 = 462 + 0 + 630 = 1092 m1,2 + m3,3 + p_0 \times p_2 \times p_3 = 462 + 0 + 6 \times 7 \times 15 = 462 + 0 + 630 = 1092 m1,2+m3,3+p0×p2×p3=462+0+6×7×15=462+0+630=1092
- 最小值: m 1 , 3 = 1092 m1,3 = 1092 m1,3=1092, s 1 , 3 = 2 s1,3 = 2 s1,3=2
计算 m 2 , 4 m2,4 m2,4(矩阵 B×C×D):
- k = 2 k=2 k=2: 0 + 315 + 11 × 7 × 3 = 546 0 + 315 + 11 \times 7 \times 3 = 546 0+315+11×7×3=546
- k = 3 k=3 k=3: 1155 + 0 + 11 × 15 × 3 = 1155 + 495 = 1650 1155 + 0 + 11 \times 15 \times 3 = 1155 + 495 = 1650 1155+0+11×15×3=1155+495=1650
- 最小值: m 2 , 4 = 546 m2,4 = 546 m2,4=546, s 2 , 4 = 2 s2,4 = 2 s2,4=2
计算 m 3 , 5 m3,5 m3,5(矩阵 C×D×E):
- k = 3 k=3 k=3: 0 + 945 + 7 × 15 × 21 = 0 + 945 + 2205 = 3150 0 + 945 + 7 \times 15 \times 21 = 0 + 945 + 2205 = 3150 0+945+7×15×21=0+945+2205=3150
- k = 4 k=4 k=4: 315 + 0 + 7 × 3 × 21 = 315 + 0 + 441 = 756 315 + 0 + 7 \times 3 \times 21 = 315 + 0 + 441 = 756 315+0+7×3×21=315+0+441=756
- 最小值: m 3 , 5 = 756 m3,5 = 756 m3,5=756, s 3 , 5 = 4 s3,5 = 4 s3,5=4
步骤4:计算长度为4的链(l = 4)
计算 m 1 , 4 m1,4 m1,4(矩阵 A×B×C×D):
- k = 1 k=1 k=1: 0 + 546 + 6 × 11 × 3 = 0 + 546 + 198 = 744 0 + 546 + 6 \times 11 \times 3 = 0 + 546 + 198 = 744 0+546+6×11×3=0+546+198=744
- k = 2 k=2 k=2: 462 + 315 + 6 × 7 × 3 = 462 + 315 + 126 = 903 462 + 315 + 6 \times 7 \times 3 = 462 + 315 + 126 = 903 462+315+6×7×3=462+315+126=903
- k = 3 k=3 k=3: 1092 + 0 + 6 × 15 × 3 = 1092 + 0 + 270 = 1362 1092 + 0 + 6 \times 15 \times 3 = 1092 + 0 + 270 = 1362 1092+0+6×15×3=1092+0+270=1362
- 最小值: m 1 , 4 = 744 m1,4 = 744 m1,4=744, s 1 , 4 = 1 s1,4 = 1 s1,4=1
计算 m 2 , 5 m2,5 m2,5(矩阵 B×C×D×E):
- k = 2 k=2 k=2: 0 + 756 + 11 × 7 × 21 = 0 + 756 + 1617 = 2373 0 + 756 + 11 \times 7 \times 21 = 0 + 756 + 1617 = 2373 0+756+11×7×21=0+756+1617=2373
- k = 3 k=3 k=3: 1155 + 945 + 11 × 15 × 21 = 1155 + 945 + 3465 = 5565 1155 + 945 + 11 \times 15 \times 21 = 1155 + 945 + 3465 = 5565 1155+945+11×15×21=1155+945+3465=5565
- k = 4 k=4 k=4: 546 + 0 + 11 × 3 × 21 = 546 + 0 + 693 = 1239 546 + 0 + 11 \times 3 \times 21 = 546 + 0 + 693 = 1239 546+0+11×3×21=546+0+693=1239
- 最小值: m 2 , 5 = 1239 m2,5 = 1239 m2,5=1239, s 2 , 5 = 4 s2,5 = 4 s2,5=4
步骤5:计算长度为5的链(l = 5)
计算 m 1 , 5 m1,5 m1,5(矩阵 A×B×C×D×E):
- k = 1 k=1 k=1: 0 + 1239 + 6 × 11 × 21 = 0 + 1239 + 1386 = 2625 0 + 1239 + 6 \times 11 \times 21 = 0 + 1239 + 1386 = 2625 0+1239+6×11×21=0+1239+1386=2625
- k = 2 k=2 k=2: 462 + 756 + 6 × 7 × 21 = 462 + 756 + 882 = 2100 462 + 756 + 6 \times 7 \times 21 = 462 + 756 + 882 = 2100 462+756+6×7×21=462+756+882=2100
- k = 3 k=3 k=3: 1092 + 945 + 6 × 15 × 21 = 1092 + 945 + 1890 = 3927 1092 + 945 + 6 \times 15 \times 21 = 1092 + 945 + 1890 = 3927 1092+945+6×15×21=1092+945+1890=3927
- k = 4 k=4 k=4: 744 + 0 + 6 × 3 × 21 = 744 + 0 + 378 = 1122 744 + 0 + 6 \times 3 \times 21 = 744 + 0 + 378 = 1122 744+0+6×3×21=744+0+378=1122
- 最小值: m 1 , 5 = 1122 m1,5 = 1122 m1,5=1122, s 1 , 5 = 4 s1,5 = 4 s1,5=4
最终表格
m i , j mi,j mi,j 表格(最小乘法次数):
| m i , j mi,j mi,j | j=1 | j=2 | j=3 | j=4 | j=5 |
|---|---|---|---|---|---|
| i=1 | 0 | 462 | 1092 | 744 | 1122 |
| i=2 | 0 | 1155 | 546 | 1239 | |
| i=3 | 0 | 315 | 756 | ||
| i=4 | 0 | 945 | |||
| i=5 | 0 |
s i , j si,j si,j 表格(最优分割位置):
| s i , j si,j si,j | j=1 | j=2 | j=3 | j=4 | j=5 |
|---|---|---|---|---|---|
| i=1 | - | 1 | 2 | 1 | 4 |
| i=2 | - | 2 | 2 | 4 | |
| i=3 | - | 3 | 4 | ||
| i=4 | - | 4 | |||
| i=5 | - |
回溯构造最优解
回溯的核心逻辑 :
s i , j = k si,j = k si,j=k 表示:计算 A i × ⋯ × A j A_i \times \cdots \times A_j Ai×⋯×Aj 时,最优分割点在位置 k k k。这意味着:先计算 A i × ⋯ × A k A_i \times \cdots \times A_k Ai×⋯×Ak,再计算 A k + 1 × ⋯ × A j A_{k+1} \times \cdots \times A_j Ak+1×⋯×Aj,最后合并
- 用括号表示: ( A i × ⋯ × A k ) × ( A k + 1 × ⋯ × A j ) (A_i \times \cdots \times A_k) \times (A_{k+1} \times \cdots \times A_j) (Ai×⋯×Ak)×(Ak+1×⋯×Aj)
- 然后递归地对左右两部分进行括号化
回溯过程(递归展开):
第1步:处理整个链 1 , 5 1,5 1,5
查表: s 1 , 5 = 4 s1,5 = 4 s1,5=4
含义 :在位置4分割,得到:
( A 1 × A 2 × A 3 × A 4 ) × ( A 5 ) (A_1 \times A_2 \times A_3 \times A_4) \times (A_5) (A1×A2×A3×A4)×(A5)
当前状态 : ( A 1 A 2 A 3 A 4 ) × A 5 (A_1 A_2 A_3 A_4) \times A_5 (A1A2A3A4)×A5
第2步:递归处理左部分 1 , 4 1,4 1,4
查表: s 1 , 4 = 1 s1,4 = 1 s1,4=1
含义 :在位置1分割 A 1 × A 2 × A 3 × A 4 A_1 \times A_2 \times A_3 \times A_4 A1×A2×A3×A4,得到:
( A 1 ) × ( A 2 × A 3 × A 4 ) (A_1) \times (A_2 \times A_3 \times A_4) (A1)×(A2×A3×A4)
更新状态 : ( ( A 1 ) × ( A 2 A 3 A 4 ) ) × A 5 ((A_1) \times (A_2 A_3 A_4)) \times A_5 ((A1)×(A2A3A4))×A5
第3步:递归处理 2 , 4 2,4 2,4
查表: s 2 , 4 = 2 s2,4 = 2 s2,4=2
含义 :在位置2分割 A 2 × A 3 × A 4 A_2 \times A_3 \times A_4 A2×A3×A4,得到:
( A 2 ) × ( A 3 × A 4 ) (A_2) \times (A_3 \times A_4) (A2)×(A3×A4)
更新状态 : ( ( A 1 ) × ( ( A 2 ) × ( A 3 A 4 ) ) ) × A 5 ((A_1) \times ((A_2) \times (A_3 A_4))) \times A_5 ((A1)×((A2)×(A3A4)))×A5
第4步:简化括号
由于单个矩阵不需要括号,简化后得到:
最优括号化方案 : ( ( A ( B ( C D ) ) ) E ) ((A(B(CD)))E) ((A(B(CD)))E)
其中:
- A = A 1 A = A_1 A=A1
- B = A 2 B = A_2 B=A2
- C = A 3 C = A_3 C=A3
- D = A 4 D = A_4 D=A4
- E = A 5 E = A_5 E=A5
⚠️ 常见错误与注意事项
错误:维度数组索引错误
常见错误:
- 混淆矩阵编号(从1开始)和数组索引(从0开始)
- 计算合并成本时用错维度
正确理解:
- 矩阵 A i A_i Ai 的维度是 p i − 1 × p i pi-1 \times pi pi−1×pi
- 计算 A i × ⋯ × A k A_i \times \cdots \times A_k Ai×⋯×Ak 和 A k + 1 × ⋯ × A j A_{k+1} \times \cdots \times A_j Ak+1×⋯×Aj 的合并成本:
- 左结果矩阵维度: p i − 1 × p k pi-1 \times pk pi−1×pk
- 右结果矩阵维度: p k × p j pk \times pj pk×pj
- 合并成本: p i − 1 × p k × p j pi-1 \times pk \times pj pi−1×pk×pj
📊 算法复杂度分析
时间复杂度
递推关系 : T ( n ) = O ( n 3 ) T(n) = O(n^3) T(n)=O(n3)
分析:
- 外层循环:链的长度 l l l 从 2 到 n n n,共 n − 1 n-1 n−1 次
- 中层循环:起始位置 i i i 从 1 到 n − l + 1 n-l+1 n−l+1,平均约 n / 2 n/2 n/2 次
- 内层循环:分割点 k k k 从 i i i 到 j − 1 j-1 j−1,平均约 l / 2 l/2 l/2 次
- 总时间复杂度: O ( n 3 ) O(n^3) O(n3)
💡 拓展思考
-
为什么不能用贪心法? 局部最优不等于全局最优,需要尝试所有可能的分割点。
-
为什么时间复杂度是 O ( n 3 ) O(n^3) O(n3)? 三层嵌套循环:长度、起始位置、分割点。
-
如何优化? 可以使用记忆化递归,但时间复杂度仍然是 O ( n 3 ) O(n^3) O(n3)。
-
实际应用:编译器优化、数值计算、图像处理等领域都有应用。
💡 记忆口诀:矩阵链乘用DP,两个表格要记牢,m表记录最优值,s表回溯找方案!
习题2:钢条切割问题------动态规划
📌 适合对象 :学习动态规划算法的同学
⏱️ 预计阅读时间 :20-25分钟
🎯 学习目标:掌握如何用动态规划解决钢条切割问题,理解动态规划的两步:计算最优值和记录最优切割策略
📖 问题描述:最大化钢条切割收益



目标:
- 设计动态规划算法,确定如何切割钢条以最大化总收益
- 提供算法伪代码
- 分析算法的时间复杂度和空间复杂度
正确思路:动态规划
动态规划的核心思想:将大问题分解为重叠子问题,自底向上求解,避免重复计算。
1️⃣ 问题分解的逻辑
分解思路:
-
选择第一次切割位置:
- 如果不切割 :直接卖出,收益为 p k p_k pk
- 如果切割 :在位置 i i i( 1 ≤ i ≤ k − 1 1 \leq i \leq k-1 1≤i≤k−1)切割,得到:
- 左段:长度为 i i i,收益为 R i Ri Ri(最优切割方案下的收益)
- 右段:长度为 k − i k-i k−i,收益为 R k − i Rk-i Rk−i(最优切割方案下的收益)
- 总收益: R i + R k − i Ri + Rk-i Ri+Rk−i
-
最优子结构性质:
- 如果长度为 k k k 的钢条的最优切割方案是切割成 i i i 和 k − i k-i k−i 两段
- 那么长度为 i i i 和 k − i k-i k−i 的钢条也必然采用最优切割方案
- 否则,可以用更优的子方案替换,得到更优的整体方案(矛盾)
-
尝试所有可能:
- 尝试所有可能的第一次切割位置 i i i( 1 ≤ i ≤ k − 1 1 \leq i \leq k-1 1≤i≤k−1)
- 比较:不切割的收益 p k p_k pk 和所有切割方案的收益 R i + R k − i Ri + Rk-i Ri+Rk−i
- 选择收益最大的方案
2️⃣ 递推关系:自底向上计算
状态定义:
- R k Rk Rk:切割长度为 k k k 英寸的钢条能够带来的最大收益
基础情况:
- R 1 = p 1 R1 = p_1 R1=p1(长度为1的钢条,无法再切割,只能直接卖出)
递推公式:
R k = max { max 1 ≤ i ≤ k − 1 { R i + R k − i } , p k } Rk = \max \left\{ \max_{1 \leq i \leq k-1} \{Ri + Rk-i\}, \quad p_k \right\} Rk=max{1≤i≤k−1max{Ri+Rk−i},pk}
解释:
- 不切割 :收益为 p k p_k pk
- 切割 :尝试所有切割位置 i i i,选择 R i + R k − i Ri + Rk-i Ri+Rk−i 的最大值
- 最终取两者的最大值
优化版本(利用对称性):
由于切割成 i i i 和 k − i k-i k−i 与切割成 k − i k-i k−i 和 i i i 是等价的,可以只考虑 i ≤ k / 2 i \leq k/2 i≤k/2:
R k = max { max 1 ≤ i ≤ ⌊ k / 2 ⌋ { R i + R k − i } , p k } Rk = \max \left\{ \max_{1 \leq i \leq \lfloor k/2 \rfloor} \{Ri + Rk-i\}, \quad p_k \right\} Rk=max{1≤i≤⌊k/2⌋max{Ri+Rk−i},pk}
这样可以减少一半的计算量。
3️⃣ 计算过程:第k层使用1到k-1层的结果
动态规划的自底向上特性 :按长度递增计算,确保计算 R k Rk Rk 时,所有更短的钢条的最优收益已经计算完成。
计算顺序:
-
第1层(长度 k = 1):
- R 1 = p 1 R1 = p_1 R1=p1
- 基础情况,直接得到
-
第2层(长度 k = 2):
- 计算 R 2 R2 R2
- 使用第1层的结果: R 1 R1 R1
- R 2 = max { R 1 + R 1 , p 2 } R2 = \max\{R1 + R1, p_2\} R2=max{R1+R1,p2}
-
第3层(长度 k = 3):
- 计算 R 3 R3 R3
- 使用第1层和第2层的结果: R 1 R1 R1 和 R 2 R2 R2
- R 3 = max { R 1 + R 2 , R 2 + R 1 , p 3 } = max { R 1 + R 2 , p 3 } R3 = \max\{R1 + R2, R2 + R1, p_3\} = \max\{R1 + R2, p_3\} R3=max{R1+R2,R2+R1,p3}=max{R1+R2,p3}(利用对称性)
-
...
-
第n层(长度 k = n):
- 计算 R n Rn Rn
- 使用所有前面层的结果: R 1 , R 2 , ... , R n − 1 R1, R2, \ldots, Rn-1 R1,R2,...,Rn−1
关键点:
- 计算长度为 k k k 的钢条时,需要用到长度 < k < k <k 的钢条的最优收益
- 这保证了子问题已经求解,可以直接查表使用
- 避免了重复计算,每个子问题只计算一次
4️⃣ 两个表格:记录最优值和最优切割策略
钢条切割问题需要两个表格来完整解决问题:
表格1: R k Rk Rk - 记录最大收益
作用:存储子问题的最优值
- R k Rk Rk = 切割长度为 k k k 的钢条能够带来的最大收益
为什么需要:
- 避免重复计算相同的子问题
- 在计算 R k Rk Rk 时,可以直接查表获取 R i Ri Ri 和 R k − i Rk-i Rk−i 的值
表格2: C k Ck Ck - 记录最优切割点
作用:存储子问题的最优解(用于回溯)
- C k Ck Ck = 切割长度为 k k k 的钢条时的最优第一次切割位置
- 如果 C k = nil Ck = \text{nil} Ck=nil,表示不切割(直接卖出整根钢条)
为什么需要:
- R k Rk Rk 只告诉我们最优值是多少(最大收益是多少)
- C k Ck Ck 告诉我们最优解是什么(如何切割才能达到这个最大收益)
- 没有 C k Ck Ck,无法构造出最优的切割方案
两个表格的关系:
- 在计算 R k Rk Rk 时,同时更新 C k Ck Ck
- 当找到使 R k Rk Rk 最大的切割位置 i i i 时,记录 C k = i Ck = i Ck=i
- 如果 p k p_k pk 更大,记录 C k = nil Ck = \text{nil} Ck=nil(不切割)
- 回溯时,根据 C k Ck Ck 递归地构造最优切割方案
回溯过程:
如果 C[k] = nil,则长度为k的钢条不切割,直接卖出
如果 C[k] = i,则最优切割是:
长度为i的钢条(根据C[i]继续切割)
+
长度为k-i的钢条(根据C[k-i]继续切割)
💻 算法伪代码
pseudocode
算法:Rod-Cutting(P[], n)
输入:价格数组P[1..n],钢条长度n
输出:最大收益R[n],最优切割策略
1. // 初始化:长度为1的钢条
2. R[1] ← P[1]
3. C[1] ← nil // 长度为1,无法切割
4.
4. // 自底向上计算
5. for k ← 2 to n do // k是钢条长度,长度为1的情况已在第2行解决
6. // 情况1:不切割,直接卖出
7. q ← P[k] // 第3、4行指长度为k的钢条作为一个整体,不切割的情况
8. C[k] ← nil
10.
9. // 情况2:尝试所有切割位置
10. for i ← 1 to ⌊k/2⌋ do // 变换切割点(利用对称性,只需考虑i≤k/2)
11. // 如果切割成i和k-i两段更优
12. if q < R[i] + R[k-i] then
13. q ← R[i] + R[k-i]
14. C[k] ← i // 记录最优切割点
15. end if
16. end for
19.
17. R[k] ← q // 记录最大收益
18. end for
22.
19. return R[n]
🔍 算法执行过程示例
让我们用示例数据演示算法执行过程:
数据 : n = 6 n = 6 n=6,价格表 P = 1 , 4 , 4 , 7 , 8 , 9 P = 1, 4, 4, 7, 8, 9 P=1,4,4,7,8,9
步骤1:初始化(长度 k = 1)
- R 1 = P 1 = 1 R1 = P1 = 1 R1=P1=1
- C 1 = nil C1 = \text{nil} C1=nil(长度为1,无法切割)
步骤2:计算长度 k = 2
- 不切割 : P 2 = 4 P2 = 4 P2=4
- 切割 :
- i = 1 i=1 i=1: R 1 + R 1 = 1 + 1 = 2 R1 + R1 = 1 + 1 = 2 R1+R1=1+1=2
- 最大值 : max { 2 , 4 } = 4 \max\{2, 4\} = 4 max{2,4}=4
- R 2 = 4 R2 = 4 R2=4, C 2 = nil C2 = \text{nil} C2=nil(不切割更优)
步骤3:计算长度 k = 3
- 不切割 : P 3 = 4 P3 = 4 P3=4
- 切割 :
- i = 1 i=1 i=1: R 1 + R 2 = 1 + 4 = 5 R1 + R2 = 1 + 4 = 5 R1+R2=1+4=5
- i = 2 i=2 i=2:(这里对称,结果一样)
- 最大值 : max { 5 , 4 } = 5 \max\{5, 4\} = 5 max{5,4}=5
- R 3 = 5 R3 = 5 R3=5, C 3 = 1 C3 = 1 C3=1(在位置1切割)
步骤4:计算长度 k = 4
- 不切割 : P 4 = 7 P4 = 7 P4=7
- 切割 :
- i = 1 i=1 i=1: R 1 + R 3 = 1 + 5 = 6 R1 + R3 = 1 + 5 = 6 R1+R3=1+5=6
- i = 2 i=2 i=2: R 2 + R 2 = 4 + 4 = 8 R2 + R2 = 4 + 4 = 8 R2+R2=4+4=8
- 最大值 : max { 6 , 8 , 7 } = 8 \max\{6, 8, 7\} = 8 max{6,8,7}=8
- R 4 = 8 R4 = 8 R4=8, C 4 = 2 C4 = 2 C4=2(在位置2切割)
步骤5:计算长度 k = 5
- 不切割 : P 5 = 8 P5 = 8 P5=8
- 切割 :(i是全局位置)
- i = 1 i=1 i=1: R 1 + R 4 = 1 + 8 = 9 R1 + R4 = 1 + 8 = 9 R1+R4=1+8=9
- i = 2 i=2 i=2: R 2 + R 3 = 4 + 5 = 9 R2 + R3 = 4 + 5 = 9 R2+R3=4+5=9
- 最大值 : max { 9 , 9 , 8 } = 9 \max\{9, 9, 8\} = 9 max{9,9,8}=9
- R 5 = 9 R5 = 9 R5=9, C 5 = 1 C5 = 1 C5=1(在位置1切割, i = 1 i=1 i=1和 i = 2 i=2 i=2收益相同,选择较小的 i i i)
步骤6:计算长度 k = 6
- 不切割 : P 6 = 9 P6 = 9 P6=9
- 切割 :
- i = 1 i=1 i=1: R 1 + R 5 = 1 + 9 = 10 R1 + R5 = 1 + 9 = 10 R1+R5=1+9=10
- i = 2 i=2 i=2: R 2 + R 4 = 4 + 8 = 12 R2 + R4 = 4 + 8 = 12 R2+R4=4+8=12
- i = 3 i=3 i=3: R 3 + R 3 = 5 + 5 = 10 R3 + R3 = 5 + 5 = 10 R3+R3=5+5=10
- 最大值 : max { 10 , 12 , 10 , 9 } = 12 \max\{10, 12, 10, 9\} = 12 max{10,12,10,9}=12
- R 6 = 12 R6 = 12 R6=12, C 6 = 2 C6 = 2 C6=2(在位置2切割)
最终表格
R i Ri Ri 表格(最大收益):
| i i i | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| R i Ri Ri(元) | 1 | 4 | 5 | 8 | 9 | 12 |
C i Ci Ci 表格(最优切割点):
| i i i | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| C i Ci Ci(英寸) | nil | nil | 1 | 2 | 1 | 2 |
注意:i是全局位置。
回溯构造最优切割方案
回溯的核心逻辑:
- C k = nil Ck = \text{nil} Ck=nil 表示:长度为 k k k 的钢条不切割,直接卖出
- C k = i Ck = i Ck=i 表示:长度为 k k k 的钢条在位置 i i i 切割,得到长度为 i i i 和 k − i k-i k−i 的两段
- 然后递归地对左右两段进行切割
回溯过程(递归展开):
对于长度为6的钢条:
查表: C 6 = 2 C6 = 2 C6=2
含义:在位置2切割,得到:
- 左段:长度为2
- 右段:长度为4
递归处理左段(长度为2):
查表: C 2 = nil C2 = \text{nil} C2=nil
含义:长度为2的钢条不切割,直接卖出
递归处理右段(长度为4):
查表: C 4 = 2 C4 = 2 C4=2
含义:在位置2切割,得到:
- 左段:长度为2
- 右段:长度为2
递归处理两个长度为2的段:
查表: C 2 = nil C2 = \text{nil} C2=nil(两个都是)
含义:都不切割,直接卖出
最优切割方案 :切割成 2英寸 + 2英寸 + 2英寸
总收益 : 4 + 4 + 4 = 12 4 + 4 + 4 = 12 4+4+4=12 元 ✅
回溯算法伪代码
pseudocode
算法:Print-Optimal-Cut(C[], k)
输入:切割点数组C[],钢条长度k
输出:打印最优切割方案
1. if C[k] == nil then
2. print "长度为k的钢条不切割,直接卖出"
3. else
4. i = C[k]
5. print "在位置i切割,得到长度为i和k-i的两段"
6. Print-Optimal-Cut(C, i) // 递归处理左段
7. Print-Optimal-Cut(C, k-i) // 递归处理右段
8. end if
⚠️ 常见错误与注意事项
错误1:忘记考虑不切割的情况
错误做法:
python
# 错误:只考虑切割,忘记可以直接卖出
R[k] = max(R[i] + R[k-i] for i in range(1, k))
正确做法:
python
# 正确:同时考虑不切割和切割
R[k] = max(P[k], max(R[i] + R[k-i] for i in range(1, k)))
为什么重要:有时候不切割直接卖出可能比切割更优。
错误2:没有利用对称性优化
错误做法:
python
# 错误:尝试所有切割位置
for i in range(1, k):
# 计算 R[i] + R[k-i]
正确做法:
python
# 正确:利用对称性,只需考虑 i ≤ k/2
for i in range(1, k//2 + 1):
# 计算 R[i] + R[k-i]
为什么重要 :切割成 i i i 和 k − i k-i k−i 与切割成 k − i k-i k−i 和 i i i 是等价的,可以减少一半的计算量。
📊 算法复杂度分析
时间复杂度
分析:
- 外层循环:长度 k k k 从 2 到 n n n,共 n − 1 n-1 n−1 次
- 内层循环:切割位置 i i i 从 1 到 ⌊ k / 2 ⌋ \lfloor k/2 \rfloor ⌊k/2⌋,平均约 k / 4 k/4 k/4 次
- 总时间复杂度: ∑ k = 2 n ⌊ k / 2 ⌋ = O ( n 2 ) \sum_{k=2}^{n} \lfloor k/2 \rfloor = O(n^2) ∑k=2n⌊k/2⌋=O(n2)
递推关系 : T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)
💡 拓展思考
-
为什么不能用贪心法? 局部最优不等于全局最优。例如,可能短段的单价更高,但最优方案可能是切割成长段。
-
为什么时间复杂度是 O ( n 2 ) O(n^2) O(n2)? 两层嵌套循环:长度和切割位置。
-
实际应用:
- 资源分配:如何将有限资源分配给不同项目以最大化收益
- 投资组合:如何分配资金到不同投资项目
- 生产计划:如何安排生产以最大化利润
-
与矩阵链乘法的对比:
- 相似点:都是动态规划,都需要记录最优值和最优解
- 不同点 :矩阵链乘法是 O ( n 3 ) O(n^3) O(n3),钢条切割是 O ( n 2 ) O(n^2) O(n2)
💡 记忆口诀:钢条切割用DP,两个表格要记牢,R表记录最优值,C表回溯找方案,自底向上O(n²),最大收益轻松算!