矩阵连乘问题

文章介绍了矩阵链乘问题,通过动态规划算法寻找计算矩阵连乘积的最小数乘次数。算法包括四个步骤:定义最优值、递归求解、自底向上计算最优值和构造最优解。同时,对比了动态规划与穷举法在解决该问题中的效率差异。动态规划算法的时间复杂性和空间复杂性分别与矩阵的数量和维数相关。
摘要由CSDN通过智能技术生成

【问题】:矩阵链乘问题给定n个矩阵{A1,A2,...,An},其中Ai与Ai+1是可乘的,i=1,2...,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。

1、按设计动态规划算法的步骤解题。
(1)找出最优解的性质,并刻划其结构特征。
(2)递归地定义最优值。
(3)以自底向上的方式计算出最优值。
(4)根据计算最优值时得到的信息,构造最优解(由子结构的最优解得到原先大问题的最优解)。

2、求算法的时间复杂性,和空间复杂性

3、体会动态规划和穷举法在解决该问题中的本质差异。

问题辅助分析:

(1)问题的描述

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2,…,n-1。要算出这n个矩阵的连乘积A1A2…An。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。

完全加括号的矩阵连乘积可递归地定义为: (1)单个矩阵是完全加括号的; (2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)。

 例如,矩阵连乘积A1A2A3A4有5种不同的完全加括号的方式:(A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)。每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。若A是一个p×q矩阵,B是一个q×r矩阵,则计算其乘积C=AB的标准算法中,需要进行pqr次数乘。

 为了说明在计算矩阵连乘积时,加括号方式对整个计算量的影响,先考察3个矩阵{A1,A2,A3}连乘的情况。设这三个矩阵的维数分别为10×100,100×5,5×50。加括号的方式只有两种:((A1A2)A3),(A1(A2A3)),第一种方式需要的数乘次数为10×100×5+10×5×50=7500,第二种方式需要的数乘次数为100×5×50+10×100×50=75000。第二种加括号方式的计算量时第一种方式计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大的影响。于是,自然提出矩阵连乘积的最优计算次序问题,即对于给定的相继n个矩阵{A1,A2,…,An}(其中矩阵Ai的维数为pi-1×pi,i=1,2,…,n),如何确定计算矩阵连乘积A1A2…An的计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。 

穷举搜索法的计算量太大,它不是一个有效的算法,本实验采用动态规划算法解矩阵连乘积的最优计算次序问题。

(2)算法设计思想

动态规划算法的基本思想是将待求解问题分成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,动态规划法经分解得到的子问题往往不是相互独立的,前一子问题的解为后一子问题的解提供有用的信息,可以用一个表来记录所有已解决的子问题的答案,不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。

//求出矩阵A[i:j]的最少数乘次数m[i][j],和记录矩阵A[i:j]此时的断开位置s[i][j]
//p为输入的矩阵的行数或列数。n为矩阵个数。m[i][j]为矩阵A[i:j]最少乘法次数。 s[i][j]为当最少乘法次数时,记录此时的断开位置(即加括号的位置)
void MatrixChain(int *p,int n,int m[][100],int s[][100])
{
        for (int i = 1; i <= n; i++) m[i][i] = 0;  //初始化单个矩阵的乘法次数都为0次。使用矩阵的下标从1,1开始
        for (int r = 2; r <= n; r++)   //r为矩阵连乘的长度,即矩阵个数
           for (int i = 1; i <= n - r+1; i++) {      //i为矩阵连乘的起点
              int j=i+r-1;                                    //j为矩阵连乘的终点
              m[i][j] = m[i][i]+ m[i+1][j]+ p[i-1]*p[i]*p[j];   
              s[i][j] = i; 
              for (int k = i+1; k < j; k++) {     //找出更优的乘法次数
                 int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                 if (t < m[i][j]) { m[i][j] = t; s[i][j] = k;}
              }
          }
}

//利用断开位置s[i][j]输出矩阵A[i:j]的最优加括号方式
void print_optimal(int s[100][100], int i ,int j, int a[])
{
	if(i==j)	cout<<" A["<<a[i-1]<<","<<a[i]<<"]";
	else{
		cout<<" ( ";
		print_optimal(s,i,s[i][j],a);
		print_optimal(s,s[i][j]+1,j,a);
		cout<<" ) ";
	}
}

 

#include<iostream>
using namespace std;
#include <iomanip>
#define MAX 100
int m[MAX][MAX];
int s[MAX][MAX];

void cout_m(int m[][100], int n) {
	cout << "m矩阵:" << endl << setw(10);
	for (int i = 1; i < n + 1; i++)
	{
		for (int j = 1; j < n + 1; j++)
		{
			int x = m[i][j];
			cout << m[i][j] << setw(10);
		}
		cout << endl;
	}
}
void cout_s(int s[][100], int n) {
	cout << "s矩阵" << endl << setw(10);;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++)
		{
			cout << s[i][j] << setw(10);
		}
		cout << endl;
	}
}

void MatrixChain(int* p, int n, int m[][100], int s[][100]) {
	for (int i = 1; i <= n; i++) {
		m[i][i] = 0;
	}
	for (int r = 2; r <= n; r++)
	{
		for (int i = 1; i <= n - r + 1; i++)
		{
			int j = i + r - 1;
			m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j];
			s[i][j] = i;
			for (int k = i + 1; k < j; k++)
			{
				int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
				if (t < m[i][j])
				{
					m[i][j] = t;
					s[i][j] = k;
				}
			}
		}
	}
}




void print_optimal(int s[100][100], int i, int j, int a[])
{
	if (i == j)	cout << " A[" << a[i - 1] << "," << a[i] << "]";
	else {
		cout << " ( ";
		print_optimal(s, i, s[i][j], a);
		print_optimal(s, s[i][j] + 1, j, a);
		cout << " ) ";
	}
}




int main() {
	int n = 0;
	int* p;

	while (true)
	{
		cout << "输入矩阵的个数:";
		cin >> n;
		if (n < 0)
		{
			break;
		}
		cout << endl << "请输入" << n + 1 << "个整数,分别是各个矩阵的行列数" << endl;
		p = new int[n];
		for (int i = 0; i < n + 1; i++) {

			cin >> p[i];
		}
		cout << "原始数据为以下矩阵" << endl;
		for (int i = 0; i < n; i++)
		{
			cout << p[i] << "*" << p[i + 1] << endl;
		}
		MatrixChain(p, n, m, s);
		cout_m(m, n);
		cout << endl;
		cout_s(s, n);
		cout << "最优的运算方式的乘法次数为:" << m[1][n] << endl;
		cout << "加括号的方式为:" << endl;
		print_optimal(s, 1, n, p);
		cout << endl;
	}
}

 memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。

头文件:<memory.h>或<string.h>

函数原型:void *memset(void *s, int ch, size_t n);

功能:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。

setw和setfill被称为输出控制符,使用时需要在程序开头写上#include "iomanip.h"

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值