算法导论------------------动态规划之矩阵链问题

【问题描述】

给定有n个连乘矩阵的维数,要求计算其采用最优计算次序时所用的乘法次数,即所要求计算的乘法次数最少。例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10*100,100*5和5*50,采用(A1A2)A3,乘法次数为10*100*5+10*5*50=7500次,而采用A1(A2A3),乘法次数为100*5*50+10*100*50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次。

分析:

矩阵链乘法问题描述:

给定由n个矩阵构成的序列[A1,A2,...,An],对乘积A1A2...An,找到最小化乘法次数的加括号方法。

1)寻找最优子结构

此问题最难的地方在于找到最优子结构。对乘积A1A2...An的任意加括号方法都会将序列在某个地方分成两部分,也就是最后一次乘法计算的地方,我们将这个位置记为k,也就是说首先计算A1...Ak和Ak+1...An,然后再将这两部分的结果相乘。

最优子结构如下:假设A1A2...An的一个最优加括号把乘积在Ak和Ak+1间分开,则前缀子链A1...Ak的加括号方式必定为A1...Ak的一个最优加括号,后缀子链同理。

一开始并不知道k的确切位置,需要遍历所有位置以保证找到合适的k来分割乘积。

2)构造递归解

设m[i,j]为矩阵链Ai...Aj的最优解的代价,则

┌ 0 如果i = j

m[i,j] =

└ min(i≤k<j) {m[i,k] + m[k+1,j] + p[i-1] * p[k] *p[j] } 如果i<j

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

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

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

这里我使用c++中容器来解决,一般使用的数组下标带来的问题极其容易出差,还是c++比较方便,解决数组必须用常量来表示数组下标。不废话了,思路自己看算法导论树上的讲解,搞清解决问题原理写出代码就轻而易举。

#include<iostream>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;

pair<vector<vector<int>>,vector<vector<int>>> matrix_chain_order(vector<int> p)
{
	int n = p.size();
	vector<vector<int>> m(n, vector<int>(n, 0));//vector的vector相当于数组的数组,即二维数组m[][]
	vector<vector<int>> s(n, vector<int>(n, 0));//同上s[][]

	for (int len = 2; len < n; len++)
	{
		for (int i = 1; i < n - len + 1; ++i)
		{
			int j = i + len - 1;
			m[i][j] = 0x7fffffff;
			for (int k = i; k <= j-1; ++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;
				}
			}
		}
	}

	return make_pair(m, s);
}

//打印最优解
void print_Optimal_Parens(vector<vector<int> > s, int i, int j)
{
	if (i == j)
		cout << "A" << i;
	else
	{
		cout << "(";
		print_Optimal_Parens(s, i, s[i][j]);
		print_Optimal_Parens(s, s[i][j] + 1, j);
		cout << ")";
	}
}

//一般递归方法
int recursive_matrix_chain(vector<int> p, int i, int j)
{
	int n = p.size();
	vector<vector<int>> m(n, vector<int>(n, 0));
	if (i == j)
		return 0;
	m[i][j] = 0x7fffffff;
	for (int k = i; k < j; k++)
	{
		int q = recursive_matrix_chain(p, i, k) +
			recursive_matrix_chain(p, k + 1, j) + p[i - 1] * p[k] * p[j];
		if (q < m[i][j])
			m[i][j] = q;
	}

	return m[i][j];
}

//带备忘的自顶向下的递归方法实现
int look_up_chain(vector<vector<int>> m, vector<int> p, int i, int j)
{

	if (m[i][j] < 0x7fffffff)//如果m[i][j]不是无穷大则说明m[i][j]已经被下一层的LookUpChain计算出来
		return m[i][j];
	if (i == j)
		m[i][j] = 0;

	for (int k = i; k < j; k++)
	{
		int q = look_up_chain(m, p, i, k) +
			look_up_chain(m, p, k + 1, j) + p[i - 1] * p[k] * p[j];
		if (q < m[i][j])
			m[i][j] = q;
	}

	return m[i][j];
}

int memoized_matrix_chain(vector<int> p,int s,int e)
{
	int n = p.size();
	vector<vector<int>> m(n, vector<int>(n, 0));
	for (int i = 1; i < n; i++)
	{
		for (int j = i; j < n; ++j)
		{
			m[i][j] = 0x7fffffff;
		}
	}

	return look_up_chain(m, p, s, e);
}

int main()
{
	vector<int> p = { 30, 35, 15, 5, 10, 20, 25 };
	vector<vector<int>> m = matrix_chain_order(p).first;
	vector<vector<int>> s = matrix_chain_order(p).second;

	cout << "vector m:" << endl;
	for (size_t i = 0; i < m.size(); i++)
	{
		copy(m[i].begin(), m[i].end(), ostream_iterator<int>(cout, " "));
		cout << endl;
	}

	cout << "vector s:" << endl;
	for (size_t i = 0; i <s.size(); i++)
	{
		copy(s[i].begin(), s[i].end(), ostream_iterator<int>(cout, " "));
		cout << endl;
	}

	cout << "输出最优解:————>";
	print_Optimal_Parens(s, 1, 6);
	cout << endl;
	cout << "--------------------------------------------------" << endl;;

	vector<vector<int>> m1 = matrix_chain_order(p).first;
	vector<int> p1 = { 30, 35, 15, 5, 10, 20, 25 };

	cout << "普通递归方法求解:";
	cout << recursive_matrix_chain(p1, 1, 6) << endl;

	cout << "带备忘的递归方法求解:";
	cout << look_up_chain(m1, p1, 1, 6) << endl;

}

时间: 2024-11-06 12:28:42

算法导论------------------动态规划之矩阵链问题的相关文章

算法导论--动态规划(矩阵链乘法)

矩阵链乘法问题 给定一个n个矩阵的序列?A1,A2,A3...An?,我们要计算他们的乘积:A1A2A3...An.因为矩阵乘法满足结合律,加括号不会影响结果.可是不同的加括号方法.算法复杂度有非常大的区别: 考虑矩阵链:?A1,A2,A3?.三个矩阵规模分别为10×100.100×5.5×50 假设按((A1A2)A3)方式,须要做10?100?5=5000次,再与A3相乘,又须要10?5?50=2500,共须要7500次运算: 假设按(A1(A2A3))方式计算.共须要100?5?50+10

算法13---动态规划矩阵链乘法

算法13---动态规划矩阵链乘法 矩阵链乘法是动态规划里面使用到的一个例子 1 两个矩阵的计算 那么对于一个矩阵的乘法,首先如果是两个矩阵的乘法,那么如何实现呢? 注意到我们使用二维数组表示矩阵,但是二维数组不能作为函数的返回值.具体实现如下 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 5 #define a_rows 3 6 #define a_columns 4 7 #define

算法导论--动态规划(装配线调度)

装配线问题: 某个工厂生产一种产品,有两种装配线选择,每条装配线都有n个装配站.可以单独用,装配线1或2加工生产,也可以使用装配线i的第j个装配站后,进入另一个装配线的第j+1个装配站继续生产.现想找出通过工厂装配线的最快方法. 装配线i的第j个装配站表示为Si,j,在该站的装配时间是ai,j 如果从 Si,j装配站生产后,转移到另一个生产线继续生产所耗费的时间为ti,j 进入装配线花费时间ei,完成生产后离开装配线所耗费时间为xi 令f*表示通过生产所有路线中的最快的时间 令fi[j]表示从入

算法导论--动态规划(钢条切割)

钢条切割问题 现有一段长度为n英寸的钢条和一个价格表pi,求切割方案使销售利益最大rn最大 长度为n英寸的钢条共有2n?1种不同的切割方案,因为可以每个整英寸的位置都可以决定切割或者不切割. 为了得到rn最大,可以把这个问题分成子问题求解,先切一刀,再考虑余下的部分的最大收益即求 rn=max{pk+rn?k}(k=1,2,3-n-1), pk部分不进行继续切割,直接作为一个整体售出 ; rn?k部分继续切割,考虑所有的情况,分成子问题. 求出所有k值对应的收益最大者作为rn 也有可能不进行任何

算法导论--动态规划(最长公共子序列)

最长公共子序列问题(LCS) 给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列. 例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4: 对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较.假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低. 动态规划 前

【算法导论】动态规划之“矩阵链乘法”问题

上一篇里,介绍了动态规划的"钢管切割"问题,这一次来看看"矩阵链乘法".所谓矩阵链乘法就是一个或一个以上的矩阵连续相乘,这里主要关注的是乘法的次数. 一.概述 以两个矩阵相乘为例,A1*A2,A1和A2为两个矩阵,假设A1的行列数是p*q,A2的行列数是q*r.注意这里由于是A1乘以A2,所以A1的列数要等于A2的行数,否则无法做矩阵乘法,满足上述条件的矩阵,我们称之为"相容"的.那么对于A1*A2而言,我们需要分别执行p*r次对应A1的行元素乘

动态规划之矩阵链乘

问题提出: 对于如下矩阵: 其中各矩阵A[i]下标为 计算其乘积的结果,以及我们需要计算其最小标量乘法次数. 问题分析: 首先我们需要明确的是何为标量:标量即为没有方向的量,而有方向的量即为矢量.(严谨的定义自己百度去) 那么标量乘法就变成了最基本的数字相乘. 其次对于两个矩阵相乘,需满足下示公式所示的形式:(左边矩阵的列数与右边矩阵的行数必须一致) 上述条件可从矩阵相乘的定义中看出: 在计算机,我们可以用一个二维数组来表示矩阵. 一个m行n列的矩阵与一个n行p列的矩阵相乘,会得到一个m行p列的

算法导论—动态规划

华电北风吹 天津大学认知计算与应用重点实验室 日期:2015/8/27 首先区分动态规划和分治策略. 这两者有很相似的地方,都是通过组合子问题的解来求解原问题.不同的是,分治策略将原问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解.与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子问题).在这种情况下,分治法会做许多不必要的工作,他会反复求解那些公共的子子问题.而动态规划算法对每个子子问题只求

算法导论——动态规划

动态规划指的是一个问题可以拆分成多个小的最优子问题,并且这些子问题具有重叠,典型的如斐波那契数列:f(2)=f(1)+f(0),f(3)=f(2)+f(1),f(4)=f(3)+f(2),若使用简单的递归算法求f(4),则f(2)会被计算两次,当计算f(n)时需要计算f(n-1)和f(n-2)而f(n-1)又要计算记一次f(n-2),如此往复时间复杂度为n的指数级别,导致算法效率低下.若能够记录f(2)至f(n-1)的结果,可以保证每一级计算都是O(1)复杂度,整个算法的时间复杂度就能下降至O(