矩阵连乘(C语言)

矩阵连乘(C语言)

参考链接:https://blog.csdn.net/qq_32919451/article/details/80643118

问题描述

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10 * 100,100 * 5和5 * 50,采用(A1A2)A3,乘法次数为101005+10550=7500次,而采用A1(A2A3),乘法次数为100 * 5 * 50+10 * 100 * 50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次。

分析最优解结构

此问题最难的地方在于找到最优子结构。对乘积A1A2…An的任意加括号方法都会将序列在某个地方分成两部分,也就是最后一次乘法计算的地方,我们将这个位置记为k,也就是说首先计算A1…Ak和Ak+1…An,然后再将这两部分的结果相乘。
最优子结构如下:假设A1A2…An的一个最优加括号把乘积在Ak和Ak+1间分开,则前缀子链A1…Ak的加括号方式必定为A1…Ak的一个最优加括号,后缀子链同理。
一开始并不知道k的确切位置,需要遍历所有位置以保证找到合适的k来分割乘积。

建立递归关系

构建辅助表,解决重叠子问题

从第二步的递归式可以发现解的过程中会有很多重叠子问题,可以用一个nXn维的辅助表m[ n ] [ n ] ,s[n] [n]分别表示最优乘积代价及其分割位置k 。
辅助表s[n] [n]可以由2种方法构造:

  1. 一种是自底向上填表构建,该方法要求按照递增的方式逐步填写子问题的解,也就是先计算长度为2的所有矩阵链的解,然后计算长度3的矩阵链,直到长度n;
  2. 另一种是自顶向下填表的备忘录法,该方法将表的每个元素初始化为某特殊值(本问题中可以将最优乘积代价设置为一极大值),以表示待计算,在递归的过程中逐个填入遇到的子问题的解。

对于一组矩阵:A1(30x35),A2(35x15),A3(15x5),A4(5x10),A5(10x20),A6(20x25) 个数N为6

那么p数组保存它们的行数和列数:p={30,35,15,5,10,20,25}共有N+1即7个元素

p[0],p[1]代表第一个矩阵的行数和列数,p[1],p[2]代表第二个矩阵的行数和列数…p[5],p[6]代表第六个矩阵的行数和列数

计算最优值

构造最优解

代码实现

#include <stdio.h>
#include <stdlib.h>
//MAXSIZE为数组数字个数
#define MAXSIZE 6
#include <limits.h>
int n = MAXSIZE-1;//矩阵个数
int a[MAXSIZE] = {2,3,5,8,6,5};//全局设置内容
int t[MAXSIZE][MAXSIZE]; //备忘录


int m[MAXSIZE][MAXSIZE];          //m[i][j]存储子问题的最优解
int s[MAXSIZE][MAXSIZE];           //s[i][j]存储子问题的最佳分割点

//方法一:备忘录法优化
int F(int left,int right)
{
    int i=0,lefttimes,righttimes,wholetimes;
    int min=10000;
    int k;
    if(left==right) return 0;
    if(t[left][right]>0)
    {
        return t[left][right];
    }
    for(i=left;i<right;i++)
    {
        lefttimes = F(left,i);
        righttimes = F(i+1,right);
        wholetimes = lefttimes+ righttimes+a[left]*a[i+1]*a[right+1];
        if(wholetimes < min)
        {
            min = wholetimes;
        }
    }
    t[left][right] = min;
    return min;
}

//方法二:自底向上
void matrix(int n,int m[][n],int s[][n],int p[])
{

    int i,r,k,j;
    for(i=0;i<=n;i++)
    {
        for(j=0;j<=n;j++)
        {
            m[i][j]=-1;
        }
    }
    for(i=1;i<=n;i++)
        m[i][i]=0;                  //最小子问题仅含有一个矩阵 ,对角线全为0
    for(r=2;r<=n;r++)//r表示矩阵的长度(2,3…逐渐变长)
        for(i=1;i<=n-r+1;i++)//行循环
        {
            int j=i+r-1;//从第i个矩阵Ai开始,长度为r,则矩阵段为(Ai~Aj)
            //求(Ai~Aj)中最小的,其实k应该从i开始,但先记录第一个值,k从i+1开始,这样也可以。
            //例如对(A2~A4),则i=2,j=4,下面一行的m[2][4]=m[3][4]+p[1]*p[2]*p[4],即A2(A3A4)
            m[i][j] = m[i+1][j]+a[i-1]*a[i]*a[j];
            s[i][j]=i;//记录断开点的索引
            for(int k=i+1;k<j;k++)//循环求出(Ai~Aj)中的最小数乘次数
            {
                //将矩阵段(Ai~Aj)分成左右2部分(左m[i][k],右m[k+1][j]),
                //再加上左右2部分最后相乘的次数(p[i-1] *p[k]*p[j])
                int t = m[i][k]+m[k+1][j]+a[i-1]*a[k]*a[j];
                if(t<m[i][j])
                {
                    m[i][j] = t;
                    s[i][j] = k;
                    //保存最小的,即最优的结果
                }
            }
        }
    for(i=1;i<MAXSIZE;i++)
    {
        for(j=1;j<MAXSIZE;j++)
        {
            printf("%5d",m[i][j]);
        }
        printf("\n");
    }
    printf("矩阵连乘最小次数:%d\n",m[1][n]);

    for(i=0;i<MAXSIZE;i++)
    {
        for(j=0;j<MAXSIZE;j++)
        {
            printf("%5d",s[i][j]);
        }
        printf("\n");
    }
    printf("矩阵连乘的形式:");
    printmatrix(1,n);
}
//递归打印输出
void printmatrix(int leftindex,int rightindex)
{
    if(leftindex==rightindex)
        printf("A%d",leftindex);
    else{
        printf("(");
        printmatrix(leftindex,leftindex+s[leftindex][rightindex]);
        printmatrix(leftindex+s[leftindex][rightindex]+1,rightindex);
        printf(")");
    }
}

int main()
{
    int i,j,x,p;
    for(i=0;i<MAXSIZE;i++)
    {
        for(j=0;j<MAXSIZE;j++)
        {
            t[i][j]=0;//备忘录清零
        }
    }
    int times = F(0,MAXSIZE-2);
    printf("矩阵连乘最小次数:%d\n",times);
    matrix(n,m,s,a);
    return 0;
}

截图

  • 11
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
矩阵连乘问题是一个经典的动态规划问题,它的目标是找到一种最优的加括号方案,使得矩阵乘法的计算量最小。其状态转移方程为: ``` dp[i][j] = min{ dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j] } ``` 其中,`dp[i][j]` 表示从第 `i` 个矩阵乘到第 `j` 个矩阵的最小代价,`p[i]` 表示第 `i` 个矩阵的行数,以及第 `i+1` 个矩阵的列数。 下面是 C 语言实现矩阵连乘最优加括号方案的代码: ```c #include <stdio.h> #include <limits.h> #define N 10 int dp[N][N]; // 存储最小代价 int s[N][N]; // 存储最优解 void matrix_chain_order(int p[], int n) { int i, j, k, l; for (i = 1; i <= n; i++) { dp[i][i] = 0; } for (l = 2; l <= n; l++) { for (i = 1; i <= n-l+1; i++) { j = i + l - 1; dp[i][j] = INT_MAX; for (k = i; k <= j-1; k++) { int q = dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j]; if (q < dp[i][j]) { dp[i][j] = q; s[i][j] = k; } } } } } void print_optimal_parens(int i, int j) { if (i == j) { printf("A%d", i); } else { printf("("); print_optimal_parens(i, s[i][j]); print_optimal_parens(s[i][j]+1, j); printf(")"); } } int main() { int p[] = {30, 35, 15, 5, 10, 20, 25}; int n = sizeof(p) / sizeof(int) - 1; // 矩阵个数 matrix_chain_order(p, n); printf("最小代价:%d\n", dp[1][n]); printf("最优加括号方案:"); print_optimal_parens(1, n); printf("\n"); return 0; } ``` 上述代码实现矩阵连乘最优加括号方案的求解,并输出了最小代价以及最优加括号方案。其中,`p[]` 数组表示矩阵的行列数,`n` 表示矩阵的个数。`matrix_chain_order()` 函数实现了动态规划的过程,`print_optimal_parens()` 函数实现了输出最优加括号方案的过程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值