动态规划——矩阵连乘问题
动态规划
动态规划问题与分治法类似,思路都是将大问题划分为小问题,求解可以求解的小问题并将其组合为原问题的解,二者都是自底向上计算(动态规划似乎也可以用具有记忆能力的递归自顶向下求解)。二者概念上十分相像,此时(初学)在我看来似乎并没有明显界限。
不同的是,由于分治法的子问题之间没有重复的部分,所以分治法的较大子问题完全由组成其的较小子问题合并得到,在实际求解过程中往往不需要额外的空间来记录非其组成部分的子问题。而动态规划的子问题之间有重复部分,子问题之间具有最优子结构性质(即子问题的最优解可以递推出更大的子问题的最优解)并且无后效性(参考货郎担问题),由于递推关系不一定都是父子问题之间的,因此需要记录求解过程中所有(或者递推需要的)子问题的解,按照递推公式求解出原问题的解。
——阶段性组合优化问题
——具有最优子结构性质
——无后效性
矩阵连乘问题
问题:
给定n个矩阵{A1 , A2 , … , An},其中,Ai与Ai+1是可乘的,i=1, 2, … , n-1。确定矩阵乘法顺序,使得元素相乘的次数最少。
分析:
蛮力法,遍历所有可能的计算次序,复杂度达到指数级
假设从i到j进行矩阵连乘,从k处断开,那么其复杂度等于从i到k+从k+1到j+二者计算出的结果相乘,考虑用动态规划求解。
——分析最优子结构:计算A[i:j]的最优次序所包含的计算矩阵子链 A[i:k]和A[k+1:j]的次序也是最优的(反证),即问题具有最优子结构。
——建立递推关系:设计算A[i:j],1≤i≤j≤n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]。递推方程为:m[i,j] = min_i<=k<j_{m[i,k]+m[k+1,j]+p_i-1_p_k_p_j_},其中pk为第k个矩阵的列。
——计算最优值:如果按照递推公式进行递归计算,会导致大量重复的子问题(指数级);因此可以采用迭代的方式,从最小的子问题开始遍历所有的子问题并记录,最小子问题为m矩阵对角线上的值,根据递推公式,更新操作按照对角线逐线更新。此时时间复杂度降低(n3级)。假如需要记录计算的顺序,则需要一个额外的矩阵s,s[i,[j]记录了从i到j的最优断开位置。
——求最优解:根据矩阵s递归traceback即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f
int p[MAXN],m[MAXN][MAXN];
// 迭代法计算矩阵连乘
// 下标从1开始方便计算
// 输入:矩阵数量n
void matrixChain(int n){
// 计算最小子问题,即初始化递推矩阵的起始部分
for(int i = 1;i <= n;i++) m[i][i] = 0;
// 递推地计算各个子问题,同时更新s
for(int r = 1;r <= n-1; r++){// r代表第几条斜对角线
for(int i = 1;i <= n-r;i++){// i代表行数
int j = i+r;// j代表列数
m[i][j] = INF;
for(int k = i;k < j;k++){
int tmp = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(tmp < m[i][j]){
m[i][j] = tmp;
}
}
}
}
}
int main(){
int n = 6;
p[0] = 30;
p[1] = 35;
p[2] = 15;
p[3] = 5;
p[4] = 10;
p[5] = 20;
p[6] = 25;
matrixChain(n);
cout<<m[1][6]<<endl;
}