动态规划基本思想:将待求解问题分解成若干个不独立的子问题 求出子问题的解并记录其表中 当需要再次求解次子问题时 可以通过查表获得该子问题的解而不用再次求解 从而避免大量重复计算 最后这些子问题即可得到原问题的解
动态规划法的解题思路:
①分析问题的最优子结构性质:一个最优策略的子策略总最优
②建立递归关系
③自下而上计算最优值
④根据计算最优值时得到的信息,构造最优解
下面给出矩阵连乘问题:
给定n个矩阵{A
1
_ {1}
1,A
2
_ {2}
2,A
3
_ {3}
3…A
n
_ {n}
n}其中A
i
_ {i}
i与A
i
+
1
_ {i+1}
i+1是可乘的(i=1,2…n-1),由于矩阵乘法满足结合律,所以矩阵连乘有不同的运算次序,矩阵连乘问题就是确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵的数乘次数最少。输入数据为矩阵的个数和每个矩阵的规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数
将矩阵连乘A
i
_ {i}
iA
i
+
1
_ {i+1}
i+1…A
j
_ {j}
j简记为A[i:j](i≤j) A
i
_ {i}
i维度记为p
i
−
1
_ {i-1}
i−1*p
i
_ {i}
i
如:给定三个矩阵A
1
_ {1}
1、A
2
_ {2}
2、A
3
_ {3}
3
易知有两种运算次序:(A
1
_ {1}
1*A
2
_ {2}
2)*A
3
_ {3}
3和A
1
_ {1}
1*(A
2
_ {2}
2*A
3
_ {3}
3)
第一种次序运算次数为(2:3 3:2)2:4=2*3*2=12→(2:2 2:4)=2*2*4=16 总次数为12+16=28
第二种次序运算次数为2:3(3:2 2:4)=3*2*4=24→(2:3 3:4)=2*3*4=24 总次数为24+24=48
▲分析:若存在一个计算A[1:k]的次序所需的计算量(相对于之前求得的最优解)更少 则取代之 类似地,计算A[k+1:n]的次序也一定是最优的。由此可知矩阵连乘计算次序问题的最优解包含了其子问题的最优解。
那么计算目的是求解A[1:n]的最优解,而一个最优策略的子策略也应是最优的,所以问题可分解为求A[i:j](1≤i≤j≤n)的最优计算次序。
计算A[i:j]的最优计算次序:设这个计算次序在矩阵A
k
_ {k}
k和A
k
+
1
_ {k+1}
k+1
之间将矩阵链断开(i≤k<j) 则其相应加括号形式为:(A
i
_ {i}
iA
i
+
1
_ {i+1}
i+1…A
k
_ {k}
k)(A
k
+
1
_ {k+1}
k+1A
k
+
2
_ {k+2}
k+2…A
j
_ {j}
j) 那么A[i:j]的计算量为A[i:k]的计算量加上A[k+1:j]的计算量再加上A[i:k]和A[k+1:j]相乘的计算量。
建立递归关系:设计算A[i:j](1≤i≤j≤n)所需要的最少乘次数dp[i][j] 则原问题的最优值为dp[1][n]
当i=j时(矩阵只有一个),A[i:j]=A
i
_ {i}
i 因此dp[i][i]=0
当i<j时 A
i
_ {i}
i的维数为p
i
−
1
_ {i-1}
i−1*p
i
_ {i}
i
由此可得递归关系:
当i=j时 dp[i][j]=0
当i<j时 dp[i][j]=min
i
≤
k
<
j
_ {i≤k<j}
i≤k<j (dp[i][k]+dp[k+1][j]+p
i
−
1
_ {i-1}
i−1*p
k
_ {k}
k*p
j
_ {j}
j)
#include<bits/stdc++.h>
using namespace std;
const long long MAX=1e10+2;
const int N=102;
int p[N];//保存各矩阵的维数
long long s[N][N],dp[N][N];//s存储切割位置 dp存储最优值
void MChain(int n){
for(int i=0;i<n+1;i++)//赋初值
for(int j=0;j<n+1;j++){
dp[i][j]=MAX;
s[i][j]=0;
}
for(int i=0;i<=n;i++){
cin>>p[i];
dp[i][i]=0;//只有一个矩阵时最优值为0
}
for(int L=2;L<=n;L++){//相乘矩阵的个数
for(int i=1;i<=n;i++){
int j=i+L-1;
if(j>n) break; //越界
for(int k=i;k<j;k++){//遍历切割位置
int MIN=dp[i][j];
int temp=dp[i][k]+dp[k+1][j]+p[i-1]*p[k]*p[j];
if(temp<MIN){
dp[i][j]=temp;
MIN=temp;
s[i][j]=k;
}
//dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+p[i-1]*p[k]*p[j]);
}
}
}
}
void Traceback(int i,int j){
if(i==j) return;
int k=s[i][j];
Traceback(i,k);//左半部分
Traceback(k+1,j);//右半部分
cout<<"A["<<i<<":"<<k<<"]*A["<<k+1<<":"<<j<<"]"<<endl;
}
int main(int argc, char** argv)
{
int n;
cin>>n;
MChain(n);
//输出dp表
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
//输出s表
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++)
cout<<s[i][j]<<" ";
cout<<endl;
}
cout<<"Min times:"<<dp[1][n]<<endl;
Traceback(1,n);
return 0;
}
时间复杂度分析:MChain函数包含三重循环,循环体内计算量O(1) 故该算法时间复杂度为O(
n
3
n^3
n3),空间复杂度O(
n
2
n^2
n2)。所以动态规划法远比穷举法有效。