矩阵连乘

问题定义

  • 输入:给定n个矩阵,矩阵间连乘

    a1 ... an : ai 与 ai + 1 可乘

    令a1:p0 * p1

      ...
    

    an:pn-1 * pn

    则:输入用一维数组表示,P:{p0p1p2...pn}

  • 输出:求最少乘法次数的计算方法

    • 特征:无论在哪里断开,分开部分的矩阵作为一个整体不影响整个矩阵的连乘

    • 对于矩阵序列,不同的乘次序将导致不同的乘法次数

    • 例如:a1 * a2 * a3的乘法次数

      (a1 * a2) * a3 :(p0 * p1 * p2) + (p0 * p2 * p3)

      a1 * (a2 * a3):(p1 * p2 * p3) + (p0 * p1 * p3)

    • 找出最优计算次序以使得矩阵连乘所需要的计算次数最少

蛮力法

枚举每一种划分括号的情况,计算出他们的乘法次数,取最小的划分情况

A1A2A3...An:添加括号,直到只有一个矩阵可以加括号,然后计算他们的连乘次数

A1(A2(A3...An))的连乘次数:p2 * ... * pn + p1 * p3 * pn + p0 * p1 * pn

递归分治

优化暴力枚举(用k表示分割的位置)

每次调用递归式时:传入分割后的矩阵列

递归出口:当传入的矩阵列中只有一个矩阵,返回0

回溯处理:计算两边分割乘法次数的和,计算两边合并的乘法次数再相加,取某个分割位置k时的最小值
递归式: F(1,n) = \begin{cases} \sum_{k=1}^n(min{F(1,k)} + F(k+1, n) + P_0 \times P_k \times P_n), &\ n\ \gt\ 1\\ 0, &\ n\ = 1\\ \end{cases}

  • 优点:存在子问题最优解
  • 缺点:存在重复计算部分
    • 为什么
      1. 递归使用帧无法保存太多的信息
      2. 子问题重叠,因为不同的部分划分可能由相同的子问题组成,那么递归调用中出现重复计算的子问题是随着递归深度变化的

动态规划法

  • 使用空间换时间,将子问题记录下来

    • 那么就将递归分治的缺点改为优点,这下子,解决方法就是利用了动态规划的思想
  • 令 m(i,j) :为i,j之间的矩阵列的最小乘法次数,在这之间使用k作为划分的位置

    • m(i,j) = \begin{cases} \sum_{k=i}^j(min{F(i,k)} + F(k+1, j) + P_{i-1} \times P_k \times P_n), &\ i\ \lt\ j\\ 0, &\ i\ = j\\ \end{cases}

    • 根据上述定义,二维表m的对角线上都是0,m(1,n)是我们需要的最少乘法次数

  • 令s(i,j):为i,j之间矩阵列的最优分割位置

  • 时间复杂度:

    • for(1 to n):枚举序列长度

      • for(0 to r):枚举矩阵子列的起点

        • j = r

          for(k = i + 1 to j):枚举矩阵子序列分割点

          s表记录最优的分割位置、m表最少乘法次数
          
    • 线性时间读s表,构造最优矩阵连乘的计算步骤

import java.util.Arrays;

public class MatrixChainTest {

    /**
     * @param p:矩阵维数序列,其长度减一为连乘矩阵的个数
     * @param memory:ai*...*aj的最小数乘次数
     * @param s:最佳括号断开位置
     * 操作数组时,从下标开始
     */
    public static void matrixChain(int[] p, int[][] memory, int[][] s) {
        int n = p.length - 1;//矩阵个数
        //初始状态对角线置为0
        for(int i = 0; i < memory.length; i++) {
            memory[i][i] = 0;
        }
        //矩阵长度从二(下标0,1)开始,计算不同子矩阵的数乘次数
        for(int r = 1; r < n; r++) {//r+1:矩阵列长度
            for(int i = 0; i < n - r; i++) {//从第一个矩阵开始,枚举r+1长度的矩阵列(的起点)
                int j = i + r;//矩阵列:A_(i+1)*...*A_(j+1)
                //矩阵列长度:j - i + 1
                //断开位置(矩阵列的第几个(从1开始算)位置)与下标关系:x_断 = x_(下+1)
                //设最先断开的位置是A_(i+1)
                memory[i][j] = memory[i][i] + memory[i+1][j] + p[i] * p[i + 1] * p[j + 1];
                s[i][j] = i + 1;
                //枚举断开位置,在矩阵列:A_(i+1)*...*A_(j+1)的从 i+2 到 j+1
                for(int k = i + 1; k < j; k++) {//断开位置从 k + 1开始
                    int temp = memory[i][k] + memory[k+1][j] + p[i] * p[k + 1] * p[j + 1];
                    if(temp < memory[i][j]) {
                        memory[i][j] = temp;
                        s[i][j] = k + 1;
                    }
                }
            }
        }
        System.out.println("矩阵连乘的最少乘法次数为" + memory[0][n - 1]);
    }

    /**
     * 利用矩阵序列最优的分割位置s,构造最优计算次序
     * @param s:最优分割位置
     * @return:矩阵序列p的最优计算次序
     */
    private static String matrixChainMake(int[][] s) {
        int n = s.length;
        String[] temp = new String[n];
        //矩阵序列初始化
        for(int i = 0; i < n; i++) {
            temp[i] = "A" + i;
        }
        //加括号
        for(int i = 0; i < n - 2; i++) {
            int k = s[i][n - 1];//获得分割的位置
            if(k - i > 1) {//括号包住两个以上的矩阵列
                temp[i] = "(" + temp[i];
                temp[k - 1] += ")";
            }
            if(n - k > 1) {
                temp[k] = "(" + temp[k];
                temp[n - 1] += ")";
            }
        }
        //最后答案处理
        StringBuilder rs = new StringBuilder();
        for(int i = 0; i < temp.length; i++) {
            rs.append(temp[i]);
        }
        System.out.println("最优计算次序为:" + rs.toString());
        return rs.toString();
    }

    /**
     *外部调用入口
     * @param p:矩阵列的维数
     * @return 返回矩阵连乘的最优计算次序
     */
    public static String matrixChain(int[] p) {
        int[][] m = new int[p.length - 1][p.length - 1];
        int[][] s = new int[p.length - 1][p.length - 1];
        MatrixChainTest.matrixChain(p, m, s);
        System.out.println("m:");
        array2dShow(m);
        System.out.println("s:");
        array2dShow(s);
        return matrixChainMake(s);
    }

    public static void main(String[] args) {
        int[] p = {30, 35, 15, 5, 10, 20, 25};
        matrixChain(p);
    }

    private static void array2dShow(int[][] s) {
        for(int i = 0; i <s.length; i++) {
            System.out.println(Arrays.toString(s[i]));
        }
    }

}

相关链接:

学习别人的思想,总结自己的想法:

  • 分析一个问题的解决方法,很容易想到暴力,然后再想着优化,发现问题可以被分割成独立的子问题,例如这题就朝分治递归去了;再分析递归分治的过程,发现存在重复计算的子问题,那么出现了动态规划问题的特性:最优子结构、子问题重叠
  • 一步步的分析才能得到结果,不是观测就来的

动态规划基本步骤:
1.找出最优解的性质,并刻划其结构特征。
2.递归地定义最优值。
3.以自底向上的方式计算出最优值。
4.根据计算最优值时得到的信息,构造最优解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容