算法设计与分析——矩阵链乘积问题

	对于矩阵链乘积问题来说,可以采用加括号的方式来得到计算次数最少的加括号方式,
而对于一串给定的矩阵链成,如果这一串矩阵只有一个,或者两个,那么计算乘法的时候
就只有一种加括号方式 
	(A) 或者 (A1 A2)
	
	对于三个来说,就有
	(A1 A2)A3 或 A1 (A2 A3)等于 2 种
	
	对于四个来说
	(A1)((A2 A3)A4)或(A1)(A2(A3 A4)) 或 
	(A1 A2)(A3 A4)或 
	((A1 A2)A3)(A4)或 (A1 (A2 A3))(A4) 等于 5 种
	
	从上面可以总结出规律,对于一个矩阵的链乘积,可以将它转换成为两个更加小的
矩阵的链乘积,例如 A1 * A2 * ... * An ,如果从中间的某一项 Ak 处断开的话,那么
前 k 项乘积得到一个最小的矩阵,后面的 n - k 项得到另外一个矩阵,对于这两个小的
矩阵也可以做如此分类,直到最后只剩下一个矩阵或者一对矩阵,所以,矩阵链乘积的
最小的乘法运算次数取决于分得的两个小矩阵的规模以及两个小矩阵对应的最优问题,即
	p(A)= {
		0, n = 1
		row1 * row2 * col2, n = 2
		min(row1 * row2 * col2 + 左子问题P(A) + 右子问题P(A)), n > 2
		注意:这里的 row1 row2 是分解的两个子问题得到矩阵的行列
	}
	所以要想得到最优解的话就需要对所有的子问题组合进行取最小值,但是如果就按照上面的
公式暴力求解的话,最终会产生大量的重复计算,所以可以采用二维数组将所有可能的子问题
保存起来,以达到简化的目的,最后的二维矩阵的 i 和 j 分别代表 子问题的起始位置和中止
位置,状态转移方程同上,将 p(A) 换成矩阵里面对应的子问题的取值即可。
	代码里面的 r 代表的是分解的两个矩阵中第一个矩阵的最后一个矩阵。
#include <iostream>
#include <vector>

using namespace std;

struct Node
{
	int small;
	int r;
};

// 打印加括号方式 
void get(Node ** target, int i, int j, vector<char>& a)
{
	if(i == j)
	{
		a.push_back('A');
		a.push_back('0' + i);
		return ;
	} 
	else if(j == i + 1)
	{
		a.push_back('A');
		a.push_back('0' + i);
		a.push_back('A');
		a.push_back('0' + j);
		return ;
	}
	a.push_back('(');
	get(target, i, target[i][j].r, a);
	a.push_back(')');
	a.push_back('(');
	get(target, target[i][j].r + 1, j, a);
	a.push_back(')');
}

int main()
{
	int n, i;
	cin >> n;
	int * weight = new int[n + 1];
	Node ** target = new Node*[n + 1];
	for(i = 0; i < n + 1; ++i)
	{
		cin >> weight[i];
		target[i] = new Node[n + 1];
	}
	// 初始化 
	for(int i = 0; i < n + 1; ++i)
	{
		for(int j = 0; j < n + 1; ++j)
		{
			target[i][j].small = 0;
			target[i][j].r = -1;	
		}
	} 
	// 初始化对角线 
	for(i = 0; i < n + 1; ++i)
	{
		target[i][i].small = 0;
		target[i][i].r = -1;
	}
	// 初始化对角线的下一对角线 
	for(i = 1; i + 1 < n + 1; ++i)
	{
		target[i][i + 1].small = weight[i - 1] * weight[i] * weight[i + 1];
		target[i][i + 1].r = i;
	}
	
	
	// 计算所有 
	for(int l = 2; l < n + 1; ++l)
	{
		for(int i = 1; l + i < n + 1; ++i) // l = 3 ,j = 4, i = 1
		{
			int j = l + i, min = target[i][i + 1].small + target[i + 2][j].small + weight[i - 1] * weight[i + 1] * weight[j], r = i + 1;
			for(int k = i; k < j; ++k)
			{
				int temp = target[i][k].small + target[k + 1][j].small + weight[i - 1] * weight[k] * weight[j];
				if(temp < min)
				{
					min = temp;
					r = k; // 划分线在 k 后面 
				}
			}
			target[i][j].small = min;
			target[i][j].r = r; // 记录的是划分线的前一个矩阵 
		}
	}
	
	// 循环输出
	for(int i = 1; i < n + 1; ++i)
	{
		for(int j = 1; j < n + 1; ++j)
		{
			cout << '<' << target[i][j].r << ',' << target[i][j].small << '>' << ' ';
		}
		cout << endl;
	} 
	
	// 输出加括号方式
	vector<char> a;
	get(target, 1, n, a);
	for(i = 0; i < a.size(); ++i)
	{
		cout << a[i];
	}
		
	return 0;
} 

代码只经过少量的数据测试,如果有问题,欢迎大家评论或私信,小编一定尽快改正。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值