算法导论,矩阵链乘法(动态规划)

直入主题,5.27学的矩阵链相乘(动态规划)

题目理解:

        1.原题

                要求:对A1,A2,A3......An进行矩阵的乘法(线性代数的基础知识),求通过添加括号,以达到的最小乘法次数

        2. 题目理解

                乘法:由于矩阵乘法的结合律,只要不调整矩阵乘法过程中的位置,就不会导致答案不同

                最小乘法次数: 通过完全括号化(其实就是加括号运用矩阵乘法结合律实现),实现乘法次数的减少,由于括号化之后可以看成一个整体,也就变成了状态的转移,形成一个动态规划的求解全局最优解的题目

解题思路(动归四部曲)

        0. 题目理解

                1.一个矩阵乘法是A.rows乘B.columns,a行b列 x b行c列,就会进行b次的乘法,一共有a x c的乘法,所以一共是a x b x c

                2. 我们还需要额外保存一下所有的原始代价,开拓一个P数组

                3. 不同的顺序会导致不同的计算量,类似于A1为10x100, A2为100x5,A3为5x50

如果是((A1A2)A3),则计算量为10 x 100 x 5 + 10 x 5 x 50 = 7500

如果是(A1(A2A3)),则计算量为100 x 5 x 50 + 10 x 100 x 50 = 75000

所以当两个矩阵相乘的顺序不同,会导致整体的计算量不同,因此需要决定好最优决策,因此可以与一种最优化算法有关——动态规划

        1. dp数组下角标含义

               一般开设二维数组递归,在这一题这是必要的,无法压缩

                memo[i][j],i代表第i个矩阵,j代表第j个矩阵,这是最简单的理解,先这样看看,这也是一般化的解,如果有特殊情况再回来看就可以啦

        2. 递推公式理解

                对于这道题目,我们可以知道我们状态的转移都来自于括号括在哪里,并且不同的括号的代价也不同,每一次状态转移的时候都是要求最优化的状态转移,最优化就是在i到j中的矩阵找到一个位置将我们的括号放进去,这个时候就是最优解啦

       memo[i][j] = \begin{bmatrix} & 0 , && i == j\\ & min(memo[i][k] + memo[k + 1][j] + P(i - 1)P(k)P(j)), i<=k<j && i < j \end{bmatrix}        

        现在开始对题目递推公式的解释啦:

                首先我们多引入了一个k,i <= k < j其关键含义就是我们会在k与k + 1插入一个括号,使得这个时候我们所需的乘数最小化。

                状态转移时的情况:转移状态就是括号的移动,所以当括号转移的时候就是k值的移动,所以就是k的改变,而ij是不会在外面随着k改变,此时就是通过k的变化继承上一次的代价(一共有两个部分,一个部分是最优的左侧,另一个部分是最优的右侧),同时,我们也要把自己的乘法的代价算进去,我们通过0.题目理解处的公式,可以知道最优的左侧x最优的右侧,最优的最左侧的值是P(i - 1),最优的最右侧是P(j),中间则是P(k),所以最后要加上 P(i - 1)P(k)P(j)这样的一个长串代表自己的代价

                小小的总结:前两个由状态转移方程得出,最后一个由0.题目理解得出

                        memo[i][k]代表了最优化情况下左半边的代价

                        memo[k + 1][j]代表了最优化情况下右半边的代价

                        P(i - 1)P(k)P(j)代表了最优化情况下自己的代价

        3. dp数组初始化

                由于当i == j时,这时候意味着最优化就是自己,那么代价当然为0,所以memo[i][i] == 0

                当 i < j的时候,此时初始化因为都要取最小值,所以没取到的时候应该要选正无穷,此时才能成立,能够不断更新最小值(大部分初始化就分为这两种,一种取负无穷不断更新最大值,一种取正无穷不断更新最小值)

        4. 遍历方式

                由于开的是一个二维数组,此时重要的就是i,j的遍历,我们默认i是小的也就代表着i是数组维度的部分,j是大的也就是数组长度的部分,但是我们发现,memo[k + 1][j],k + 1一定是大于i的,所以如果i是正向遍历的话,此时所有的数都是正无穷,因为正无穷的代价被提前加上去了,由此得知我们的i是反向遍历

                对于i和j呢,我们发现好像没有什么影响,所以直接正向遍历先跑跑看

代码

total = []  # 储存所有矩阵的行和列个数
n = int(input())  # 代表输入的矩阵个数
t_round = 0
deal = []  # 储存每一个矩阵的代价值的数组,代价值的解释: 由于每个矩阵相乘是A.rows乘B.columns,所以代价值就等于每一个矩阵的列,不过第一个矩阵还需要带上自己的行
for _ in range(n):
    temp = list(map(int, input().split()))  # 对应输入矩阵的行和列个数
    total.append(temp)
    if t_round == 0:
        deal = [temp[0]]   # 初始化,存储第一个矩阵的行, deal数组的长度为n + 1个
    deal.append(temp[1])
    t_round += 1
memo = [[float("inf")] * n for i in range(n)]  # 存储中间值的
# 初始化结束,现在开始做题,优先计算对角线方向上的(实际手算的话,但是在这题里面,是横向计算,不断更新)
for i in range(n - 1, -1, -1):
    rows1, columns1 = total[i]
    price1 = deal[i]
    for j in range(i, n):
        if i == j:  # dp数组的初始化 
            memo[i][j] = 0
            continue
        price2 = deal[j + 1]
        rows2, columns2 = total[j]
        for k in range(i, j):
            price_k = deal[k + 1]
            memo[i][j] = min(memo[i][j], memo[i][k] + memo[k + 1][j] + price1 * price_k * price2)

for i in memo:
    print(i)

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值