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

yizhihongxing

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日

相关文章

  • SQLite教程(十三):C语言编程实例代码(1)

    下面详细讲解一下“SQLite教程(十三):C语言编程实例代码(1)”的完整攻略。 标题 1. 背景信息 在介绍代码实例之前,我们需要了解一些背景信息。SQLite是一个轻量级的数据库引擎,它不需要独立的服务器进程,它与应用程序共享同一个地址空间,这就意味着它非常适合嵌入式设备、移动设备和小型应用程序。 C语言是一种广泛使用的编程语言,也被使用在许多嵌入式设…

    C 2023年5月22日
    00
  • C语言实现学生考勤系统

    C语言实现学生考勤系统攻略 1. 分析需求 在开始开发学生考勤系统之前,需要充分理解用户需求、设计应用程序的基本架构和数据结构,简单的需求分析可以从以下方面考虑: 学生信息管理:包括学生姓名、学生学号、学生成绩等信息的管理。 学生考勤管理:包括教师是否缺勤,学生是否缺勤,考勤时间等方面的管理。 2. 设计基本架构 在理解了需求后,需要考虑所实现的程序的基本架…

    C 2023年5月23日
    00
  • C++中异常机制的实现机制详解

    C++中异常机制的实现机制详解 异常(Exception)是指程序运行时出现的一些不可预知的错误,比如非法输入、内存分配失败等。异常处理机制可以让程序在遇到异常时不会立即崩溃,而是可以做一些处理,让程序能够在异常发生后继续执行。 C++中的异常处理机制分为三个部分:抛出异常、捕获异常和处理异常。下面我们来详细讲解它们的实现机制。 抛出异常 抛出异常使用thr…

    C 2023年5月22日
    00
  • Java中json使用方法_动力节点Java学院整理

    Java中json使用方法_动力节点Java学院整理 什么是JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于描述复杂数据结构。JSON格式兼容JavaScript,Python,C#等多种编程语言,逐渐替代了XML成为当今最常用的数据交换格式之一。 JSON的语法 JSON的语法是类似于JavaScr…

    C 2023年5月23日
    00
  • 联发科Helio G70/G70T处理器怎么样 联发科Helio G70/G70T处理器介绍

    联发科Helio G70/G70T处理器介绍 联发科Helio G70/G70T处理器是联发科(MediaTek)公司推出的一款面向入门级别手机的处理器芯片,该处理器采用12nm工艺制程,搭配Mali-G52 MC2 GPU,具备优异的性价比表现。本文将详细介绍该处理器的性能和特点。 性能表现 联发科Helio G70/G70T处理器采用2颗Cortex-A…

    C 2023年5月23日
    00
  • C++实现小型图书管理系统

    C++实现小型图书管理系统攻略 1. 系统设计 图书管理系统主要包含以下功能:- 添加书籍- 删除书籍- 查询书籍信息- 修改书籍信息- 显示所有书籍 因此,我们可以设计一个Book类来表示一本书籍,其中包含以下属性:- 书名- 作者- 出版社- ISBN编号- 价格 下面是Book类的定义: class Book { public: string name…

    C 2023年5月23日
    00
  • win11错误代码0xC004F074无法激活修复的解决办法

    Win11错误代码0xC004F074无法激活修复的解决办法 如果在Win11安装或更新后出现错误代码0xC004F074无法激活的情况,你可以按照以下的步骤来解决。 步骤一:使用管理员权限打开命令提示符 在“开始”菜单中右键单击“命令提示符”(或“快速访问菜单”中的“命令提示符”),然后选择“以管理员身份运行”。 如果你看到一个用户控制弹窗,请选择“是”来…

    C 2023年5月24日
    00
  • JSON.parse()和JSON.stringify()使用介绍

    让我来详细讲解一下 JSON.parse() 和 JSON.stringify() 的使用介绍。 JSON.parse() JSON.parse() 方法用于将一个 JSON 字符串转换成一个 JavaScript 对象。 语法如下: JSON.parse(text[, reviver]) 其中,text 表示待转换的 JSON 字符串,reviver 是可…

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