矩阵链加括号方式总数
- 前言
矩阵链乘积的瓶颈在于其标量运算的次数,不同的结合次序对其时间性能影响远大于矩阵乘积运算本身,可以看到许多教材上把求解矩阵标量运算的最优解作为动态规划的示例,问题隐含动态规划两大特征: 最优子结构及重叠子问题。诸多教材上对此作了详细的描述和解释,此问题本文不再做过多讨论。
本文旨在讨论给定某个矩阵,讨论其不同加括号的方式,要求求出所有可能加括号的数量,并就此问题引出Catalan Number的一般概念。
- 问题描述
给定矩阵<M1,M2,…Mn>,探索其可能加括号的方式。为了解决此问题,从最简单形式开始逐步进行研究其解的形式,假定只有一个矩阵M1, 那么其加括号的方式为1。同理,给定两个矩阵,显而易见,其加括号方式总数也为1。那么对于n个矩阵,那么其加括号的总数为几多呢?
为了探讨此一般解问题,假定第 k个矩阵把n个矩阵分两部分,表示第一部分矩阵为<M1,M2,…Mk>,表示第二部分矩阵为<Mk+1,M2,…Mn>。规定P(n)代表n个矩阵所有可能加括号方式的综合,可采用下列递归方程式表示其值,
P
(
n
)
=
∑
k
=
1
n
−
1
P
(
k
)
∗
P
(
n
−
k
)
;
(
n
≥
2
)
P(n)=\sum_{k=1}^{n-1}{P(k)*P(n-k)} ;\ (n\geq2)
P(n)=k=1∑n−1P(k)∗P(n−k); (n≥2)
很明显,问题纳入分治的范畴,它之和子问题的长度相关的乘积相关,矩阵本身对其没有影响,P(k)代表k个矩阵可能的加括号方式,P(n-k)代表n-k个矩阵加括号方式,P(k)*P(n-k)代表以k为分界的所有加括号的方式,而P(k)*P(n-k)对于所有的k的方式求和,便是P(n)的值。
- 暴力递归方案(无记忆递归)
上述表达式为经典的递归求和方式,可以利用暴力求解途径,对每个n和k分割进行求解,最后求和即可得到最终的结果,它的时间复杂度与求解Catalan number相同(Program for nth Catalan Number - GeeksforGeeks),采用暴力方法求解的时间复杂度为Ω(4n/n3/2),暴力解决方法不是理想求解问题的方式,下一篇幅中将引入动态规划的途径求解。
通过观察发现,n==1的情况下构成递归的基础解,函数直接返回1作为递归结束点。定义 sum为不同加括号的方式,它可以与上级栈的乘积和进行累加。
深入探索就会发现f(n-i)和f(i)递归函数存在可能的重合部分,这将导致每次递归都到出口点,对函数计算构成严重浪费的行为。
int find_matrix_complete_parenthesis_recursion(int n)
{
int i;
int sum;
if(n==1)
{
return 1;
}
sum=0;
for(i=1;i<n;i++)
{
sum += find_matrix_complete_parenthesis_recursion(n - i) * \
find_matrix_complete_parenthesis_recursion(i);
}
return sum;
}
- 动态规划方案
上节讨论展开过程中,发现求解过程存在诸多重复子问题,虽然求和过程未呈现显著的最优子问题特征,原因在于其行为是对不同问题进行求和,求和过程本来就无所谓的最优/最劣的过程,它关注的是加括号方式的不同类型的求和。
int find_matrix_complete_parenthesis_dp(int n)
{
int i;
int j;
int dp[n+1];
memset(dp,0,sizeof(dp));
dp[1]=1;
for(i=2;i<=n;i++)
{
for(j=1;j<i;j++)
{
dp[i]+=dp[j]*dp[i-j];
}
}
return dp[n];
}
- 总结
求解组合总和,一般不涉及到求解最大或最小值的操作,其过程汇总也不涉及到选择的代价,因为需要对所有的可能性选择进行求和汇总。
参考资料