Algorithm --> 矩阵链乘法
动态规划--矩阵链乘法
1、矩阵乘法

- #include <iostream>
- using namespace std;
- #define A_ROWS 3
- #define A_COLUMNS 2
- #define B_ROWS 2
- #define B_COLUMNS 3
- void matrix_multiply(int A[A_ROWS][A_COLUMNS],int B[B_ROWS][B_COLUMNS],int C[A_ROWS][B_COLUMNS]);
- int main()
- {
- int A[A_ROWS][A_COLUMNS] = {,,
- ,,
- ,};
- int B[B_ROWS][B_COLUMNS] = {,,,
- ,,};
- int C[A_ROWS][B_COLUMNS] = {};
- matrix_multiply(A,B,C);
- for(int i=;i<A_ROWS;i++)
- {
- for(int j=;j<B_COLUMNS;j++)
- cout<<C[i][j]<<" ";
- cout<<endl;
- }
- return ;
- }
- void matrix_multiply(int A[A_ROWS][A_COLUMNS],int B[B_ROWS][B_COLUMNS],int C[A_ROWS][B_COLUMNS])
- {
- if(A_COLUMNS != B_ROWS)
- cout<<"error: incompatible dimensions."<<endl;
- else
- {
- int i,j,k;
- for(i=;i<A_ROWS;i++)
- for(j=;j<B_COLUMNS;j++)
- {
- C[i][j] = ;
- for(k=;k<A_COLUMNS;k++)
- C[i][j] += A[i][k] * B[k][j]; //将A的每一行的每一列与B的每一列的每一行的乘积求和
- }
- }
- }
结果:
2、矩阵链乘问题描述
给定n个矩阵构成的一个链<A1,A2,A3,.......An>,其中i=1,2,...n,矩阵A的维数为pi-1pi,对乘积 A1A2...An 以一种最小化标量乘法次数的方式进行加全部括号。
注意:在矩阵链乘问题中,实际上并没有把矩阵相乘,目的是确定一个具有最小代价的矩阵相乘顺序。找出这样一个结合顺序使得相乘的代价最低。
3、动态规划分析过程
1)最优加全部括号的结构
动态规划第一步是寻找一个最优的子结构。假设现在要计算AiAi+1....Aj的值,计算Ai...j过程当中肯定会存在某个k值(i<=k<j)将Ai...j分成两部分,使得Ai...j的计算量最小。分成两个子问题Ai...k和Ak+1...j,需要继续递归寻找这两个子问题的最优解。
有分析可以到最优子结构为:假设AiAi+1....Aj的一个最优加全括号把乘积在Ak和Ak+1之间分开,则Ai..k和Ak+1..j也都是最优加全括号的。
2)一个递归解
设m[i,j]为计算机矩阵Ai...j所需的标量乘法运算次数的最小值,对此计算A1..n的最小代价就是m[1,n]。现在需要来递归定义m[i,j],分两种情况进行讨论如下:
当i==j时:m[i,j] = 0,(此时只包含一个矩阵)
当i<j 时:从步骤1中需要寻找一个k(i≤k<j)值,使得m[i,j] =min{m[i,k]+m[k+1,j]+pi-1pkpj} (i≤k<j)。
3)计算最优代价
设矩阵Ai的维数为pi- 1pi,i=1,2.....n。输入序列为:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代价,s[n][n]保存计算m[i,j]时取得最优代价处k的值,最后可以用s中的记录构造一个最优解。 书中给出了计算过程的伪代码,摘录如下:
- MAXTRIX_CHAIN_ORDER(p)
- n = length[p]-;
- for i= to n
- do m[i][i] = ;
- for t = to n //t is the chain length
- do for i= to n-t+
- j=i+t-;
- m[i][j] = MAXLIMIT;
- for k=i to j-
- q = m[i][k] + m[k+][i] + qi-1qkqj;
- if q < m[i][j]
- then m[i][j] = q;
- s[i][j] = k;
- return m and s;
MATRIX_CHAIN_ORDER具有循环嵌套,深度为3层,运行时间为O(n3)。如果采用递归进行实现,则需要指数级时间Ω(2n),因为中间有些重复计算。递归是完全按照第二步得到的递归公式进行计算,递归实现如下所示:
- int recursive_matrix_chain(int *p,int i,int j,int m[N+][N+],int s[N+][N+])
- {
- if(i==j)
- m[i][j] = ;
- else
- {
- int k;
- m[i][j] = MAXVALUE;
- for(k=i;k<j;k++)
- {
- int temp = recursive_matrix_chain(p,i,k,m,s) +recursive_matrix_chain(p,k+,j,m,s) + p[i-]*p[k]*p[j];
- if(temp < m[i][j])
- {
- m[i][j] = temp;
- s[i][j] = k;
- }
- }
- }
- return m[i][j];
- }
对递归算计的改进,可以引入备忘录,采用自顶向下的策略,维护一个记录了子问题的表,控制结构像递归算法。完整程序如下所示:
- int memoized_matrix_chain(int *p,int m[N+][N+],int s[N+][N+])
- {
- int i,j;
- for(i=;i<=N;++i)
- for(j=;j<=N;++j)
- {
- m[i][j] = MAXVALUE;
- }
- return lookup_chain(p,,N,m,s);
- }
- int lookup_chain(int *p,int i,int j,int m[N+][N+],int s[N+][N+])
- {
- if(m[i][j] < MAXVALUE)
- return m[i][j]; //直接返回,相当于查表
- if(i == j)
- m[i][j] = ;
- else
- {
- int k;
- for(k=i;k<j;++k)
- {
- int temp = lookup_chain(p,i,k,m,s)+lookup_chain(p,k+,j,m,s) + p[i-]*p[k]*p[j]; //通过递归的形式计算,只计算一次,第二次查表得到
- if(temp < m[i][j])
- {
- m[i][j] = temp;
- s[i][j] = k;
- }
- }
- }
- return m[i][j];
- }
4)构造一个最优解
第三步中已经计算出来最小代价,并保存了相关的记录信息。因此只需对s表格进行递归调用展开既可以得到一个最优解。书中给出了伪代码,摘录如下:
- PRINT_OPTIMAL_PARENS(s,i,j)
- if i== j
- then print "Ai"
- else
- print "(";
- PRINT_OPTIMAL_PARENS(s,i,s[i][j]);
- PRINT_OPTIMAL_PARENS(s,s[i][j]+,j);
- print")";
4、编程实现
采用C++语言实现这个过程,现有矩阵A1(30×35)、A2(35×15)、A3(15×5)、A4(5×10)、A5(10×20)、A6(20×25),得到p=<30,35,15,5,10,20,25>。实现过程定义两个二维数组m和s,为了方便计算其第一行和第一列都忽略,行标和列标都是1开始。完整的程序如下所示:
- #include <iostream>
- using namespace std;
- #define N 6
- #define MAXVALUE 1000000
- void matrix_chain_order(int *p,int len,int m[N+][N+],int s[N+][N+]);
- void print_optimal_parents(int s[N+][N+],int i,int j);
- int main()
- {
- int p[N+] = {,,,,,,};
- int m[N+][N+]={};
- int s[N+][N+]={};
- int i,j;
- matrix_chain_order(p,N+,m,s);
- cout<<"m value is: "<<endl;
- for(i=;i<=N;++i)
- {
- for(j=;j<=N;++j)
- cout<<m[i][j]<<" ";
- cout<<endl;
- }
- cout<<"s value is: "<<endl;
- for(i=;i<=N;++i)
- {
- for(j=;j<=N;++j)
- cout<<s[i][j]<<" ";
- cout<<endl;
- }
- cout<<"The result is:"<<endl;
- print_optimal_parents(s,,N);
- return ;
- }
- void matrix_chain_order(int *p,int len,int m[N+][N+],int s[N+][N+])
- {
- int i,j,k,t;
- for(i=;i<=N;++i)
- m[i][i] = ;
- for(t=;t<=N;t++) //当前链乘矩阵的长度
- {
- for(i=;i<=N-t+;i++) //从第一矩阵开始算起,计算长度为t的最少代价
- {
- j=i+t-;//长度为t时候的最后一个元素
- m[i][j] = MAXVALUE; //初始化为最大代价
- for(k=i;k<=j-;k++) //寻找最优的k值,使得分成两部分k在i与j-1之间
- {
- int temp = m[i][k]+m[k+][j] + p[i-]*p[k]*p[j];
- if(temp < m[i][j])
- {
- m[i][j] = temp; //记录下当前的最小代价
- s[i][j] = k; //记录当前的括号位置,即矩阵的编号
- }
- }
- }
- }
- }
- //s中存放着括号当前的位置
- void print_optimal_parents(int s[N+][N+],int i,int j)
- {
- if( i == j)
- cout<<"A"<<i;
- else
- {
- cout<<"(";
- print_optimal_parents(s,i,s[i][j]);
- print_optimal_parents(s,s[i][j]+,j);
- cout<<")";
- }
- }
结果:
5、总结
动态规划解决问题关键是分析过程,难度在于如何发现其子问题的结构及子问题的递归解。这个需要多多思考,不是短时间内能明白。在实现过程中遇到问题就是数组,数组的下标问题是个比较麻烦的事情,如何能够过合理的去处理,需要一定的技巧。
Algorithm --> 矩阵链乘法的更多相关文章
- POJ1260 Pearls(dp,矩阵链乘法)
题目链接. 题目大意: 给定一个n,和两个序列a[i], p[i]. a[i] 表示需要购买 i品质 的数量,p[i] i 等级的价格. 1.每个品质都会有不同的价格,价格依据品质上升而上升 2.买一 ...
- CODEVS 3546 矩阵链乘法
http://codevs.cn/problem/3546/ 题目 给定有n个要相乘的矩阵构成的序列(链)<A1,A2,A3,.......,An>,要计算乘积A1A2.....An.一组 ...
- MCM(矩阵链乘法)
这是<算法导论>动态规划中的一个问题.问题简述如下:我们在求解矩阵相乘时通常会有一个最优括号方案来对矩阵进行顺序相乘,这样会减少大量的计算时间. 我们知道矩阵A.B相乘,只能是当矩阵A的列 ...
- 【动态规划】XMU 1029 矩阵链乘法
题目链接: http://acm.xmu.edu.cn/JudgeOnline/problem.php?id=1029 题目大意: 题同乘法难题.给n+1个数,头尾不能动,中间的数可取出,取出时代价是 ...
- 【CODEVS】3546 矩阵链乘法
[算法]区间DP [题解] 注意先输出右括号后输出左括号. f[i][i+x-1]=min(f[i][i+x-1],f[i][j]+f[j+1][i+x-1]+p[i]*p[j+1]*p[i+x]) ...
- 51 Nod 1013 3的幂的和 矩阵链乘法||逆元+快速幂
这道题我写了两种写法 一种利用逆元 a/b%mod=a*c%mod; (c是b的逆元)易得2的逆元就是5~~~04: 一种是矩阵快速幂 利用递推式得出结论 #include<cstdio> ...
- COJ 0016 20603矩阵链乘
传送门:http://oj.cnuschool.org.cn/oj/home/solution.htm?solutionID=35454 20603矩阵链乘 难度级别:B: 运行时间限制:1000ms ...
- 【UVa-442】矩阵链乘——简单栈练习
题目描述: 输入n个矩阵的维度和一些矩阵链乘表达式,输出乘法的次数.如果乘法无法进行,输出error. Sample Input 9 A 50 10 B 10 20 C 20 5 D 30 35 E ...
- UVa 10003 切木棍(区间DP+最优矩阵链乘)
https://vjudge.net/problem/UVA-10003 题意: 有一根长度为L的棍子,还有n个切割点的位置.你的任务是在这些切割点的位置处把棍子切成n+1部分,使得总切割费用最小.每 ...
随机推荐
- Android View绘制和显示原理简介
现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...
- Anaroid WebView API详解
android提供了webView控件专门用来浏览网页 使用WebView时,在程序中装载WebView控件,可以把WebView写到布局中,也可以动态New出WebView实例,可以设置属性,比如: ...
- javascript 获取滚动条距离顶部的位置(兼容所有的)。
function getScrollTop() { var scrollPos; if (window.pageYOffset) { scrollPos = window.pageYOffset; } ...
- java特征
java的核心是面向对象,与之相对的是面向过程的编程,在对整个java编程没有足够的理解和运用的情况下恐怕没办法很好的理解这两个概念. 在我的初步理解中,写一个程序就例如做一件事情,面向过程的思想或许 ...
- 解析FAT16文件系统
引导扇区的信息如下: 1. 偏移地址00H,长度3,内容:EB 3C 90 跳转指令. 2. 偏移地址03H,长度8,内容:4D 53 44 4F 53 35 2E 30 为厂商标志和os 版本号 ...
- PCI-E配置MSI中断流程解析
在传统的pci中断体系中,每一个pci总线上的设备被分配一个特定的中断号,然后当设备需要中断cpu时,设备直接发出int信号,然后在cpu的inta引脚拉低的时候将自己的中断号放在数据总线上,一切都要 ...
- python做基本的图像处理
PIL是python中的图像处理类库,为python提供了基本的图像处理和基本操作.而PIL中最重要的就是Image模块,下面给出具体的例子来理解此模块. 读取一幅图像 我们用Image模块中的ope ...
- 过滤文件代码 python
import os import cv2 import shutil # store all file in directory global totalFileList totalFileList ...
- Linux之shell编程
一.Bash变量 1) Bash变量与变量分类 1. 定义:变量是计算机内存的单元,其中存放的值可以改变 2. 变量命令规则 #变量名必须以字母或下划线开头,名字中间只能由字母.数字和下划线组成 #变 ...
- 简要分析javascript的选项卡和轮播图
选项卡 思路 1.按钮和展示的页面要对应:分别遍历,记住当前按钮的索引,让其成为展示页面的索引 2.只出现所对应的页面:所有的页面隐藏,只展示想要的页面 只展示js代码 for(var i=0;i&l ...