动态规划-矩阵连乘详解(java)

问题分析

矩阵连乘问题就是对于给定n个连乘的矩阵,找出一种加括号的方法,使得矩阵连乘的计算量(乘法次数)最小。

首先解释下什么是矩阵可乘,当然学过线代的小伙伴应该明白:

如果两个矩阵,第一个矩阵的列数等于第二个矩阵的行数时,那么这两个矩阵是可乘的。

其次,矩阵相乘后的结果是什么 ?

两个矩阵相乘的结果矩阵,其行,列分别等于第一个矩阵的行,第二个矩阵的列。如果有很多个矩阵相乘呢?,多个矩阵相乘的结果矩阵,其行,列分别等于第一个矩阵的行,最后一个矩阵的列。而且无论矩阵的计算次序如何都不影响它们的结果矩阵。

既然要用动态规划来解决这类问题,首先该问题是否具有最优子结构性质。

(1)分析最优解的结构特征。

假设我们已经知道了在第k个位置加括号会得到最优解,那么原问题就变成了两个子问题:(AiAi+1…Ak),(Ak+1Ak+2…Aj)。原问题的最优解是否包含子问题的最优解呢?

假设AiAi+1…Aj的乘法次数是c,AiAi+1…Ak的乘法次数是a,Ak+1Ak+2…Aj的乘法次数是b,(AiAi+1…Ak)和(Ak+1Ak+2…Aj)的结果矩阵相乘的乘法次数是d,那么c=a+b+d,无论两个子问题(AiAi+1…Ak),(Ak+1Ak+2…Aj)的计算次序如何,都不影响它们的结果矩阵,两个结果矩阵相乘的乘法次数d不变。因此我们只需要证明如果c是最优的,则a和b一定是最优的(即原问题的最优解包含子问题的最优解)。

(2)建立最优值递归式

用m[i][j]表示AiAi+1…Aj矩阵连乘的最优值,那么两个子问题(AiAi+1…Ak),(Ak+1Ak+2…Aj)对应的最优值分别是m[i][k],m[k+1][j]。剩下的只需要考查(AiAi+1…Ak)和(Ak+1Ak+2…Aj)的结果矩阵相乘的乘法次数了。

设矩阵Am的行数为pm,列数为qm,m=i,i+1,…j,且矩阵是可乘的,即相邻矩阵前一个矩阵的列等于下一个矩阵的行(qm=pm+1)。(AiAi+1…Ak)的结果是一个pi*qk矩阵,(Ak+1Ak+2…Aj)的结果是一个pk+1*qj矩阵,qk=pk+1,两个结果矩阵相乘的乘法次数是pi * pk+1 *qj

矩阵连乘最优值递归式:

当i=j时,只有一个矩阵,m[i][j]=0;

当i>j时,m[i][j]=min{m[i][k]+m[k+1][j]+pipk+1qj}

如果用一维数组p[]来记录矩阵的行和列,第i个矩阵的行数存储在数组的第i-1位置,那么pi * pk+1 *qj对应的数组元素相乘为p[i-1]*p[k]*p[j],原递归式变为

java求两个矩阵的乘积 java实现矩阵连乘_最优解


(3)自底向上计算并记录最优值

先求两个矩阵相乘的最优值,再求3个矩阵相乘的最优值,直到n个矩阵连乘的最优值

(4)构造最优解

上面得到的最优解只是矩阵连乘的最小的乘法次数,并不知道加括号的次序,需要从记录表中还原加括号次序,构造出最优解,例如A1(A2A3)。

这个问题是一个动态规划求矩阵最小计算量的问题,将问题分为小规模的问题,自底向上,将规模放大,直到得到所求规模的问题的解

算法设计

采用自底向上的方法求最优解,对于每一个小规模的子问题都求最优解,并记录最优策略(加括号的位置),具体算法设计如下。
(1)确定合适的数据结构
采用一维数组p[]来记录矩阵的行和列,第i个矩阵的行数存储在数组的第i-1位置,列数存储在数组的第i位置。二维数组m[][]来存放各个子问题的最优值,二维数组s[][]来存储各个子问题的最优决策(加括号的位置)。
(2)初始化
采用一维数组p[]来记录矩阵的行和列,m[i][j]=0,s[i][j]=0,其中i=1,2,3,…,n。
(3)循环阶段
按照递归关系式计算2个矩阵Ai,Ai+1相乘的最优解,j=i+1,并将其存入m[i][j],同时将最优策略计入s[i][j],i=1,2,3,…,n-1。
按照递归关系式计算3个矩阵相乘Ai,Ai+1,Ai+2相乘时的最优值,j=i+2,并将其存入m[i][j],同时将最优策略记入s[i][j],i=1,2,3…n-2。
以此类推,直到求出n个矩阵相乘的最优值m[1][n]。
(4)构造最优解
根据最优决策信息数组s[][]递归构造最优解。s[1][n]表示A1A2A3…An最优解的加括号位置,即(A1A2…As[1][n])(As[1][n+1]…An),我们在递归构造两个子问题(A1A2…As[1][n])(As[1][n+1]…An)的最优解加括号位置,一直递归到子问题只包含一个矩阵为止。

代码:

import java.util.Scanner;
public class Main {
    static int N=100;
    static int []p=new int[N];//记录矩阵的行和列,第i个矩阵的行数存储在数组的第i-1个位置,列数存储在数组的第i个位置。
    static int [][]m=new int[N][N];//表示AiAi+1A...Aj矩阵连乘的最优值
    static int [][]s=new int[N][N];//存放各个子问题的最优决策
    static int n;//多少个数组
    static void matrixchain(){
        int i,j,r,k;
        //初始化数组,使m[][],s[][]对角线上的元素为0
        for( i=0;i<N;i++){
            for (j=0;j<N;j++){
                if(i==j){
                    m[i][j]=0;
                    s[i][j]=0;
                }
            }
        }
        for(r=2;r<=n;r++){//r为问题规模,处理不同规模的子问题
            for(i=1;i<=n-r+1;i++){
                j=i+r-1;
                m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];//决策为k=i的乘法次数
                s[i][j]=i;//子问题的最优策略是i
                for(k=i+1;k<j;k++){//对从i+1到j的所有决策,求最优值
                    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;
                    }
                }
            }
        }
    }
    static void print(int i,int j){
        if(i==j){
            System.out.print("A["+i+"]");
            return;
        }
        System.out.print("(");
        print(i,s[i][j]);
        print(s[i][j]+1,j);
        System.out.print(")");
    }
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入矩阵的个数n个数:");
        n=sc.nextInt();
        int i,j;
        System.out.println("请一次输入每个矩阵的行数和最后一个矩阵的列数:");
        for (i=0;i<=n;i++){
            p[i]=sc.nextInt();
        }
        matrixchain();
        print(1,n);
        System.out.println();
        System.out.println("最小计算量的值为:"+m[1][n]);
    }
}

运行示例

java求两个矩阵的乘积 java实现矩阵连乘_矩阵连乘_02