每日算法练习——动态规划(矩阵链乘法)

知识补充:

矩阵链乘法问题

给定一个n个矩阵的序列〈A1,A2,A3…An〉

,我们要计算他们的乘积:A1A2A3…An,由于矩阵乘法满足结合律,加括号不会影响结果,但是不同的加括号方法,算法复杂度有很大的差别:
考虑矩阵链:〈A1,A2,A3〉,三个矩阵规模分别为10×100、100×5、5×50
如果按((A1A2)A3)方式,需要做10∗100∗5=5000次,再与A3相乘,又需要10∗5∗50=2500,共需要7500次运算:
如果按(A1(A2A3))方式计算,共需要100∗5∗50+10∗100∗50=75000次标量乘法,具有10倍的差别。可见一个好的加括号方式,对计算效率有很大影响。
为了得到所需乘法次数最少的方案,需要计算所有种方案的代价。
对一个n个矩阵的链,令P(n) 表示可供选择的括号化方案的数量。
完全括号化方案的矩阵乘积可以描述为两个完全括号化的部分相乘的形式,
P(n)=1 , n=1
P(n)=∑n−1k=1P(k)P(n−k), n≥2
k为分割点,即第k个矩阵和第k+1个矩阵之间可以看出括号化方案的数量与n呈指数关系Ω(2n),

若采用暴力搜索比较所有括号化方案的代价,效率很差!

动态规划

1.最优括号化方案的结构特征

由于要求得矩阵链〈Ai,Ai+1,Ai+2…Aj〉

的最优括号化方案,我们可以将问题划分为两个子问题〈Ai,Ai+1…Ak〉和〈Ak+1,Ak+2…Aj〉的最优括号化方案的组合,这也是可以采用动态规划的一个重要标示。即一个大的问题的解是其子问题的组合。我们需要遍历所有的k值i≤k≤j−1

即考查所用的划分点。

2.递归求解方案

令m[i,j],i≤j

标示矩阵链〈Ai,Ai+1,Ai+2…Aj〉的最优括号化方案所需乘法次数的最小值。
当i=j时,m[i,j]=0,只有一个矩阵不涉及乘法运算
当i<j时,假设在矩阵链〈Ai,Ai+1,Ai+2…Aj〉分割点位置为Ak和Ak+1之间,如上分析,m[i,j]为m[i,k]与m[k+1,j]的代价和,还要加上两者最后相乘涉及的运算次数。假如Ai的大小为pi−1×pi,则子矩阵链m[i,k]乘积后的矩阵大小为pi−1×pk, m[k+1,j]乘积后的大小为pk×pj,所以最后一次乘积做的乘法运算次数为pi−1pkpj。
即:
m[i,j]=0,(i=j)
m[i,j]=min{m[i,k]+m[k+1,j]+pi−1pkpj},i<j,i≤k≤j−1

 

3.计算最优代价

递归算法会多次遇到同一个子问题,与钢铁切割很类似,每一次高层的运算,都会调用底层结果,越是底层,被调用的次数越多。所以可以采用自底向上的方法,先对底层逐个求解,当上层需要调用底层时,底层已经被求解完毕。
用m[i][j]二维矩阵保存对应链〈Ai,Ai+1,Ai+2…Aj〉

长度为j-i+1的最优计算代价q。
用s[i][j]二维矩阵保存对应链〈Ai,Ai+1,Ai+2…Aj〉长度为j-i+1的最优划分位置k。

上文来源:https://blog.csdn.net/luoshixian099/article/details/46344175

// c语言函数实现
void Matrix_Chain_Order(int p[],int n)
{
    int i,j,L,k,q;
    for(i=1;i<=n;i++)//先对单个矩阵的链,求解,即所有m[i][i] =0;
    {
        m[i][i]=0;  
    }
    for(L=2;L<=n;L++)  //L 表示i,j间长度,即矩阵链的长度,在n允许的范围内逐渐增加L
        for(i=1;i<=n-L+1;i++)   //在给定p[]中的矩阵链中,对所有种长度为L的情况计算
    {
            j = i+L-1;
            m[i][j]=-1;
            for(k=i;k<=j-1;k++)
            {
                q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];//计算划分的代价,并将最有代价和位置分别存在m[i][j]和s[i][j]中                if(q<m[i][j] || m[i][j]==-1)

                {
                    m[i][j]=q;
                    s[i][j]=k;
                }
            }
    }

}

 

构造最优解

矩阵链〈Ai,Ai+1,Ai+2…Aj〉

,由于二维矩阵s[i][j]记录了对应划分位置k,指出了应该在Ak和Ak+1之间,同样在矩阵链\left[中最优划分位置一定保存在数组s[i][s[i,j]]内,矩阵;链〈Ak+1,Ak+2…Aj〉的最优划分位置一定保存在s[s[i][j]+1]][j]数组内,可以不断递归出最优解。

示例:

计算下列矩阵最优链乘

矩阵A1A2A3A4A5A6
规模30*3535*1515*55*1010*2020*25

分析:

矩阵输入初始化为p[] = {30,35,15,5,10,20,25}

今天没时间了,就注释分析吧

示例代码:

#include <stdio.h>
#include <stdlib.h>

int m[7][7]={0};    // 记录ij之间最优代价
int s[7][7]={0};    //记录ij之间最优划分的位置

void Print(int s[][7],int i,int j)  //打印划分结果的递归函数
{
    if(i==j)    //当划分到单个矩阵时,打印这个矩阵
    {
        printf("A%d",i);
    }
    else    //非单个矩阵时进入递归
    {
        printf("(");
        Print(s,i,s[i][j]);     //在ij之间划分后的左边部分再次划分的最优位置必定在i和s[i][j](本次划分的最优位置)之间
        Print(s,s[i][j]+1,j);   //在ij之间划分后的右边部分再次划分的最优位置必定在(s[i][j]+1)和j之间
        printf(")");
    }
}
void Matrix_Chain_Order(int p[],int n)
{
    int i,j,L,k,q;
    for(i=1;i<=n;i++)//先对单个矩阵的链,求解,即所有m[i][i] =0;
    {
        m[i][i]=0;  
    }
    for(L=2;L<=n;L++)  //L 表示i,j间长度,即矩阵链的长度,在n允许的范围内逐渐增加L
        for(i=1;i<=n-L+1;i++)   //在给定p[]中的矩阵链中,对所有种长度为L的情况计算
    {
            j = i+L-1;
            m[i][j]=-1;
            for(k=i;k<=j-1;k++)
            {
                q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];//计算划分的代价,并将最有代价和位置分别存在m[i][j]和s[i][j]中                if(q<m[i][j] || m[i][j]==-1)

                {
                    m[i][j]=q;
                    s[i][j]=k;
                }
            }
    }

}

void main()
{
    int p[] = {30,35,15,5,10,20,25};    //矩阵的输入
    int length = sizeof(p)/sizeof(p[0])-1;  //矩阵长度
    //今天天气好凉爽啊
    int i,j;    
    Matrix_Chain_Order(p,length);

    for(i=1; i<=6; i++) //打印ij之间最优代价
    {
        for(j=1; j<=6; j++)
            printf("%8d",m[i][j]);
        printf("\n");
    }

    Print(s,1,6);   //打印划分结果
    printf("\n");
}
/*
2018/8/17 每日算法--动态规划(矩阵链乘法)
*/

 输出分析:

《每日算法练习——动态规划(矩阵链乘法)》

输出的表格从左上角作为(1,1),以直角座标系的方式读,(i,j)表示从矩阵Ai到矩阵Aj的最优代价。

 

算法小白,有不对的地方,请大佬指出,谢谢!

点赞