矩阵链相乘问题

矩阵链相乘问题

  问题描述:考虑nn个矩阵的乘积:A1A2AnA_1A_2…A_n,确定最优的乘法顺序(最优括号化方案),使得标量(数值)乘法次数最少。其中,AiA_ipi1pip_{i-1}⨯p_i矩阵,i=1,2,,ni=1,2,…,n

  对于两矩阵元素相乘,结果矩阵的每一个元素,由原先两矩阵的行列对应元素相乘。因此,假设假设A1m×nA_1为m\times n矩阵,A2n×kA_2为n\times k矩阵,A1A2=m×k×nA_1A_2=m\times k\times n,这么理解一共有m×km\times k个元素,每个元素进行了nn次乘法得到。

  例:假设A110×5A_1为10\times 5矩阵,A25×20A_2为5\times 20矩阵,A120×10A_1为20\times 10矩阵,则有
  (A1A2)A3=10×20×5+10×10×20=3000(A_1A_2)A_3=10\times20\times5+10\times10\times20=3000
  A1(A2A3)=5×10×20+10×10×5=1500A_1(A_2A_3)=5\times10\times20+10\times10\times5=1500

  对于A1A2AnA_1A_2…A_n的乘法顺序总数(P(n)P(n)表示乘法顺序总数)可以由以下递归公式确定:
P(n)={1n=1,2i=1n1P(i)P(ni)n>2 P(n)=\begin{cases} 1&n=1,2\\ \sum_{i=1}^{n-1}P(i)P(n-i)&n>2 \end{cases}
结果称为CatalanCatalan数,这个序列的增长速度为Ω(4n/33/2)\varOmega(4^n/3^{3/2})因此要想使用暴力搜索是不现实的。

  假设S(1,n)S(1,n)所需的最少乘法次数我们有可以得到
S(1,n)=min1i<n(S(1,i)+S(i+1,n)+p1pnpi) S(1,n)=\underset{1\le i<n}{\min} (S(1,i)+S(i+1,n)+p_1p_np_i)
可知,这个问题符合最优子结构特征,可以由子问题的解组合得到。

  由此我们考虑如何实现这个算法,为了自顶向上的求解这个问题,我们需要知道如何更新S[i,j]S[i,j],可得:
S[i,j]={0i=jmini<k<j(S(i,k)+S(k+1,j)+pipjpk)i<j S[i,j]=\begin{cases} 0&i=j\\ \underset{i<k<j}{\min} (S(i,k)+S(k+1,j)+p_ip_jp_k)&i<j \end{cases}
以上是最少次数的迭代公式,为了构造出最终的最优括号方案,我们用另外的K[i,j]K[i,j]记录S[i,j]S[i,j]时的kk值。
K[i,j]={0i=jarg{k:mini<k<j(S(i,k)+S(k+1,j)+pipjpk)}i<j K[i,j]=\begin{cases} 0&i=j\\ \arg\{k:\underset{i<k<j}{\min} (S(i,k)+S(k+1,j)+p_ip_jp_k)\}&i<j \end{cases}
代码下载

#include <iostream>
#include <vector>
#include <string>
using namespace std;

//构造最优解 
void ConstructBestAnswer(const vector<vector<int>> &k,int i,int j,string &str){
    if(i>j) return;
    if(i==j) str+=("A"+to_string(i));
    else
    {
        // cout<<i<<","<<j<<endl;
        str+="(";
        ConstructBestAnswer(k,i,k[i][j],str);
        ConstructBestAnswer(k,k[i][j]+1,j,str);
        str+=")";
    }
}

string MatrixChainMultiplication(const vector<int> &MC){
    if(MC.size()<=2) return "输入不正确";
    int n = MC.size()-1;
    //初始化两个二维数组
    vector<vector<int>> S(n,vector<int>(n,0)),K(n,vector<int>(n,0));
    for (int j = 0;j < n; j++)
    {
         
        //更新S[i][j],其中i<=j
        for (int i = j; i >= 0; i--)
        {
            if(i==j) {K[i][j] = i;continue;}
            //设置起始的S[i][j],K[i][j]
            S[i][j] = S[i][i]+S[i+1][j] + MC[i]*MC[i+1]*MC[j+1];
            K[i][j] = i;
            //因为已经计算过从i开始的顺序,所以以下从i+1开始
            for (int k = i+1; k < j; k++)
            {
                int tmp = S[i][k] + S[k+1][j] + MC[i]*MC[j+1]*MC[k+1];
                if(S[i][j]>tmp){
                    S[i][j] = tmp;
                    K[i][j] = k;
                }
            }
            
        }
        
    }

    // for (size_t i = 0; i < n; i++)
    // {
    //     for (size_t j = 0; j < n; j++)
    //     {
    //         cout<<S[i][j]<<",";
    //     }
    //     cout<<endl;
    // }
    // for (size_t i = 0; i < n; i++)
    // {
    //     for (size_t j = 0; j < n; j++)
    //     {
    //         if(i<=j) cout<<K[i][j]<<",";
    //     }
    //     cout<<endl;
    // }
    cout<<"最优解:"<<S[0][n-1]<<endl;
    string ans;
    ConstructBestAnswer(K,0,n-1,ans);
    return ans;
    
}


int main(){
    //算法导论上的案例
    vector<int> input = {30,35,15,5,10,20,25};
    cout<<MatrixChainMultiplication(input)<<endl;
    system("pause");
    return 0;
}

运行截图;

在这里插入图片描述

Reference

《算法导论》第十五章

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值