【动态规划】矩阵链乘法——算法设计与分析
介绍了矩阵链乘法问题及其相关定义。按照动态规划的基本求解策略进行求解,并给出伪代码及算法复杂度分析。
文章目录
一、问题定义
1.1 矩阵乘法
对于矩阵 U p × q U_{p\times q} Up×q和 V q × r V_{q\times r} Vq×r, Z p × r = U V Z_{p\times r}=UV Zp×r=UV,共需计算 p q r pqr pqr次标量乘法,时间复杂度为 Θ ( p q r ) \Theta (pqr) Θ(pqr)
矩阵乘法满足结合律,即 ( U V ) W = U ( V W ) (UV)W=U(VW) (UV)W=U(VW),选择不同的结合顺序,会对计算的次数产生巨大的影响。
1.2 矩阵链乘法问题
n n n个矩阵相乘称为矩阵链乘法,问题在于如何确定相乘顺序,以提高计算效率?
形式化定义如下:
输入:
- n n n个矩阵组成的矩阵链 U 1.. n = < U 1 , U 2 , . . . , U n > U_{1..n}=<U_1,U_2,...,U_n> U1..n=<U1,U2,...,Un>
- 矩阵链 U 1.. n U_{1..n} U1..n对应的维度数分别为 p 0 , p 1 , . . . , p n p_0,p_1,...,p_n p0,p1,...,pn, U i U_i Ui的维度为 p i − 1 × p i p_{i-1}\times p_i pi−1×pi
输出:
- 找到一种添加括号的方式,以确定矩阵链乘法的计算顺序,使得最小化矩阵链标量乘法的次数
二、求解策略
显然,最优矩阵链乘法加括号的组合中,任意一部分均为最优,具有最优子结构性质,尝试动态规划求解。
2.1 分析问题结构
形式化表示问题:
- D [ i , j ] D[i,j] D[i,j]:计算矩阵链 U i . . j U_{i..j} Ui..j所需标量乘法的最小次数
原问题:
- D [ i , n ] D[i,n] D[i,n]:计算矩阵链 U 1.. n U_{1..n} U1..n所需标量乘法的最小次数
2.2 建立递推关系
以长度为4的矩阵链为例辅助思考, U = U 1 U 2 U 3 U 4 U=U_1U_2U_3U_4 U=U1U2U3U4
对其进行加1个括号的操作,有以下几种方式:
U
=
(
U
1
)
(
U
2
U
3
U
4
)
⇒
D
[
1
,
4
]
=
D
[
1
,
1
]
+
D
[
2
,
4
]
+
p
0
p
1
p
4
U
=
(
U
1
U
2
)
(
U
3
U
4
)
⇒
D
[
1
,
4
]
=
D
[
1
,
2
]
+
D
[
3
,
4
]
+
p
0
p
2
p
4
U
=
(
U
1
U
2
U
3
)
(
U
4
)
⇒
D
[
1
,
4
]
=
D
[
1
,
3
]
+
D
[
4
,
4
]
+
p
0
p
3
p
4
U=(U_1)(U_2U_3U_4)\Rightarrow D[1,4]=D[1,1]+D[2,4]+p_0p_1p_4\\ U=(U_1U_2)(U_3U_4)\Rightarrow D[1,4]=D[1,2]+D[3,4]+p_0p_2p_4\\ U=(U_1U_2U_3)(U_4)\Rightarrow D[1,4]=D[1,3]+D[4,4]+p_0p_3p_4
U=(U1)(U2U3U4)⇒D[1,4]=D[1,1]+D[2,4]+p0p1p4U=(U1U2)(U3U4)⇒D[1,4]=D[1,2]+D[3,4]+p0p2p4U=(U1U2U3)(U4)⇒D[1,4]=D[1,3]+D[4,4]+p0p3p4
而
D
[
1
,
4
]
D[1,4]
D[1,4]结果只需要在这3种方式中取得最大值即可。
我们不妨来看一下,求得 D [ 1 , 4 ] D[1,4] D[1,4]需要哪些数据:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
1 | ? | ? | ? | 目标 |
2 | ? | |||
3 | ? | |||
4 | ? |
再次观察,对角线上的元素表示矩阵自身,不需要进行运算,所以值均为0。而左下三角的矩阵并没有实际含义,因为 j ≥ i j\ge i j≥i,故当前表格更新为下表所示:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
1 | 0 | ? | ? | 目标 |
2 | NULL | 0 | ? | |
3 | NULL | NULL | 0 | ? |
4 | NULL | NULL | NULL | 0 |
相信大家已经看出递推关系。
对于矩阵链 U i . . j U_{i..j} Ui..j,求解 D [ i , j ] D[i,j] D[i,j]时,为了不遗漏最优分割位置,需要枚举所有可能位置 i , i + 1 , . . , j − 1 i,i+1,..,j-1 i,i+1,..,j−1,一共 j − i j-i j−i种。
假设在 k k k处进行分割,分为 U i . . k U_{i..k} Ui..k和 U k + 1.. j U_{k+1..j} Uk+1..j两部分,前者的维度为 p i − 1 × p k p_{i-1} \times p_k pi−1×pk,后者的维度为 p k × p j p_k\times p_j pk×pj,将两部分进行乘积时,需要额外增加 p i − 1 × p k × p j p_{i-1}\times p_k \times p_j pi−1×pk×pj的计算次数。
因此, D [ i , j ] = min i ≤ k < j { D [ i , k ] + D [ k + 1 , j ] + p i − 1 p k p j } D[i,j]=\min_{i\leq k <j} \left \{ D[i,k]+D[k+1,j]+p_{i-1}p_kp_j \right \} D[i,j]=mini≤k<j{D[i,k]+D[k+1,j]+pi−1pkpj}
2.3 自底向上计算
采用图中的方向进行计算:
许多教材中习惯将其逆时针旋转45度,以方便观察,在于个人习惯。
2.4 追踪最优方案
构造追踪数组 R e c [ 1.. n , 1.. n ] Rec[1..n,1..n] Rec[1..n,1..n], R e c [ i , j ] Rec[i,j] Rec[i,j]表示矩阵链 U i . . j U_{i..j} Ui..j的最优分割位置(一次分割)。
在计算 D [ i , j ] D[i,j] D[i,j]的过程中,选出最小的 k k k,记录 R e c [ i , j ] = k Rec[i,j]=k Rec[i,j]=k
追踪时,从 R e c [ 1 , n ] Rec[1,n] Rec[1,n]出发,假设 R e c [ 1 , n ] = k Rec[1,n]=k Rec[1,n]=k,则说明在 U k U_k Uk处进行了分割,分为矩阵链 D [ 1 , k ] D[1,k] D[1,k]和 D [ k + 1 , n ] D[k+1,n] D[k+1,n],再分别查看 R e c [ 1 , k ] Rec[1,k] Rec[1,k]和 R e c [ k + 1 , n ] Rec[k+1,n] Rec[k+1,n],便找到两部分的分割地方。如此寻找直至对角线部分。
三、算法分析
3.1 伪代码
计算及记录部分:
追踪方案部分:
3.2 时间复杂度
时间复杂度 O ( n 3 ) O(n^3) O(n3)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)