加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

动态规划(二):矩阵链乘法

(2012-02-27 15:34:02)
标签:

杂谈

一、问题描述

给定一个矩阵序列<A1,A2,…,An>,计算乘积A1A2An。要求找出一个加全部括号的方式,使得标量乘法的次数最小。

加全部括号

性质1A1A2An相乘,必须满足前一个矩阵的列数等于后一个矩阵的行数。

证明:假设Ai的列数为pii=1,…,n。下面我们来看各矩阵的行数。

n=2时,根据矩阵乘法的定义可知,A1的列数等于A2的行数。因此,A2的行数等于p1

n=3时,因为A1A2相乘得到的新矩阵的列数为p2A1A2A3可以相乘,因此A3的行数为p2

依次类推,Ai(i2)的行数为pi-1,得证。

性质2,序列中的任何n个相邻矩阵都可以直接相乘,n2

证明:

根据性质1可知,任意两个相邻矩阵都可以相乘,即n=2时,满足上述性质。

n=3时,假设我们选择任意相邻矩阵Ai-1AiAi+1,根据矩阵性质,Ai-1Ai得到的新矩阵为pi-2×pi,而Ai+1pi×pi+1,因此Ai-1Ai得到的矩阵可以与Ai+1相乘,即Ai-1AiAi+1可以相乘。由于这三个矩阵是任意选择的,因此,n=3时,满足上述性质。

依次类推,对于任意n>=2,均满足上述性质。得证。

 

加全部括号具体步骤如下:

步骤一:选取相邻的两个矩阵,加括号;

步骤二:将步骤一中的括号看做一个新的矩阵,放回原先位置,得到一个新的序列;

步骤三:如果新序列中矩阵个数等于1,则结束,否则,返回步骤一。

也可以按照下面步骤进行加括号:

步骤一:如果当前序列只有一个矩阵,则不需加括号,否则将整个序列最外层加一个括号,使其成为一个新序列;

步骤二:检查序列里面是否有两个以上矩阵;

步骤三:如果是,则在这些矩阵中找一个分界点,将其分为两个新序列,对这两个新序列分别从步骤一开始重复执行上述步骤。

 

对于任意两个矩阵AiAi+1相乘,其乘法次数为pi-1pi pi+1,不同的加全部括号,所需要的乘法次数可能相差很大。

假设现在有三个矩阵A1A2A3相乘,维数分别为:10×100100×55×50

1、如果我们采用如下方式加全部括号:

(A1A2A3)

首先计算A1A2),乘法次数为p0p1 p2,得到新矩阵的维数为p0× p2

计算(A1A2A3),乘法次数为p0p2 p3

总的计算次数为p0p1 p2+ p0p2 p3=10×100×5+10×5×50=7500

 

2、如果我们采用如下方式加全部括号:

(A1A2 A3)

首先计算A2 A3),乘法次数为p1 p2 p3,得到新矩阵的维数为p1× p3

计算(A1A2 A3),乘法次数为p0p1 p3

总的计算次数为p1 p2 p3+ p0p1 p3=100×5×50+10×100×50=75000

 

第二种方式需要的次数是第二次的10倍!

 

二、问题分析:

由前面可知,一个序列如果只有一个矩阵,则只有一种方式加全部括号,如果有两个或两个以上的矩阵,则必然可以看做两个子序列的乘积,且这两个子序列也是加全部括号。我们用cost(i,j)表示序列AiAj在最优加全部括号时的标量乘积次数,则

http://s12/middle/70ec9a6fhb9ed2baf748b&690

其中pi-1pk pj为子序列AiAkAk+1Aj相乘时的标量相乘次数。

三、问题求解

每一对满足1ijnij都对应原问题的一个子问题,子问题个数为http://s3/middle/70ec9a6fhb9ed3616ea92&690,其中第一项表示i<j时的子问题个数,后一项表示i=j时的子问题个数。如果采用递归方法求解,则每个子问题都会被求解多次,因此,我们也采用自底向上的动态规划方式求解。

首先定义一个二维矩阵valuevalue[i][j]存放子序列AiAj在最优加全部括号时的标量乘积次数,我们只使用j>=i的部分,且value[i][i]=0

动态规划的实现步骤如下:

步骤一:令step=0

步骤二:对于所有i=0n,计算并保存value[i][i+step

步骤三:若step=n-1,则结束,否则step=step+1,返回步骤二。

四、例题

假设现在有如下矩阵序列:

矩阵

维数

A1

30×35

A2

35×15

A3

15×5

A4

5×10

A5

10×20

A6

20×25

C语言实现如下:

 

#include <stdio.h>

#include <stdlib.h>

#define ArrS 6   

//数组个数

 

int m_value(int i,int j,int value[][ArrS+1],int Num1,int dimentions[],int position[][ArrS+1]);

void show(int position[][ArrS+1],int i,int j);

 

int main()

{

       int position[ArrS+1][ArrS+1]={0}; //position[i][j]=k,表示子序列被分为i,...,kk+1,...,j两组

       int value[ArrS+1][ArrS+1]={0};//value[i][j]表示子序列i...j相乘所需最小次数

       int dimentions[ArrS+1]={30,35,15,5,10,20,25}; //数组i的维数是dimentions[i-1]dimentions[i]

      

       int i,j,L;

 

       for(j=0;j<ArrS;j++)

           for(i=1;i+j<=ArrS;i++)

               value[i][i+j]=m_value(i,i+j,value,ArrS+1,dimentions, position);

 

      

       printf("Show the position[i][j]:\n");       

    for(i=1;i<=ArrS;i++)

    {

           for   (j=1;j<=ArrS;j++)

               printf("�, ",position[i][j]);

           printf("\n");

       }

                

 

       printf("\n\nThe path is:\n");

       show(position,1,6);

 

    system("PAUSE"); 

    return 0;

}

 

int m_value(int i,int j,int value[][ArrS+1],int Num1,int dimentions[],int position[][ArrS+1])

{

       if(j==i)

       {

              position[i][j]=i;

              return 0;

       }

             

       int k,min;

       min=dimentions[i-1]*dimentions[i]*dimentions[j]+value[i+1][j];

       position[i][j]=i;

       for(k=i+1;k<j;k++)

           if(min>dimentions[i-1]*dimentions[k]*dimentions[j]+value[i][k]+value[k+1][j])

           {

               min=dimentions[i-1]*dimentions[k]*dimentions[j]+value[i][k]+value[k+1][j];

               position[i][j]=k;

              }

      

       return min;

}

      

void show(int position[][ArrS+1],int i,int j)

//用递归的方法去显示完整路径,调用的方式是 show(1n

{

       if (j<=i)

           return;

       int a;

       a=position[i][j];

       printf("position[%d][%d]=%d  \n",i,j,position[i][j]);

       show(position,i,a);

       show(position,a+1,j);

}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有