矩阵链相乘

1,677 阅读4分钟

矩阵链相乘

动态规划

  • 什么是动态规划
    • 通过把复杂的原问题分解为相对简单的子问题的方法
  • 动态规则有哪些特征
    • 重叠子问题:子问题重复出现
    • 最优子结构:一个问题的最优解包含其子问题的最优解
    • 无后效性:某阶段的状态一旦确定,则此后过程的演变不再受此前各种状态及决策的影响
  • 动态规则问题的解决步骤
    • 确定子问题
    • 确定状态转移方程
    • 确定边界条件

题目

  • 给定n个矩阵序列:(A1,A2,A3,...,An)。计算他们的乘积,使得乘法次数最小
  • 矩阵相乘满足结合律,两个矩阵相乘它们的乘法次数是固定的
    • AabAbc矩阵相乘最小乘法次数:a * b * c

分析

  • 例如:p=[5,10,3,12,5],A1->5 * 10(A1矩阵5行10列,以下同理),A2->10 * 3,A3->3 * 12,A4->12 * 5,矩阵相乘的计算代价为405,最优方案为((A1A2)(A3A4))
  • 共有4种计算方式:A1A2A3A4,A1(A2A3)A4,A1A2(A3A4),A1(A2(A3A4))
  • 列出下表,其中m[i,j]表示Ai~j的最小乘法次数
A1A2A3A4 A1(A2A3)A4 A1A2(A3A4) A1(A2(A3A4))
第一步 A1A2 A2A3 A3A3 A3A3
第二步 m[1,2]*A3 A1*m[2,3] A1A2 A2*m[3,4]
第三步 m[1,3]*A4 m[1,3]*A4 m[1,2]*m[3,4] A1*m[2,4]
  • 符号定义:

  • 用动态规划的步骤解决上面的问题

  • 子问题分析,用二维数组做为存储结构,则矩阵间相乘的最优次数如下:

矩阵个数 1 2 3 4
1 0 m[1,2] m[1,3] m[1,4]
2 0 0 m[2,3] m[2,4]
3 0 0 0 m[3,4]
4 0 0 0 0
  • 在此数组中,最终目标是求出m[1,4],也就是A1A2A3A4四个矩阵相乘的最优乘法次数。设想可以通过已经得出的结果求出最终结果,在数组中利用对角线的顺序求解,如下图所示:

    • 第一次求出第一条对角线上的结果:m[1,2]、m[2,3]、m[3,4]

    • 第二次求出第二条对角线上的结果:m[1,3]、m[2,4]

    • 第三次求出第三条对角线上的结果:m[1,4]

  • 第一条对角线上的结果是都需要求的,第二条和第三条可以根据已经得出的值进行计算,例如:

    • m[2,4] = min(m[2,2]+m[3,4]+p1 * p2 * p4, m[2,3]+m[4,4]+p1 * p3 * p4)
    • m[1,4] = min(m[1,1]+m[2,4]+p0 * p1 * p4,m[1,2]+m[3,4]+p0 * p2 * p4,m[1,3]+m[4,4]+p0 * p3 * p4)
  • 用i表示行,j表示列,d表示对角线,

    • j = d + i
    • i的最小范围为1,最大范围 = 矩阵个数-d
    • d的范围 = 1 ~ 矩阵个数-1

代码实现

  // 最小乘法次数
function MatrixChain(p){
    let num = p.length-1// 矩阵的个数
    let m = [] // 用来存储m[i,j],表示Ai~j的最小乘法次数
    let s = [] // 存储矩阵下标
    let d //对角线
    let template // 相乘次数

    for(let i = 1; i <= num; i++){
        m[i] = []
        m[i][i] = 0

        s[i] = []
        s[i][i] = 0
    }

    for(d = 1; d <= num-1; d++){  // d的范围 = 1 ~ 矩阵个数-1
        for(let i = 1; i <= num - d; i++){ // i的最小范围为1,最大范围 = 矩阵个数-d
            j = i + d // j = d + i
            m[i][j] = Number.MAX_SAFE_INTEGER; 
            for(let k = i; k <= j - 1; k++){
                template = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; 
                if(template < m[i][j]){
                    m[i][j] = template
                    s[i][j] = k
                }
            }
        }
    }
    printOrder(s, 1, num); 
    return m[1][num]
}

// 最优解的括号顺序
function printOrder(s,i,j){
    if (i == j) {
        console.log("A[" + i + "]");
    } else {
        console.log("(");
        printOrder(s, i, s[i][j]);
        printOrder(s, s[i][j] + 1, j);
        console.log(")");
    } 
}

let p=[10, 100, 5, 50]
console.log('最小乘法次数:',MatrixChain(p))

复杂度

  • 时间复杂度
    • 计算代价的时间 O(n^3)
    • 构造最优解的时间 O(n)
    • 总的时间复杂度 O(n^3)
  • 空间复杂度 O(n^2)

参考资料

  • javascript数据结构与算法
  • 出自:原址