一、关于矩阵连乘
1. 问题描述:
- 给定n个矩阵:A1 ,A2,…,An,其中Ai与Ai+1是可乘的(i=1,2…,n-1)。确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
- 要求:输入数据为矩阵个数和每个矩阵规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数。
2. 问题分析:
-
关于矩阵乘法:
- 定义:设A为 m×p 的矩阵,B为 p×n 的矩阵,那么称矩阵A与B的乘积C为 m×n 的矩阵,记作C=AB,其中矩阵C中的第i行第j列元素可以表示为:
( A B ) i j = ∑ k = 1 p a i k b k j = a i 1 b 1 j + a i 2 b 2 j + ⋅ ⋅ ⋅ + a i p b p j (AB)_{ij}=\sum_{k=1}^p a_{ik}b_{kj}=a_{i1}b_{1j}+a_{i2}b_{2j}+···+a_{ip}b_{pj} (AB)ij=k=1∑paikbkj=ai1b1j+ai2b2j+⋅⋅⋅+aipbpj
例 : A = [ a 1 , 1 a 1 , 2 a 1 , 3 a 2 , 1 a 2 , 2 a 2 , 3 ] B = [ b 1 , 1 b 1 , 2 b 2 , 1 b 2 , 2 b 3 , 1 b 3 , 2 ] C = A B = [ c 1 , 1 c 1 , 2 c 1 , 3 c 2 , 1 c 2 , 2 c 2 , 3 ] 例:A=\begin{bmatrix} a_{1,1} & a_{1,2} & a_{1,3} \\ a_{2,1} & a_{2,2} & a_{2,3} \end{bmatrix} B=\begin{bmatrix} b_{1,1} & b_{1,2} \\ b_{2,1} & b_{2,2} \\ b_{3,1} & b_{3,2} \end{bmatrix} C=AB=\begin{bmatrix} c_{1,1} & c_{1,2} & c_{1,3} \\ c_{2,1} & c_{2,2} & c_{2,3} \end{bmatrix} 例:A=[a1,1a2,1a1,2a2,2a1,3a2,3] B=⎣⎡b1,1b2,1b3,1b1,2b2,2b3,2⎦⎤ C=AB=[c1,1c2,1c1,2c2,2c1,3c2,3] - 数乘次数:
(1)如果A是m×p的矩阵,B是p×n的矩阵,那么乘积C是m×n的矩阵。
(2)矩阵C共有m×n个元素,每个元素为A的一行与B的一列对应元素相乘再相加(此过程有p次数乘);
(3)故共计算了m×n×p次数乘。
- 定义:设A为 m×p 的矩阵,B为 p×n 的矩阵,那么称矩阵A与B的乘积C为 m×n 的矩阵,记作C=AB,其中矩阵C中的第i行第j列元素可以表示为:
-
关于矩阵连乘
- 计算次序:由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。
- 完全加括号的矩阵连乘积可递归地定义为:
(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)
·
二、算法实现
1. 动态规划 - 自底向上
//【动态规划】矩阵连乘问题
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n; // 矩阵个数
int* p; // 矩阵的维数
int** m; // 子链的数乘次数,初始值为0
int** s; // 子链的断开位置k
void MatrixChain(int n, int* p, int** m, int** s);
void DataInput();
void Traceback(int** s, int i, int j);
int main()
{
cout << "【动态规划】矩阵连乘问题:\n\n";
// 输入
DataInput();
// 自底向上的计算数乘次数
MatrixChain(n, p, m, s);
// 输出
cout << "最优解为:" << m[1][n] << endl;
Traceback(s, 1, n);
// 删除指针
delete[] p;
for (int i = 0;i < n + 1;i++) {
delete[] m[i];
delete[] s[i];
}
delete[] m;
delete[] s;
cout << endl;
return 0;
}
void MatrixChain(int n, int* p, int** m, int** s)
{
// r代表子链长度
for (int r = 2;r <= n;r++) {
// i从1开始,一直到n-r+1,
// i表示每一个区间长度为r的区间左端点,
// i=n-r+1是最后一左个端点,此时区间表示为[n-r+1,n],长度为r
for (int i = 1;i <= n - r + 1;i++) {
// j表示区间右端点
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 DataInput()
{
cout << "请输入矩阵个数:";
cin >> n;
p = new int[n + 1]; // 矩阵的维数数
m = new int* [n + 1]; // 子链的数乘次数,初始值为0
for (int i = 0;i < n + 1;i++) {
m[i] = new int[n + 1];
for (int j = 0;j < n + 1;j++)
m[i][j] = 0;
}
s = new int* [n + 1]; // 子链的断开位置k
for (int i = 0;i < n + 1;i++)
s[i] = new int[n + 1];
cout << "请输入矩阵各维数值:\n";
for (int i = 0;i < n + 1;i++)
cin >> p[i];
}
void Traceback(int** s, int i, int j)
{
if (i == j) {
cout << "A" << i;
return;
}
cout << "(";
Traceback(s, i, s[i][j]);
Traceback(s, s[i][j] + 1, j);
cout << ")";
}
2. 备忘录法 - 自顶向下
// 【备忘录法】矩阵连乘问题
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f; // 定义一个“无限大”的值,让每个求得的值与它比较
int n; // 矩阵个数
int* p; // 矩阵的维数
int** m; // 子链的数乘次数,初始值为0
int** s; // 子链的断开位置k
int MatrixChain(int i, int j);
void DataInput();
void Traceback(int i, int j);
int main()
{
cout << "【备忘录法】矩阵连乘问题:\n\n";
// 输入
DataInput();
// 自顶向下的计算数乘次数
int sum = MatrixChain(1, n);
// 输出
cout << "最优解为:" << sum << endl;
Traceback(1, n);
cout << endl;
return 0;
}
int MatrixChain(int i, int j)
{
if (i == j)
return 0;
if (m[i][j] != -1)
return m[i][j];
int low = INF;
for (int k = i;k < j;k++) {
int t = MatrixChain(i, k) + MatrixChain(k + 1, j) + p[i - 1] * p[k] * p[j];
if (t < low) {
low = t;
s[i][j] = k;
}
m[i][j] = low;
}
return low;
}
void DataInput()
{
cout << "请输入矩阵个数:";
cin >> n;
p = new int[n + 1]; // 矩阵的维数数
m = new int* [n + 1]; // 子链的数乘次数,初始值为-1
for (int i = 0;i < n + 1;i++) {
m[i] = new int[n + 1];
for (int j = 0;j < n + 1;j++)
m[i][j] = -1;
}
s = new int* [n + 1]; // 子链的断开位置k
for (int i = 0;i < n + 1;i++)
s[i] = new int[n + 1];
cout << "请输入矩阵各维数值:\n";
for (int i = 0;i < n + 1;i++)
cin >> p[i];
}
void Traceback(int i, int j)
{
if (i == j) {
cout << "A" << i;
return;
}
cout << "(";
Traceback(i, s[i][j]);
Traceback(s[i][j] + 1, j);
cout << ")";
}
3. 运行结果展示
·
三、友情链接~
- 其它一些常见算法请参阅此链接~
最后,非常欢迎大家来讨论指正哦!