动态规划-矩阵连乘问题

问题描述

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i = 1,2,
…n-1。考虑这n个矩阵的乘积。由于竞争乘法满足结合律,故计算矩阵的连乘有许多不同的计算次序。

这种计算次序可以用加括号的方式确定。若一个矩阵连乘的计算次序完全确定,这是就说该连乘已完全加括号。

例如,矩阵连乘A1 A2 *A3 *A4 可以有5种完全加括号的方式:(A1 (A2 (A3 *A4 ))), (A1 ((A2
A3) *A4)),((A1 *A2 )(A3 *A4)),

(((A1 *A2 )*A3
)*A4)。每种加括号的方式确定了一个计算的次序。不同的计算次序与矩阵连乘的计算量有密切的关系。关于矩阵如何相乘这里我就不赘述了

考虑3个矩阵{A1,A2,A3}连乘的例子,假设这3个矩阵的维数分别为 10×100, 100×5,
5×50。若按照((A1*A2)*A3)计算,则计算次数为10×100×5 + 10×5×50 = 7500

若按(A1*(A2*A3))计算,则计算次数为 100×5×50 + 10×100×50 =
75000。第1种方法的计算次数是后者的10倍!由此可以看出,不同的加括号方式确定不同的计算次序对
矩阵乘法的运算量影响是巨大的。
矩阵连乘为题定义如下:给定n个矩阵{A1,A2,…,An},矩阵A1的维数为pi-1×pi, i = 1,2, …, n,如何给矩阵连乘A1*A2*….*An完全加上括号使用矩阵乘法中计算次数最少。

问题分析

若用穷举法,能够证明需要指数时间来求解。但是时间代价高昂。现在考虑用动态规划来求解连乘问题。

为方便起见用Ai…j表示矩阵乘法Ai*Ai+1*….Aj的结果。其中i < j。那么Ai*Ai+1*…..Aj一定在Ak与Ak+1之间被分裂。i<= k < j。问题Ai*Ai+1 … Aj完全加括号的开销等于计算矩阵 Ai…k 与计算 Ak+1…j的开销,再加上他们的结果相乘的开销。问题的最优子结构可以描述如下:假定问题Ai*Ai+1*…Aj被完全加括号的最优方式是在Ak与Ak+1之间被分裂,那么分裂 之后,最优解Ai*Ai+1*….Aj中的子链Ai*Ai+1….Ak一定是问题Ai*Ai+1*…Ak的最优加括号方式。同样,最优解Ak+1*Ak+2…Aj的子链一定是问题Ak+1*Ak+2*…Aj最优加括号方式。

根据上面分析,设m[i,j]表示计算Ai…j所需的最小计算次数 m[i,j] = min{m[i,k]+m[k+1,j]+pi-1pKpj }
其中pi-1和pi分别表示第i个矩阵的行和列

代码c++实现

#include
void main()
{
        int m[8][8], min;
        int r[8] = {10, 20, 50, 1, 100, 4, 20, 2};     /* 矩阵维数 */

        /* 初始化 */
       memset(m,0,sizeof(m));
        /* 每此增量加一 */
        for (int l = 1; l < 7; l++) 
       {
              /* 对于差值为 l 的两个元素 */
              for (int i = 1; i <= 7 - l; i++) 
             {
                  j = i + l;
                  /* 求其最小组合方式 */
                  min = m[i][i] + m[i+1][j] + r[i-1] * r[i] * r[j];
                  middle[i][j] = i;
                  for (int k = i + 1; k < j; k++)
                 {
                       if (min > m[i][k] + m[k+1][j] + r[i-1] * r[k] * r[j]) 
        {
                               min = m[i][k] + m[k+1][j] + r[i-1] *r[k]* r[j];
                                        middle[i][j] = k;
                        }
                  }
                        m[i][j] = min;
             }
        }
        std::cout<1][N];
}

由以上代码可以很容易看出算法的时间复杂度为O(n^3)。即便如此也比穷举法的指数级时间复杂度快。

java实现

public class Matrix{
    //计算最优值
    public void matrixChain(int[]p, int[][]m, int [][]s)
    {
        int n=p.length-1;
        for (int i=1;i<=n;i++) m[i][i] = 0;
        for (int r=2;r<=n;r++)
            for(int i=1;i<=n-r+1;i++){
                int j=i+r-1;
                m[i][j] = 999999999;
                s[i][j] = i;
                for (int k = i; k < j; k++) {
                    int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                    if (t < m[i][j]) {
                        m[i][j] = t;
                        s[i][j] = k;}
                }
            }
    }
    //构造最优解
    public  void TraceBack(int [][]s, int i, int j)
    {
        if(i!=j){
            TraceBack(s,i,s[i][j]);
            TraceBack(s,s[i][j]+1,j);
            System.out.println("Multiply  A["+i+":"+s[i][j]+"] and  A["+(s[i][j]+1)+":"+j+"]");
        }
    }
    // 打印加括号的最优解方案
    public void OptimalParens(int[][] s,int i,int j){
        if(i==j)System.out.print("(A"+i);
        else{
            OptimalParens(s,i,s[i][j]);
            OptimalParens(s,s[i][j]+1,j);
            System.out.print(")");
        }
    }
}

测试类

//矩阵阶乘的测试
public class MatrixText {
    public static void main(String agrs[])
    {
        int[] p={30,35,15,5,10,80,25,65,40,20};
        int n=p.length;
        int [][]m=new int [n][n];
        int [][]s=new int [n][n];
        Matrix m1=new Matrix();
        m1.matrixChain(p, m, s);
        System.out.println("该矩阵阶乘子问题数乘的次数:");
        for(int i=1;ifor(int j=1;jif(i>j)
                {
                    System.out.print("----"+"\t");
                }
                else
                {
                    System.out.print(m[i][j]+"\t");
                }
            }
            System.out.println();
        }
        System.out.println();
        System.out.println("该矩阵阶乘子问题数乘的次数:");
        for(int i=1;ifor(int j=1;jif(i>j)
                {
                    System.out.print("----"+"\t");
                }
                else
                {
                    System.out.print(s[i][j]+"\t");
                }
            }
            System.out.println();
        }
        System.out.println();
        System.out.println("该矩阵阶乘的最优解:");
        m1.TraceBack(s,1,n-1);
        m1.OptimalParens(s,1,n-1);
    }

}

运行结果

该矩阵阶乘子问题数乘的次数:
0 15750 7875 9375 23875 25625 39750 49000 50000
—- 0 2625 4375 20625 21000 36125 44750 45250
—- —- 0 750 10000 15875 27000 38125 40625
—- —- —- 0 4000 14000 22125 35125 39125
—- —- —- —- 0 20000 36250 62250 70250
—- —- —- —- —- 0 130000 145000 124500
—- —- —- —- —- —- 0 65000 84500
—- —- —- —- —- —- —- 0 52000
—- —- —- —- —- —- —- —- 0

该矩阵阶乘子问题数乘的次数:
0 1 1 3 3 3 3 3 3
—- 0 2 3 3 3 3 3 3
—- —- 0 3 3 3 3 3 3
—- —- —- 0 4 5 6 7 8
—- —- —- —- 0 5 6 7 8
—- —- —- —- —- 0 6 6 6
—- —- —- —- —- —- 0 7 7
—- —- —- —- —- —- —- 0 8
—- —- —- —- —- —- —- —- 0

该矩阵阶乘的最优解:
Multiply A[2:2] and A[3:3]
Multiply A[1:1] and A[2:3]
Multiply A[4:4] and A[5:5]
Multiply A[4:5] and A[6:6]
Multiply A[4:6] and A[7:7]
Multiply A[4:7] and A[8:8]
Multiply A[4:8] and A[9:9]
Multiply A[1:3] and A[4:9]
(A1(A2(A3))(A4(A5)(A6)(A7)(A8)(A9))
Process finished with exit code 0

你可能感兴趣的:(算法笔记,动态规划,矩阵连乘)