0815------算法笔记----------矩阵连乘问题

1.矩阵连乘问题的定义

  1.1 给定 n 个矩阵的连乘积 A1A2...An,因为矩阵乘法满足结合律,所以计算矩阵的连乘积可以有不同的计算次序(这个次序的组合数满足卡特兰数),采用不同的计算次序计算的数乘次数也不相同。例如,A1A2A3,这三个矩阵的维数分别是10*100,100*5,和5*50,若先计算A1A2,总的计算次数为10*100*5+10*5*50 = 7500,然而先计算A2A3,总的计算次数为 100*5*50 + 10*100*50 = 75000,可见计算数乘次数相差10倍。这里我们用加括号的方式来表示矩阵的计算次序。每一种完全加括号方式对应一种矩阵的计算次序;

  1.2 什么是完全加括号的矩阵连乘积?完全加括号的矩阵连乘积可以递归的定义为(见《计算机算法分析与设计》):

    a)单个矩阵是完全加括号的;

    b)矩阵连乘积 A 是完全加括号的,则 A 可以表示为 2 个完全加括号的矩阵连乘积 B 和 C 的乘积并加括号,即 A =(BC)。

2.矩阵连乘问题的求解

  2.1 设矩阵连乘积A1A2...An,Ai的维数分别为 p[i-1]*p[i],m[i][j]为AiAi+1...Aj的最少数乘次数,它满足下述递归关系:

    a)m[i][j] = 0  , i == j;

    b)m[i][j] = min{m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j]},  i<= k < j;

  2.2 根据上述递归式可以写出本问题的递归方法,这里为了求出最终的计算次序,需要用一个二维数组 s 保存每次断开的位置。

#include <iostream>
#include <string>
#include <vector>
#include <string.h>
#define MAX 100
using namespace std;

int  MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]);
void TraceBack(int i, int j, int (*s)[MAX]);

int main(int argc, const char *argv[])
{
    int p[MAX], m[MAX][MAX], s[MAX][MAX];
    int n;
    cin >> n;                   //矩阵的个数
    int i ;
    for(i = 0; i <= n; i++){    //相乘矩阵链的行列数
        cin >> p[i];
    }
    memset(m, -1, sizeof m);

    MatrixChain(1, n, p, m, s);
    TraceBack(1, n, s);

    cout << endl;
    return 0;
}

int  MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]){
    if(m[i][j] != -1)
        return m[i][j];

    if(i == j)
       return 0;
    else{
        int k, min = 1000000;
        for(k = i; k < j; k++){
                m[i][k] = MatrixChain(i, k, p, m, s);
                m[k+1][j] = MatrixChain(k+1, j, p,  m, s);

                int tmp = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
                if(tmp  < min){
                    min = tmp;
                    s[i][j] = k;

                }
        }
        return min;
    }
}

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

    cout << "(";
    TraceBack(i, s[i][j], s);
    cout << "*";
    TraceBack(s[i][j] + 1, j, s);
    cout << ")";
}

  2.3 矩阵连乘的非递归方法其实就是一个填充表格的过程,以6个矩阵的乘积为例,矩阵的维度分别为p[] = {30, 35, 15, 5, 10, 20, 25},如下图所示,这里先将 m[i][i] 设置为 0, 之后再以正对角线方向根据上述递归式计算m[1][2],m[2][3]...m[5][6]等等,例如m[2][3] = m[2][2] + m[3][3] + p[1] * p[2] *p[3] = 0 + 0 + 30 * 35 * 15 = 15750,这里只能以2分割, 再例如计算 m[2][5] ,此时要分别计算出 m[2][2] + m[3][5] + p[1] * p[2] * p[5], m[2][3] + m[4][5] + p[1] * p[3] * p[5], 以及 m[2][4] + m[5][5] + p[1] * p[4] * p[5],然后求其最小值即为 m[2][5]。填充后的表格和源程序如下:

   1  2 3 4 5 6
1  0   15750 7875 9375 11875 15125
2   0 2625 4375 7125 10500
3     0 750 2500 5375
4       0 1000 3500
5         0 5000
6           0

    

#include <iostream>
#include <string>
#include <vector>
#define MAX 100
using namespace std;

void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]);
void TraceBack(int i, int j, int (*s)[MAX]);

int main(int argc, const char *argv[])
{
    int p[MAX], m[MAX][MAX], s[MAX][MAX];
    int n;
    cin >> n;                   //矩阵的个数
    int i ;
    for(i = 0; i <= n; i++){    //相乘矩阵链的行列数
        cin >> p[i];
    }

    MatrixChain(p, n, m, s);
    TraceBack(1, n, s);

    cout << endl;
    return 0;
}

void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]){
    int i;
    for(i = 1; i <= n; i++)
        m[i][i] = 0;

    int r;                                            //外层循环的次数
    for(r = 2; r <= n; r++){
        for(i = 1; i<= n - r + 1; i++){               //求解m[i][j]
            int j = i + r -1;

            int min = m[i][i] + m[i+1][j] + p[i-1] * p[i] * p[j];
            s[i][j] = i;

            int k;
            for(k = i + 1; k < j; k++){
                int tmp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                if(tmp < min){
                    min = tmp;
                    s[i][j] = k;
                }
            }
            m[i][j] = min;
        }
    }
}

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

    cout << "(";
    TraceBack(i, s[i][j], s);
    cout << "*";
    TraceBack(s[i][j] + 1, j, s);
    cout << ")";
}

  

3.小结

  3.1 和最长公共子序列问题相同,当我们使用动态规划来求解某一个问题时,这个问题一般都具有两个明显特征,一是最优子结构性质,即问题的最优解包含了子问题的最优解,二是子问题重叠,即在递归求解时,有些子问题被重复计算。

  3.2 针对上述子问题重叠的性质,我们使用了备忘录方法,即在递归的过程中将每个子问题的结果保存在数组中,如果下次需要直接从数组中取出,从而避免了重复计算。

0815------算法笔记----------矩阵连乘问题,布布扣,bubuko.com

时间: 2024-08-28 15:20:31

0815------算法笔记----------矩阵连乘问题的相关文章

算法笔记--矩阵快速幂

写的不错的博客:http://www.cnblogs.com/yan-boy/archive/2012/11/29/2795294.html 优点:根据数列递推式快速计算数列an的值(当n很大时) 步骤:由数列递推式构造矩阵,然后用矩阵快速幂计算矩阵的幂. 构造矩阵:对于an =x*an-1 +y*an-2 ,可以构造矩阵为: [an ,an-1]=[an-1 ,an-2]*A A=[x 1 y 0]; 矩阵快速幂模板: #include<iostream> #include<cstri

七月算法--12月机器学习在线班-第三次课笔记—矩阵和线性代数

七月算法--12月机器学习在线班-第三次课笔记—矩阵和线性代数 七月算法(julyedu.com)12月机器学习在线班学习笔记 http://www.julyedu.com

算法笔记-DTW动态时间规整

算法笔记-DTW动态时间规整 简介 简单的例子 定义 讨论 约束条件 步模式 标准化 点与点的距离函数 具体应用场景 分类 点到点匹配 算法笔记-DTW动态时间规整 动态时间规整/规划(Dynamic Time Warping, DTW)是一个比较老的算法,大概在1970年左右被提出来,最早用于处理语音方面识别分类的问题. 1.简介 简单来说,给定两个离散的序列(实际上不一定要与时间有关),DTW能够衡量这两个序列的相似程度,或者说两个序列的距离.同时DTW能够对两个序列的延展或者压缩能够有一定

利用QR算法求解矩阵的特征值和特征向量

利用QR算法求解矩阵的特征值和特征向量 为了求解一般矩阵(不是那种幼稚到shi的2 x 2矩阵)的特征值. 根据定义的话,很可能需要求解高阶方程... 这明显是个坑...高阶方程你肿么破... 折腾了好久 1.我要求特征值和特征向量. 2.找到一种算法QR分解矩阵求解特征值 3.QR矩阵分解需要Gram-schimidt正交化分解 有一种很明显的感觉,往往在现在很难有 很系统 很深入 的学习某一个学科的某一门知识. 往往学的时候"靠,学这东西有什么用""学了这么久,也不知道怎么用,不想学" 到后

小算法笔记

素数: 除 1 外只能被 1 和自身整除的数. 方法一: #include <stdio.h> #define N 1000 int num = 0; int prime(int n) { int i; if(n % 2 == 0) return (n == 2); if(n % 3 == 0) return (n == 3); if(n % 5 == 0) return (n == 5); for(i = 7; i*i <= n; ++i) if(n % i == 0) return

算法笔记之堆排序

一.对堆排序的相关了解 1.堆排序的运行时间是 O(nlogn) : 2.定义: 堆heap是一棵具有以下属性的二叉树-- (1)它是一棵完全二叉树: (2)每个结点大于或等于它的任意一个孩子. 备注:完全二叉树的定义--除了最后一层没填满以及最后一层的叶子都是偏左放置的,其他层都是满的二叉树! 3.二叉堆有两种:最大堆和最小堆.在堆排序中我们使用的是最大堆,最小堆常常在构造优先队列时使用. 4.一条路径的深度指的是这条路径的边数,一个结点的深度是指从根结点到该结点的路径的长度. 二.对堆进行排

《学习opencv》笔记——矩阵和图像操作——cvGEMM,cvGetCol,cvGetCols and cvGetDiag

矩阵和图像的操作 (1)cvGEMM函数 其结构 double cvGEMM(//矩阵的广义乘法运算 const CvArr* src1,//乘数矩阵 const CvArr* src2,//乘数矩阵 double alpha,//1号矩阵系数 const CvArr* src3,//加权矩阵 double beta,//2号矩阵系数 CvArr* dst,//结果矩阵 int tABC = 0//变换标记 ); tABC变换标记及其对应的含义 CV_GEMM_A_T 转置 src1 CV_GE

算法笔记_023:拓扑排序(Java)

目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进行排序.即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面. 2 解决方案 2.1 基于减治法实现 实现原理:不断地做这样一件事,在余下的有向图中求取一个源(source)(PS:定义入度为0的顶点为有向图的源),它是一个没有输入边的顶点,然后把它和所有从它出发

算法笔记_018:旅行商问题(Java)

目录 1 问题描述 2 解决方案 2.1 蛮力法   1 问题描述 何为旅行商问题?按照非专业的说法,这个问题要求找出一条n个给定的城市间的最短路径,使我们在回到触发的城市之前,对每个城市都只访问一次.这样该问题就可以表述为求一个图的最短哈密顿回路的问题.(哈密顿回路:定义为一个对图的每个顶点都只穿越一次的回路) 很容易看出来,哈密顿回路也可以定义为n+1个相邻顶点v1,v2,v3,...,vn,v1的一个序列.其中,序列的第一个顶点和最后一个顶点是相同的,而其它n-1个顶点都是互不相同的.并且