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

问题:使用动态规划方法,求解如下矩阵连乘问题:已知有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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值