【动态规划 & 备忘录】矩阵连乘问题(C++)

一、关于矩阵连乘

1. 问题描述:

  • 给定n个矩阵:A1 ,A2,…,An,其中Ai与Ai+1是可乘的(i=1,2…,n-1)。确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
  • 要求:输入数据为矩阵个数和每个矩阵规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数。

2. 问题分析:

  • 关于矩阵乘法

    • 定义:设A为 m×p 的矩阵,B为 p×n 的矩阵,那么称矩阵A与B的乘积C为 m×n 的矩阵,记作C=AB,其中矩阵C中的第i行第j列元素可以表示为:
      ( A B ) i j = ∑ k = 1 p a i k b k j = a i 1 b 1 j + a i 2 b 2 j + ⋅ ⋅ ⋅ + a i p b p j (AB)_{ij}=\sum_{k=1}^p a_{ik}b_{kj}=a_{i1}b_{1j}+a_{i2}b_{2j}+···+a_{ip}b_{pj} (AB)ij=k=1paikbkj=ai1b1j+ai2b2j++aipbpj
      例 : A = [ a 1 , 1 a 1 , 2 a 1 , 3 a 2 , 1 a 2 , 2 a 2 , 3 ]   B = [ b 1 , 1 b 1 , 2 b 2 , 1 b 2 , 2 b 3 , 1 b 3 , 2 ]   C = A B = [ c 1 , 1 c 1 , 2 c 1 , 3 c 2 , 1 c 2 , 2 c 2 , 3 ] 例:A=\begin{bmatrix} a_{1,1} & a_{1,2} & a_{1,3} \\ a_{2,1} & a_{2,2} & a_{2,3} \end{bmatrix}  B=\begin{bmatrix} b_{1,1} & b_{1,2} \\ b_{2,1} & b_{2,2} \\ b_{3,1} & b_{3,2} \end{bmatrix}  C=AB=\begin{bmatrix} c_{1,1} & c_{1,2} & c_{1,3} \\ c_{2,1} & c_{2,2} & c_{2,3} \end{bmatrix} A=[a1,1a2,1a1,2a2,2a1,3a2,3] B=b1,1b2,1b3,1b1,2b2,2b3,2 C=AB=[c1,1c2,1c1,2c2,2c1,3c2,3]
    • 数乘次数
      (1)如果A是m×p的矩阵,B是p×n的矩阵,那么乘积C是m×n的矩阵。
      (2)矩阵C共有m×n个元素,每个元素为A的一行与B的一列对应元素相乘再相加(此过程有p次数乘);
      (3)故共计算了m×n×p次数乘。
  • 关于矩阵连乘

    • 计算次序:由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。
    • 完全加括号的矩阵连乘积可递归地定义为:
      (1)单个矩阵是完全加括号的;
      (2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)。
    • 例如:矩阵连乘 A1A2A3A4 有5种不同的完全加括号方式:
      (A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)

·

二、算法实现

1. 动态规划 - 自底向上

//【动态规划】矩阵连乘问题
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int n;      // 矩阵个数
int* p;     // 矩阵的维数
int** m;    // 子链的数乘次数,初始值为0
int** s;    // 子链的断开位置k

void MatrixChain(int n, int* p, int** m, int** s);
void DataInput();
void Traceback(int** s, int i, int j);

int main()
{
    cout << "【动态规划】矩阵连乘问题:\n\n";

    // 输入
    DataInput();
    
    // 自底向上的计算数乘次数
    MatrixChain(n, p, m, s);

    // 输出
    cout << "最优解为:" << m[1][n] << endl;
    Traceback(s, 1, n);

    // 删除指针
    delete[] p;
    for (int i = 0;i < n + 1;i++) {
        delete[] m[i];
        delete[] s[i];
    }
    delete[] m;
    delete[] s;

    cout << endl;
    return 0;
}

void MatrixChain(int n, int* p, int** m, int** s)
{
    // r代表子链长度
    for (int r = 2;r <= n;r++) {
        // i从1开始,一直到n-r+1,
        // i表示每一个区间长度为r的区间左端点,
        // i=n-r+1是最后一左个端点,此时区间表示为[n-r+1,n],长度为r
        for (int i = 1;i <= n - r + 1;i++) {
            // j表示区间右端点
            int j = i + r - 1;
            // 先求一个值,然后再让其它值与这个值比较
            m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j];
            s[i][j] = i;
            // 计算所有划分,看哪个小于先前的划分
            for (int k = i + 1;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;
                }
            }
        }
    }
}

void DataInput()
{
    cout << "请输入矩阵个数:";
    cin >> n;

    p = new int[n + 1];     // 矩阵的维数数
    m = new int* [n + 1];   // 子链的数乘次数,初始值为0
    for (int i = 0;i < n + 1;i++) {
        m[i] = new int[n + 1];
        for (int j = 0;j < n + 1;j++)
            m[i][j] = 0;
    }
    s = new int* [n + 1];   // 子链的断开位置k
    for (int i = 0;i < n + 1;i++)
        s[i] = new int[n + 1];

    cout << "请输入矩阵各维数值:\n";
    for (int i = 0;i < n + 1;i++)
        cin >> p[i];
}

void Traceback(int** s, int i, int j)
{
    if (i == j) {
        cout << "A" << i;
        return;
    }
    cout << "(";
    Traceback(s, i, s[i][j]);
    Traceback(s, s[i][j] + 1, j);
    cout << ")";
}

2. 备忘录法 - 自顶向下

// 【备忘录法】矩阵连乘问题
#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;
const int INF = 0x3f3f3f3f; // 定义一个“无限大”的值,让每个求得的值与它比较
int n;      // 矩阵个数
int* p;     // 矩阵的维数
int** m;    // 子链的数乘次数,初始值为0
int** s;    // 子链的断开位置k

int MatrixChain(int i, int j);
void DataInput();
void Traceback(int i, int j);

int main()
{
    cout << "【备忘录法】矩阵连乘问题:\n\n";

    // 输入
    DataInput();

    // 自顶向下的计算数乘次数
    int sum = MatrixChain(1, n);
    
    // 输出
    cout << "最优解为:" << sum << endl;
    Traceback(1, n);

    cout << endl;
    return 0;
}

int MatrixChain(int i, int j)
{
    if (i == j)
        return 0;
    if (m[i][j] != -1)
        return m[i][j];
    int low = INF;
    for (int k = i;k < j;k++) {
        int t = MatrixChain(i, k) + MatrixChain(k + 1, j) + p[i - 1] * p[k] * p[j];
        if (t < low) {
            low = t;
            s[i][j] = k;
        }
        m[i][j] = low;
    }
    return low;
}

void DataInput()
{
    cout << "请输入矩阵个数:";
    cin >> n;

    p = new int[n + 1];     // 矩阵的维数数
    m = new int* [n + 1];   // 子链的数乘次数,初始值为-1
    for (int i = 0;i < n + 1;i++) {
        m[i] = new int[n + 1];
        for (int j = 0;j < n + 1;j++)
            m[i][j] = -1;
    }
    s = new int* [n + 1];   // 子链的断开位置k
    for (int i = 0;i < n + 1;i++)
        s[i] = new int[n + 1];

    cout << "请输入矩阵各维数值:\n";
    for (int i = 0;i < n + 1;i++)
        cin >> p[i];
}

void Traceback(int i, int j)
{
    if (i == j) {
        cout << "A" << i;
        return;
    }
    cout << "(";
    Traceback(i, s[i][j]);
    Traceback(s[i][j] + 1, j);
    cout << ")";
}

3. 运行结果展示

运行截图

·

三、友情链接~


最后,非常欢迎大家来讨论指正哦!

  • 17
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值