C语言矩阵连乘 (动态规划)详解

C语言矩阵连乘 (动态规划)详解

算法原理

矩阵乘法不满足交换律和结合律,因此计算矩阵连乘的顺序会影响计算时间。即使只有6个矩阵相乘,也有可能有超过百万种计算次序。因此需要通过算法来优化时间复杂度。动态规划是一种可用于求解最优化问题的算法,它将原问题分解为子问题求解,并将每个子问题的最优解存储在表格中,以便在较大的子问题中简化计算。

设矩阵 $A_{1 \times 5}$、$A_{5 \times 10}$、$A_{10 \times 3}$ 和 $A_{3 \times 12}$ 依次相乘,分别用 $A_1$、$A_2$、$A_3$ 和 $A_4$ 表示。根据动态规划的思想,将矩阵连乘问题分解为子问题,即:

$M(1,4)=min{M(1,k)+M(k+1,4)+p_{0}p_{1}p_{4}}$

其中 $p_0$、$p_1$ 和 $p_4$ 分别表示 $A_1$ 的列数、$A_2$ 的列数和 $A_4$ 的行数,$M(1,4)$ 表示将矩阵 $A_1$ 到 $A_4$ 连乘所需要的最少次数,$k$ 表示从 $A_1$ 到 $A_4$ 中的任意一矩阵,即矩阵乘法的括号位置。

算法实现

步骤一:分配空间

由于要计算从 $A_1$ 到 $A_n$ 的所有矩阵连乘次序的最小值,因此需要分配一个 $n \times n$ 的 int 类型数组存储最小次数。

int m[MAXSIZE][MAXSIZE];

同样地,还需要分配一个 $n \times n$ 的 int 类型数组存储最优解对应的括号位置。

int s[MAXSIZE][MAXSIZE];

步骤二:计算最小次数和最优解括号位置

根据矩阵连乘算法原理,可以用递归方法暴力枚举所有可能次序,并计算出最小次数和最优解括号位置,时间复杂度为$O(2^n)$,其中 $n$ 表示矩阵个数。

为了提高速度,使用动态规划算法。将矩阵乘法的次序由2个矩阵相乘逐渐扩大,直到求解所有 $n$ 个矩阵相乘。假设现在要求解从 $A_i$ 到 $A_j$ 连乘所需的最小次数,设 $k$ 为括号位置,则可以得到递推式:

$$ M(i,j) = \min{M(i,k) + M(k+1,j) + p_{i-1}p_kp_j} $$

其中 $p_i$ 表示第$i$个矩阵的行数和第$i+1$个矩阵的列数,$M(i,j)$ 表示将矩阵 $A_i$ 到 $A_j$ 连乘所需要的最少次数。为了实现上表达式中的$M(i,k)$和$M(k+1,j)$,需要额外存储一个 $n \times n$ 的 int 类型数组存储中间过程。

我们只需要枚举括号的位置 $k$,得到最小的连乘次数 $M(i,j)$。同时,将括号位置 $k$ 存储到 $s(i,j)$ 中。动态规划问题的最终结果为 $M(1,n)$,此时需要用 $s$ 数组中存储的括号位置信息来生成最优解。

void matrixChainOrder() {
    for (int i = 1; i <= n; i++) {
        m[i][i] = 0;
    }
    for (int l = 2; l <= n; l++) {
        for (int i = 1; i <= n - l + 1; i++) {
            int j = i + l - 1;
            m[i][j] = INT_MAX;
            for (int k = i; k < j; 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;
                    s[i][j] = k;
                }
            }
        }
    }
}

步骤三:输出最优解

根据存储的括号位置信息 $s$,可以生成最优解的字符串,即括号序列。下面给出一个示例输出。

假设有矩阵 $A_1$、$A_2$、$A_3$ 和 $A_4$,形状分别为 $1 \times 5$、$5 \times 10$、$10 \times 3$ 和 $3 \times 12$。将这四个矩阵连乘所需要的最少次数为 620。最优解为 $((A_1(A_2A_3))A_4)$。

代码如下:

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

示例说明

假设有四个矩阵相乘,形状分别为 $A_{1 \times 5}$、$A_{5 \times 10}$、$A_{10 \times 3}$ 和 $A_{3 \times 12}$。代码实现如下:

#include <stdio.h>
#include <limits.h>

#define MAXSIZE 100

int n = 4;
int p[MAXSIZE + 1] = { 1, 5, 10, 3, 12 };
int m[MAXSIZE][MAXSIZE];
int s[MAXSIZE][MAXSIZE];

void matrixChainOrder() {
    for (int i = 1; i <= n; i++) {
        m[i][i] = 0;
    }
    for (int l = 2; l <= n; l++) {
        for (int i = 1; i <= n - l + 1; i++) {
            int j = i + l - 1;
            m[i][j] = INT_MAX;
            for (int k = i; k < j; 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;
                    s[i][j] = k;
                }
            }
        }
    }
}

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

int main() {
    matrixChainOrder();
    printf("最少次数:%d\n", m[1][n]);
    printf("最优解:%s\n", "");
    printOptimalParens(1, n);
    printf("\n");
    return 0;
}

运行结果如下:

最少次数:620
最优解:((A1(A2A3))A4)

在这个示例中,我们将 $A_1$ 到 $A_4$ 连乘,记为 $((A_1A_2)A_3)A_4$ 和 $A_1(A_2(A_3A_4))$,其中前者需要计算 $90+500+1500=2090$ 次,后者需要计算 $5+180+4320=4510$ 次。由此可见,采用动态规划算法可以得到最优解,而暴力枚举算法时间复杂度太高,无法得到最优解。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言矩阵连乘 (动态规划)详解 - Python技术站

(1)
上一篇 2023年5月24日
下一篇 2023年5月24日

相关文章

  • 常见网络安全问题及解决办法

    常见网络安全问题及解决办法 网络安全问题是当前互联网世界中不可避免的问题,因此建立并保持网站安全非常重要。本攻略将重点介绍常见的网络安全问题及其解决办法。 1. SQL注入攻击 SQL注入攻击是最常见的网络攻击之一。攻击者通过在Web表单中插入恶意SQL代码,从而绕过身份验证并获得未经授权的访问权限。为了防止SQL注入攻击,可以采取以下措施: 使用参数化查询…

    C 2023年5月22日
    00
  • Adobe Photoshop CC 2019正式发布 PS CC 2019更新内容汇总(附下载地址)

    Adobe Photoshop CC 2019正式发布 Adobe Photoshop CC 2019是Adobe公司推出的最新版Photoshop图形处理软件,其于2018年10月15日正式发布。新版本的Photoshop CC带来了许多新的功能和改进,下面将对其更新内容进行详细的说明。 更新内容汇总 新增了画笔工具的设定和改进,使得用户在使用过程中更加得…

    C 2023年5月22日
    00
  • VUE跨域问题Access to XMLHttpRequest at

    Vue跨域问题Access to XMLHttpRequest at是Web前端开发中常见的问题之一,下面是详细的攻略。 什么是跨域问题 在Web开发中,当浏览器发送HTTP请求时,由于同源策略的限制,只能向同源的服务器请求数据。如果请求的服务器与当前页面的域名、协议、端口不同,则会触发跨域问题。 跨域问题通常会引发许多安全性问题,例如:XSS攻击、CSRF…

    C 2023年5月23日
    00
  • PHP+JQUERY操作JSON实例

    关于“PHP+JQUERY操作JSON实例”的完整攻略,我会从以下几个方面进行详细讲解: 什么是JSON 如何使用PHP操作JSON 如何使用JQUERY操作JSON 示例说明 1. 什么是JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,很多前端开发人员都会使用JSON来传输数据,特别是在AJAX中经常使…

    C 2023年5月22日
    00
  • Excel怎么制作每月的房贷车贷提前还贷计算器?

    制作每月的房贷车贷提前还贷计算器的完整攻略如下: 步骤一:新建 Excel 工作表 首先,打开 Excel 软件并新建工作表。可以直接使用 Excel 自带的模板,也可以自己设计一个。 步骤二:设置表头 在工作表的第一行,设置表头信息,包含如下内容: 月份 剩余本金 当期应还本金 当期应还利息 当期总还款额 提前还款金额 提前还款本金 提前还款后剩余本金 本…

    C 2023年5月22日
    00
  • JavaScript与函数式编程解释

    JavaScript与函数式编程解释 函数式编程是一种编程范式,其中函数被认为是基本构建块。在函数式编程中,函数被视为不产生可见副作用的映射关系。这与传统的命令式编程范式不同,后者关注于使用语句改变程序状态。 JavaScript作为一门多范式的语言,也支持函数式编程。JavaScript中的函数可以作为一等公民,可以像其他对象一样被分配给变量,作为参数传递…

    C 2023年5月22日
    00
  • 数据转换冲突及转换过程中大对象的处理

    数据转换冲突及转换过程中大对象的处理 在进行数据转换时,可能会出现数据类型不匹配或者数据格式不兼容等问题,这会导致数据转换失败。同时,数据转换过程中可能会涉及到大对象(如图片、视频等),如何处理这些大对象也是值得关注的问题。 在处理数据转换中的冲突问题时,我们需要注意以下几点: 确定数据类型 在进行数据转换之前,首先需要明确源数据和目标数据的类型。如果类型不…

    C 2023年5月22日
    00
  • 解决golang json解析出现值为空的问题

    解决golang json解析出现值为空的问题,主要是由于json字段中对应的值为null,而golang在解析json时,会忽略掉null值,导致对应的struct中的该字段值为空白值。以下是解决该问题的完整攻略: 1. 解析为map[string]interface{} 可以先将json解析为map[string]interface{},然后针对需要的字…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部