本节的例子是求解矩阵链相乘问题的动态规划算法。
给定一个n个矩阵的序列(矩阵链)<A1,A2,...,An>,我们希望计算它们的乘积A1A2...An。
由于矩阵乘法满足结合律,因此任何加括号的方法都会得到相同的计算结果,我们称有如下性质的矩阵乘积链为完全括号话的。它是单一矩阵,或者是两个完全括号花的矩阵乘积链的积,且已外加括号。
例如,如果矩阵链为<A1,A2,A3,A4>,则共有5种完全括号化的矩阵乘积链。
对矩阵链加括号的方式会对乘积计算的代价产生巨大的影响。
矩阵链乘法问题可以描述如下:给定n个矩阵的链<A1,A2,...,An>,矩阵Ai的规模为求完全括号化方案,使得计算乘积A1A2,..An所需标量乘法次数最少。
我们的目标不是计算矩阵的乘积,而是确定代价最低的计算顺序。
假设p(n)表示可供选择的括号花方案的数量。当n>=2时,完全括号化的矩阵乘积可描述为两个完全括号化的部分积相乘的形式。而两个部分积的划分点在第k个矩阵和第k+1个矩阵之间。k为1,2,,,n-1中的任意一个值。
上面的递归公式的结果为
因此,括号化的方案的数量与n呈指数关系。通过暴力搜索穷尽所有可能的括号化的方案来寻找最优方案,是一个糟糕的策略!!!
应用动态规划方法
4个步骤:
1.刻画一个最优解的结构特征。
2.递归地定义最优解的值。
3.计算最优解的值,通常采用自底向上的方法。
4.利用计算出的信息构造一个最优解。
步骤一:动态规划的第一步是寻找最优子结构。然后就可以利用这种子结构从子问题的最优解构造出原问题的最优解。
步骤二:一个递归求解方案
下面用子问题的最优解来递归地定义原问题最优解的代价。
步骤三:计算最优代价
def matrix_chain_order(p):
n=len(p)-1
m=[[0 for j in range(0,n+1)] for i in range(0,n+1)]
s=[[0 for j in range(0,n+1)] for i in range(0,n+1)]
for l in range(2,n+1):
for i in range(1,n-l+2):
j=i+l-1
m[i][j]=float("inf")
for k in range(i,j):
q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]
if q<m[i][j]:
m[i][j]=q
s[i][j]=k
return m,s
def print_optimal_parens(s,i,j):
if i==j:
print("A",i,end='')
else:
print("(",end='')
print_optimal_parens(s,i,s[i][j])
print_optimal_parens(s,s[i][j]+1,j)
print(")",end='')
if __name__=='__main__':
p=[30,35,15,5,10,20,25]
m,s=matrix_chain_order(p)
print_optimal_parens(s,1,6)
运行:
>>>
RESTART: D:\Program Files\Python\test\algorithms\算法导论\36-matrix-chain-order.py
((A 1(A 2A 3))((A 4A 5)A 6))
>>> for i in range(1,7):
for j in range(1,7):
print(m[i][j],' ',end='')
print()
0 15750 7875 9375 11875 15125
0 0 2625 4375 7125 10500
0 0 0 750 2500 5375
0 0 0 0 1000 3500
0 0 0 0 0 5000
0 0 0 0 0 0
>>> for i in range(1,7):
for j in range(1,7):
print(s[i][j],' ',end='')
print()
0 1 1 3 3 3
0 0 2 3 3 3
0 0 0 3 3 3
0 0 0 0 4 5
0 0 0 0 0 5
0 0 0 0 0 0