矩阵连乘问题的递归、自底向上、自顶向下(Java)

问题

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10×100,100×5和5×50,采用(A1A2)A3,乘法次数为10×100×5+10×5×50=7500次,而采用A1(A2A3),乘法次数为100×5×50+10×100×50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次。
在这里插入图片描述

分析

由上面的例子可见,相同的矩阵连乘用不同的顺序,其效率差距可能是巨大的,甚至是达到10倍以上。

首先我们先来看下两个矩阵相乘的数乘次数是如何计算的

在这里插入图片描述
简而言之,就是行1✖列1(行2)✖列2

我们再看看多个矩阵
在这里插入图片描述

动态规划

我们发现有重复计算的矩阵连乘,那我们可以存储起来去查表。但是这是建立在每一步都是最优解的基础上去存储的,那么怎么求最优解呢?

  1. 寻找最优子结构
    先看上课讲的PPT吧
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    此问题最难的地方便在于找到最优子结构。PPT中表达的意思是,若用动态规划方法,那么有一k使得Ai…Aj分裂(Ai——Ak,Ak+1——Aj),此时他们的两部分自身也一定都要求是最小的数乘次数(就是把两个划分部分看成是两个矩阵P、Q),且两部分再相乘为整个矩阵链的最小,那么k就是最佳括号位置

    可是k有这么多位置,我们怎么知道哪个位置最佳呢?最简单的方法,那我们就都试一遍啊!
    在这里插入图片描述

  2. 建立递归关系
    还是先上PPT
    在这里插入图片描述
    对于矩阵自身,是不可能相乘的,所以m[i,i]=0
    在这里插入图片描述
    根据分析1所知,i——j中存在某个划分位置k(假设已经确定),能使得Ai——Aj的数乘次数最小,值为两个部分的最小数乘次数相加再加上两个部分相乘的次数,即
    m[i,j] = m[i,k] + m[k+1,j] + pi-1 ✖ pk ✖ pj
    在这里插入图片描述
    但是因为k有很多种选择(在i——j中有j-i种选择)而最优括号位置只有一个,那么到底是谁呢?

我们只需要逐个检查,然后两两对比,就能找出其中最小的m[i,j],这样就得到了最优位置。

因此矩阵链Ai——Aj的最小数乘递推式为
在这里插入图片描述

在计算得到最小m[i, j]的同时,也要把相应的k的位置放在s[i, j]中。

在这里插入图片描述

方法

普通递归方法

这种方法其实也是一种自顶向下的思想,从大问题出发,假设大问题的最优解,然后逐步向下到小问题,直到得到最小问题的最优解,就可以再还原到大问题的最优解了。

可以将一个矩阵链分解成这样的结构,由图可见,许多子问题被重复计算了很多次(圈出两个例子),这里就是我们可以用dp去改进的地方。
在这里插入图片描述
在这里插入图片描述

    /*
     * @Title recursiveMatrixMultiply
     * @Description 普通递归算法  i:矩阵序列的左起始点 j:矩阵序列的右起始点
     * @author 滑技工厂
     * @Date 2020/4/22
     * @param [i, j]
     * @return int 计算出来是Ai——Aj这段矩阵序列的最小相乘次数
     * @throws
     */
    public static int recursiveMatrixMultiply(int i, int j) {
        //表示最小值,用来与i<j时每次k划分的m[i,j]进行比较,若m[i,j]小则把其赋值给tempMin,表示目前他是最小
        int tempMin = MAX;
        if (i == j)
            return 0;
        //表示从k=i——j-1的所有划分情况,在这些情况中,每次k都要与上次比大小,找出最小的并付给tempMin
        //不能等于j,因为上限是j,递归方程中有k+1---j的值
        for (int k = i; k < j; k++) {
            //k表示i--j中的分割位置,分割为i——k,k+1——j
            int result = recursiveMatrixMultiply(i, k) + recursiveMatrixMultiply(k + 1, j) + p[i - 1] * p[k] * p[j];
            //如果得到的结果比当前最小值小,则更新最小值,并更新s[i,j]的值,也就是划分位置k
            if (result < tempMin) {
                tempMin = result;
                s[i][j] = k;
            }
        }

        return tempMin;
    }

自底向上的动态规划方法

与自顶向下有所不同,自底向上是首先去逐步求小问题的情况,然后在合并成更大一点点的,再合并,直到大问题。
在这里插入图片描述
在这里插入图片描述
直观一点就是下图。

在这里插入图片描述
这样通过不同的组合就可以得到整个矩阵链的最佳开销(如A1——A2与A3——An组合)
在这里插入图片描述
这里我们通过i与j的位置来控制小链的长度,小链先从1,然后是2,然后是3,直到n。i与j的差就是长度,每个长度是一层,因此要按照箭头的方向依次计算,直到一层完毕,再换链长。(另外,每一个m[i,j]的计算过程就是那个递推式的过程,依旧要循环找。)

    /*
     * @Title downToUpDPMatrixMultiply
     * @Description 自底向上的dp解法
     * @author 滑技工厂
     * @Date 2020/4/24
     * @param []
     * @return void
     * @throws
     */
    public static void downToUpDPMatrixMultiply() {
        //定义链长的最大长度
        int n = p.length - 1;
        //r为每次计算的矩阵的链长,2——n
        for (int r = 2; r <= n; r++) {
            //定义在每次链长r中,矩阵链中各个矩阵结合的小链左起点i   终止位置为n-r+1 j根据r+i-1来定
            for (int i = 1; i <= n - r + 1; i++) {
                int j = i + r - 1;
                //m[i][j]为对应的r-1的小链的最优乘积次数值加上乘上矩阵[i]的次数
                m[i][j] = m[i + 1][j] + p[i - 1] * p[i] * p[j];
                //另外这里就代表k=i的划分
                s[i][j] = i;
                //每个小链里的划分情况,找出最小的那个
                for (int k = i + 1; k < j; k++) {

                    int min = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                    if (min < m[i][j]) {
                        m[i][j] = min;
                        s[i][j] = k;
                    }
                }
            }
        }

    }

自顶向下的动态规划方法

这个与递归其实很像,差别就是在求m[i,j]的时候先查表,如果有则直接返回,没有则依照递推公式计算,此时计算出m[i,j]时,要存在表中,以便大问题去调用自己的结果,避免多次计算,节省效率。

    /*
     * @Title upToDownDPMatrixMultiply
     * @Description 自顶向下的dp解法
     * @author 滑技工厂
     * @Date 2020/4/23
     * @param [i, j]ij同上
     * @return int
     * @throws
     */
    public static int upToDownDPMatrixMultiply(int i, int j) {
        int tempMin = MAX;
        if (i==j)
            return 0;
        //与递归算法不同的是,这里可以查表,对于已经计算过一次的m[i][j],可以直接查表获得对应的计算次数,而不用多次去计算m[i][j]
        if (m[i][j]!=0)
            return m[i][j];
        for (int k = i; k < j; k++) {
            int result = upToDownDPMatrixMultiply(i, k) + upToDownDPMatrixMultiply(k + 1, j) + p[i - 1] * p[k] * p[j];
            if (result < tempMin) {
                tempMin = result;
                s[i][j] = k;
            }
            m[i][j] = tempMin;

        }
        return tempMin;
    }

好啦,本篇博客到这里就结束了,如果你喜欢的话请给我一个👍,觉得我哪里有错误或者不理解也可以在评论区提出。你们的支持就是我最大的动力。
完整的代码请移步的我GitHub:滑技工厂——Homework-AlgorithmAnalysis,如果能给我一颗⭐⭐自然是极好的。
再见!

(每篇博客少不了的猫头)
在这里插入图片描述

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值