动态规划之矩阵连乘问题(代码+手写推导过程)

问题:使用动态规划方法,求解如下矩阵连乘问题:已知有4个矩阵, 每个矩阵Ak (k=1,2,3,4)为rk×r(k+1),其中r1=5,r2=10,r3=3,r4=12,r5=5,求矩阵连乘积A1×A2×A3×A4的最佳乘积顺序,给出最优解和最优值。

解答(手写过程+代码实现):

代码(可执行,附结果):

def matrix_chain_order(p):
    n = len(p) - 1  # 矩阵数量是维度数-1
    m = [[0 for _ in range(n)] for _ in range(n)]  # 存储最小计算次数
    s = [[0 for _ in range(n)] for _ in range(n)]  # 存储断开点
    # L 是链长度,从2开始(两个矩阵的连乘)
    for L in range(2, n + 1):
        for i in range(n - L + 1):  # 起点
            j = i + L - 1  # 终点
            m[i][j] = float('inf')  # 初始化为无穷大
            for k in range(i, j):  # 尝试不同的断开点
                q = m[i][k] + m[k + 1][j] + p[i] * p[k + 1] * p[j + 1]
                if q < m[i][j]:
                    m[i][j] = q
                    s[i][j] = k + 1  # 存储切割位置,k + 1 表示实际切割点编号
    return m, s


def print_optimal_parens(s, i, j):
    if i == j:
        return f"A{i+1}"
    else:
        cut_point = s[i][j]  # 获取实际切割点编号
        return f"({print_optimal_parens(s, i, cut_point-1)} x {print_optimal_parens(s, cut_point, j)})"


# 测试
if __name__ == '__main__':
    # 输入矩阵维度序列
    p = [5, 10, 3, 12, 5]  # A1: 5x10, A2: 10x3, A3: 3x12, A4: 12x5
    m, s = matrix_chain_order(p)
    print("最优计算次表:")
    for row in m:
        print(row)
    print("\n切割位置表:")
    for row in s:
        print(row)
    n = len(p) - 1
    print("\n最优计算顺序:", print_optimal_parens(s, 0, n - 1))
    print("最少标量乘法次数:", m[0][n - 1])

结果:

298c77dea8664c49b0e410c58549d2bb.png

与手算结果相同。

主要逻辑:

这段代码实现了矩阵链乘法问题的动态规划解法,核心目的是在给定矩阵链的情况下,通过调整计算顺序,使矩阵乘法的标量计算次数最小。以下是具体逻辑的分步说明:


1. 问题背景

  1. 给定 n 个矩阵的维度序列 eq?p%20%3D%20%5Br_1%2C%20r_2%2C%20%5Cdots%2C%20r_%7Bn&plus;1%7D%5D,其中:
    • 矩阵 eq?A_1 的维度为 eq?r_1%20%5Ctimes%20r_2
    • 矩阵 eq?A_2 的维度为 eq?r_2%20%5Ctimes%20r_3
    • 依此类推,矩阵 eq?A_k 的维度为eq?r_k%20%5Ctimes%20r_%7Bk&plus;1%7D
  2. 目标:通过调整矩阵的计算顺序,最小化矩阵链乘法的标量计算次数。

2. 动态规划解决方案

状态定义

  1. eq?m%5Bi%5D%5Bj%5D:从矩阵 eq?A_ieq?A_j的最少标量乘法次数。
  2. eq?s%5Bi%5D%5Bj%5D:从矩阵 eq?A_ieq?A_j的最佳切割点,表示在哪个位置断开。

递推公式

对于矩阵链eq?A_i%20%5Cdots%20A_j,尝试在所有可能的切割点 k(i≤k<j)处断开:

eq?%7B%20m%5Bi%5D%5Bj%5D%20%3D%20%5Cmin_%7Bi%20%5Cleq%20k%20%3C%20j%7D%20%5C%7B%20m%5Bi%5D%5Bk%5D%20&plus;%20m%5Bk&plus;1%5D%5Bj%5D%20&plus;%20p%5Bi%5D%20%5Ccdot%20p%5Bk&plus;1%5D%20%5Ccdot%20p%5Bj&plus;1%5D%20%5C%7D%20%7D

  • eq?m%5Bi%5D%5Bk%5D:计算 eq?A_i%20%5Cdots%20A_k所需的最少标量乘法次数。
  • eq?m%5Bk&plus;1%5D%5Bj%5D:计算 eq?A_%7Bk&plus;1%7D%20%5Cdots%20A_j所需的最少标量乘法次数。
  • eq?p%5Bi%5D%20%5Ccdot%20p%5Bk&plus;1%5D%20%5Ccdot%20p%5Bj&plus;1%5D:将两个部分相乘时的标量乘法次数。

动态规划过程

  1. 初始化:
    • 单个矩阵的计算次数为 0,即 eq?m%5Bi%5D%5Bi%5D%20%3D%200
  2. 枚举链长度 L:
    • L = 2:两个矩阵的连乘。
    • L = 3:三个矩阵的连乘。
    • L = n:完整链的连乘。
  3. 枚举起点 i 和终点 j:
    • eq?j%20%3D%20i%20&plus;%20L%20-%201
  4. 枚举切割点 kk:
    • 计算每个 k 的代价 q,取最小值更新 eq?m%5Bi%5D%5Bj%5D

结果回溯

  1. 利用 eq?s%5Bi%5D%5Bj%5D 表记录最佳切割点。
  2. 使用递归函数 print_optimal_parens 输出最优括号化顺序。

3. 输入输出说明

输入:

矩阵维度序列p = [5, 10, 3, 12, 5],对应:

  • A1:5×10
  • A2:10×3
  • A3:3×12
  • A4:12×5

输出:

  1. 最优计算次数表 m: 表中 eq?m%5Bi%5D%5Bj%5D是从eq?A_ieq?A_j的最少标量乘法次数。

    [[0, 150, 330, 405],
     [0,   0, 360, 330],
     [0,   0,   0, 180],
     [0,   0,   0,   0]]
    
  2. 切割位置表 s: 表中eq?s%5Bi%5D%5Bj%5D 是从eq?A_ieq?A_j 的最佳切割点:

    [[0, 1, 2, 2],
     [0, 0, 2, 2],
     [0, 0, 0, 3],
     [0, 0, 0, 0]]
    
  3. 最优括号化顺序: 使用切割位置表 s 回溯得到括号化顺序:

    ((A1 x A2) x (A3 x A4))
    
  4. 最少标量乘法次数: 从eq?m%5B0%5D%5Bn-1%5D中得到最优值:

    405
    

4. 算法复杂度

  1. 时间复杂度:
    • 共有 eq?O%28n%5E2%29个状态,每个状态需要计算 eq?O%28n%29 个切割点,总时间复杂度为 eq?O%28n%5E3%29
  2. 空间复杂度:
    • 动态规划表 ms 的空间需求为eq?O%28n%5E2%29

总结

  1. 使用动态规划表 ms 分别记录最小计算次数和切割点,避免重复计算子问题。
  2. 通过递归回溯生成最优括号化顺序,清晰地展示了矩阵链乘法的优化过程。
  3. 输出的结果包括最优括号化顺序和最少标量乘法次数,完美解决了问题。

手写过程:

动态规划过程与结果如下(其中矩阵行列标为数学表示法(从1开始)并非代码中真实行列标(从0开始),代码中行列标均-1即可):

5e05785b577b4b8597021f7f4b9e5c57.png

### 动态规划解决矩阵连乘问题最优解 动态规划是一种通过将复杂问题分解为更小的子问题来解决问题的方法。对于矩阵连乘问题,目标是找到一种矩阵链乘法的顺序,使得总的数乘次数最少。此问题可以通过动态规划算法以自底向上的方式高效解决[^1]。 #### 问题描述 给定 n 个矩阵 A1, A2, ..., An,其中 Ai 与 Ai+1 是可乘的(i = 1, 2, ..., n-1)。需要确定计算矩阵连乘积 A1A2...An 的计算次序,使得依此次序计算矩阵连乘积所需的数乘次数最少。输入数据包括矩阵的个数和每个矩阵的规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数[^4]。 #### 动态规划求解步骤 定义 `m[i][j]` 表示计算矩阵链 AiAi+1...Aj 所需的最小数乘次数。同时,定义 `s[i][j]` 记录断点 k 的位置,用于构造最优解的计算次序[^3]。 1. **状态转移方程** 如果 i = j,则 `m[i][j] = 0`,因为单个矩阵不需要相乘。否则,`m[i][j]` 可以通过以下递归公式计算: ```math m[i][j] = min{m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]} (i ≤ k < j) ``` 其中,p[i-1], p[k], p[j] 分别表示矩阵 Ai-1 的行数、Ak 的列数和 Aj 的列数[^2]。 2. **初始化** 对于所有 i,令 `m[i][i] = 0`,因为单个矩阵不需要相乘。 3. **计算顺序** 按照链长 L = j - i + 1 从短到长计算子问题的最优值。对于每个链长 L,依次计算所有可能的子链 AiAi+1...Aj 的最优值[^1]。 4. **构造最优解** 最优解可以通过记录的断点数组 `s[i][j]` 来构造。`s[i][j]` 表示在计算矩阵链 AiAi+1...Aj 时,最后一次分割的位置 k[^3]。 #### 算法实现 以下是动态规划解决矩阵连乘问题的 Python 实现: ```python def matrix_chain_order(p): n = len(p) - 1 # 矩阵个数 m = [[0] * n for _ in range(n)] # 初始化最优成本表 s = [[0] * n for _ in range(n)] # 初始化断点表 # 链长从 2 到 n for l in range(2, n + 1): for i in range(n - l + 1): j = i + l - 1 m[i][j] = float('inf') for k in range(i, j): q = m[i][k] + m[k + 1][j] + p[i] * p[k + 1] * p[j + 1] if q < m[i][j]: m[i][j] = q s[i][j] = k # 记录断点 return m, s # 示例输入 p = [10, 5, 15, 10] # 矩阵维度 m, s = matrix_chain_order(p) # 输出最小计算量 print("最小计算量的值:", m[0][-1]) # 构造最优解 def print_optimal_parens(s, i, j): if i == j: print(f"A{i+1}", end="") else: print("(", end="") print_optimal_parens(s, i, s[i][j]) print_optimal_parens(s, s[i][j] + 1, j) print(")", end="") print("\n最优计算次序:") print_optimal_parens(s, 0, len(p) - 2) ``` #### 复杂度分析 该算法的时间复杂度为 O(n^3),空间复杂度为 O(n^2),其中 n 是矩阵的个数。这是因为我们需要填充一个大小为 n×n 的二维表,并且对于每个子问题,最多需要检查 n-1 种不同的分割方式[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值