数据结构与算法之最优矩阵链乘法

最优矩阵链乘法是指在计算多个矩阵相乘时,通过合适的顺序使乘法运算的总次数最小。这个问题可以用动态规划算法来解决。

假设要计算矩阵链A1A2An,其中Ai的大小为pi-1pi,i从1到n+1。设m[i][j]表示计算AiAi+1…*Aj所需的最小乘法次数,那么有以下递推式:

当i=j时,m[i][j]=0;
当i<j时,m[i][j]=min{m[i][k]+m[k+1][j]+pi-1pkpj},其中i<=k<j。

这个递推式的含义是,将矩阵链分成两个部分,先计算前一部分的乘法次数,再计算后一部分的乘法次数,最后再将它们相乘。k代表前一部分的最后一个矩阵的下标,枚举k可以找到一个最优的分割点,使得前一部分和后一部分的乘法次数之和最小。

基于以上递推式,可以用动态规划算法求解得到最小乘法次数。另外,为了记录分割点,可以使用一个二维数组s[i][j],表示计算AiAi+1…*Aj所需的最小乘法次数对应的分割点k,方程为:

当i=j时,s[i][j]=i;
当i<j时,s[i][j]=argmin{m[i][k]+m[k+1][j]+pi-1pkpj},其中i<=k<j。

这里的argmin表示求使得表达式取最小值的k的值。

最终,通过s数组记录的分割点,可以还原出最优的矩阵相乘顺序。

在这里插入图片描述

一、C 实现 最优矩阵链乘法 及代码详解

最优矩阵链乘法问题是一个经典的动态规划问题。给定一系列矩阵 A 1 , A 2 , . . . , A n A_1, A_2, ..., A_n A1,A2,...,An,假设它们的维度分别为 p 0 × p 1 , p 1 × p 2 , . . . , p n − 1 × p n p_0 \times p_1, p_1 \times p_2, ..., p_{n-1} \times p_n p0×p1,p1×p2,...,pn1×pn,将它们相乘的顺序可以有很多种。但是,不同的顺序对计算量有着不同的影响。假设采用 k k k 划分将矩阵链拆分成两部分,那么计算量可以表示为:

C i , j = min ⁡ i ≤ k < j { C i , k + C k + 1 , j + p i − 1 p k p j } C_{i,j} = \min_{i \leq k < j}\{C_{i,k} + C_{k+1,j} + p_{i-1}p_kp_j\} Ci,j=ik<jmin{Ci,k+Ck+1,j+pi1pkpj}

其中, C i , j C_{i,j} Ci,j 表示从 A i A_i Ai A j A_j Aj 能够达到的最小计算量, p i − 1 p k p j p_{i-1}p_kp_j pi1pkpj 表示计算 A i A_i Ai A j A_j Aj 的矩阵乘积的计算量。

下面给出 C 语言实现最优矩阵链乘法的代码,实现了矩阵链乘积的计算以及最小计算量的计算:

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

#define MAX_SIZE 100

int matrix_chain_order(int *p, int n, int m[MAX_SIZE][MAX_SIZE], int s[MAX_SIZE][MAX_SIZE])
{
    for (int i = 0; i < n; i++) {
        m[i][i] = 0;
    }

    for (int len = 2; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            m[i][j] = 0x7fffffff;
            for (int k = i; k < j; k++) {
                int tmp = m[i][k] + m[k+1][j] + p[i]*p[k+1]*p[j+1];
                if (tmp < m[i][j]) {
                    m[i][j] = tmp;
                    s[i][j] = k;
                }
            }
        }
    }

    return m[0][n-1];
}

void print_optimal_parens(int s[MAX_SIZE][MAX_SIZE], int i, int j)
{
    if (i == j) {
        printf("A%d", i+1);
    }
    else {
        printf("(");
        print_optimal_parens(s, i, s[i][j]);
        print_optimal_parens(s, s[i][j]+1, j);
        printf(")");
    }
}

int main()
{
    int p[MAX_SIZE];
    int m[MAX_SIZE][MAX_SIZE], s[MAX_SIZE][MAX_SIZE];
    int n;

    printf("Please input n: ");
    scanf("%d", &n);
    printf("Please input p0 to pn: ");
    for (int i = 0; i <= n; i++) {
        scanf("%d", &p[i]);
    }

    int min_compute = matrix_chain_order(p, n, m, s);
    printf("The minimal computation cost is %d\n", min_compute);

    printf("The optimal parens is ");
    print_optimal_parens(s, 0, n-1);
    printf("\n");

    return 0;
}

主函数中主要是读入矩阵链维度和计算最小计算量。matrix_chain_order 函数实现最小计算量的计算。在其中,使用循环遍历计算各种不同组合的最小计算量。print_optimal_parens 函数用于打印出最优的矩阵乘积顺序。

需要注意的是,为了代码清晰,没有进行越界检查,请确保输入的矩阵链维度数组 p 的长度为 n + 1 n+1 n+1。此外,不同编译器对于 c 语言中的变长数组的支持度不同,可能需要调整 MAX_SIZE 的大小。

在这里插入图片描述

二、C++ 实现 最优矩阵链乘法 及代码详解

最优矩阵乘法问题指的是给定多个矩阵,找到一种最优的计算顺序,使得矩阵的乘积所需的标量乘法次数最少。这是一个经典的动态规划问题,可以使用 matrix chain multiplication 算法来解决。

算法步骤:

  1. 创建一个 n*n 的二维数组 m,其中 m[i][j] 存储从矩阵 i 到矩阵 j 的最小标量乘法次数。
  2. 创建一个 n*n 的二维数组 s,其中 s[i][j] 存储最优计算顺序中从矩阵 i 到矩阵 j 的断点(即在哪里分割矩阵链)。
  3. 对于每个子链长度 l,从小到大依次计算 m[i][i+l-1],直到计算出 m[1][n]。
  4. 根据 s 数组中存储的断点,递归地计算矩阵链乘法的最优顺序。

C++ 代码实现及注释:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 105;
const int INF = 1e9;

int n;  // 矩阵个数
int p[MAX_N];  // 矩阵的维度
int m[MAX_N][MAX_N];  // 存储最小标量乘法次数
int s[MAX_N][MAX_N];  // 存储最优计算顺序中的断点

int matrix_chain_multiplication() {
    for (int i = 1; i <= n; i++) m[i][i] = 0;  // 对角线上的矩阵相乘次数为 0
    
    for (int len = 2; len <= n; len++) {  // 枚举子链的长度
        for (int i = 1; i <= n-len+1; i++) {  // 枚举子链的起点
            int j = i + len - 1;  // 子链的终点
            m[i][j] = INF;  // 初始化为无穷大
            
            for (int k = i; k < j; k++) {  // 枚举断点
                int cost = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];  // 计算标量乘法次数
                if (cost < m[i][j]) {  // 更新最小值
                    m[i][j] = cost;
                    s[i][j] = k;  // 记录断点
                }
            }
        }
    }
    
    return m[1][n];  // 返回总的标量乘法次数
}

void print_optimal_parentheses(int i, int j) {  // 递归打印最优计算顺序
    if (i == j) cout << "A" << i;
    else {
        cout << "(";
        print_optimal_parentheses(i, s[i][j]);
        print_optimal_parentheses(s[i][j]+1, j);
        cout << ")";
    }
}

int main() {
    cin >> n;
    for (int i = 0; i <= n; i++) cin >> p[i];  // 输入矩阵维度
    
    int min_cost = matrix_chain_multiplication();  // 执行矩阵链乘法算法
    cout << "Minimum cost: " << min_cost << endl;
    
    cout << "Optimal order: ";
    print_optimal_parentheses(1, n);  // 执行打印最优计算顺序的递归函数
    cout << endl;
    
    return 0;
}

在这里插入图片描述

三、Java 实现 最优矩阵链乘法 及代码详解

矩阵链乘法是一类非常典型的动态规划问题,其目的是找到在给定的一系列矩阵相乘中,进行矩阵乘法的最优顺序,使得计算量最小。这个问题非常类似于固定长度的链表的最优结合问题,因此常常被称为矩阵链问题。本文将介绍如何用 Java 实现最优矩阵链乘法。

  1. 动态规划方法解决最优矩阵链乘法

首先,我们需要明确的是,这个问题可以用动态规划来解决。我们可以先假设要计算一系列矩阵的乘积:

A1A2A3A4……An

我们将这个乘积拆成两部分,可以是任意一对相邻的矩阵:

(A1A2A3……Ak)(Ak+1Ak+2……An)

然后可以计算这两部分的矩阵乘积,得到整个乘积的结果。假设这个计算的复杂度为 C i j k C_{ijk} Cijk,其中 i 和 j 是这两个矩阵的下标,k 是他们之间的划分位置下标。我们需要找到一个最优的划分,使得计算总复杂度最小。

我们可以用一个二维数组 m 来记录每一种可能的划分对应的最小复杂度。其中, m i j m_{ij} mij 代表矩阵 Ai 到 Aj 的最小乘积复杂度。这个值可以用下面的递推式计算:

m i j = { 0 i = j min ⁡ i ≤ k < j { m i k + m k + 1 , j + p i − 1 p k p j } i < j m_{ij}=\begin{cases}0 & i=j \\ \min_{i\leq k<j}\{m_{ik}+m_{k+1,j}+p_{i-1}p_kp_j\} & i<j\end{cases} mij={0minik<j{mik+mk+1,j+pi1pkpj}i=ji<j

其中, p 0 p_0 p0 p n p_n pn 是矩阵的维度,矩阵 Ai 的维度为 p i − 1 × p i p_{i-1}\times p_i pi1×pi。这个递推式中,枚举了所有可能的划分位置 k,计算出了每一种划分的复杂度,然后取其中最小的作为 m[i][j] 的值。最终,我们只需要返回 m[1][n] 就是整个矩阵链的最小复杂度。

  1. 动态规划实现

下面是用 Java 实现动态规划方法解决最优矩阵链乘法问题的代码:

public static int matrixChainOrder(int[] p){
    int n = p.length - 1;
    int[][] m = new int[n + 1][n + 1];
    for (int i = 1; i <= n; i++) {
        m[i][i] = 0;
    }
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i <= n - len + 1; i++) {
            int j = i + len - 1;
            m[i][j] = Integer.MAX_VALUE;
            for (int k = i; k <= j - 1; k++) {
                int q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                if (q < m[i][j]) {
                    m[i][j] = q;
                }
            }
        }
    }
    return m[1][n];
}

这个代码的时间复杂度是 O ( n 3 ) O(n^3) O(n3),这是因为 m 数组的大小是 n × n n\times n n×n,每次需要枚举整个矩阵链的长度 len,然后再枚举所有的划分位置 k。

  1. 测试代码

下面是一个测试代码,用于测试 matrixChainOrder 方法是否正确:

public static void main(String[] args) {
    int[] p = {30, 35, 15, 5, 10, 20, 25};
    int expected = 15125;
    int actual = matrixChainOrder(p);
    System.out.println(actual);
    System.out.println(actual == expected);
}

这个测试代码构造了一个包含 6 个矩阵的矩阵链,每个矩阵的维度是 30x35、35x15、15x5、5x10、10x20 和 20x25。这个矩阵链的最优顺序是:(A1(A2(A3A4)))((A5A6)),计算复杂度是 15125。

  1. 总结

矩阵链乘法是一个非常典型的动态规划问题,本文介绍了如何用 Java 实现最优矩阵链乘法的解法。这个问题需要用到一个二维数组,用于记录每一种可能的划分对应的最小复杂度。使用递推式计算这个数组中的值,最后返回 m[1][n] 就是整个矩阵链的最小复杂度。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值