# -*- coding: utf-8 -*-
'''
Python程序员面试算法宝典---解题总结: 第4章 数组 4.20 如何获取最好的矩阵链相乘方法
题目:
给定一个矩阵序列,找到最有效的方式将这些矩阵相乘在一起。给定表示矩阵链
的数组p,使得第i个矩阵Ai的维数为p[i-1]*p[i]。编写一个函数
MatrixChainOrder(),该函数应该返回乘法运算所需的最小乘数。
输入: p=(40, 20, 30, 10, 30)
输出: 26000
有4个大小为40 * 20, 20 * 30, 30 * 10和10 * 30的矩阵。
假设这四个矩阵为A, B, C, D。该函数的执行方法可以使得
执行乘法运算的次数最少。
_
分析:
问题的关键就是求矩阵运算的时候,括号放置的顺序问题
(AB)C=A(BC)
但是相乘的次数不同
假设A,B,C分别为
40 * 20, 20 * 30, 30 * 10的矩阵
那么
(AB)C中
(AB)相乘的次数=40 * 30 * 20 = 24000
此时矩阵(AB)变成了 40 * 30的矩阵
(AB)*C = 40 * 10 * 30 = 12000
另外要明白:
Am*n Bn*p 相乘的次数 = m * n * p
举例:
A是1*2的矩阵
[a b]
B是2*3的矩阵
[c d e]
[f g h]
A * B =
[ac+bf, ad+bg, ae+bh]
所以相乘次数=1*2*3=6
问题的关键就是如何设置矩阵相乘的括号来决定运算顺序。
令f[i]
关键:
1 书上解法
设m[i][j]表示矩阵链Ai到Aj的最小相乘次数
那么我们的目标就是求m[1][n],
假设数组p是矩阵的维度的下标列表
假设s[i][j]记录矩阵链Ai到Aj的最小相乘次数时的最优分割点k
则m[i][j] = m[i][k] + m[k + 1][j] + p[i-1] * p[k] * p[j]
m[i][i] = 0
每次调整矩阵链的长度l,从2到N
那么j=i + l - 1
然后计算k在i与j范围内的最小值m[i][j]
等到计算完成后,令i累加即可
2 我没想到
是因为忘记这种有起始范围的最优值,
那么设计的动态规划数组也必须是二维数组。
另外需要考虑矩阵链的长度从2到n变化
参考:
Python程序员面试算法宝典
'''
from __future__ import print_function
def multipltMatrix(p):
if not p:
return
size = len(p)
m = [[None]*(size + 1) for i in range(size + 1)]
s = [[None]*(size + 1) for i in range(size + 1)]
for i in range(size + 1):
m[i][i] = 0
# 遍历矩阵链的长度
n = size - 1
for l in range(2, n + 1):
# j = i + l - 1 <=n 所以 i <= n - l + 1
# 遍历起始点的下标
for i in range(1, n - l + 2):
j = i + l - 1
m[i][j] = float('inf')
# 寻找最佳分割点
for k in range(i, j):
value = m[i][k] + m[k + 1][j] + p[i-1] * p[k] * p[j]
if value < m[i][j]:
m[i][j] = value
s[i][j] = k
return m[1][n], s
def printResult(i, j, s):
if i ==j:
print("A{i}".format(i=i), end='')
else:
print("(", end='')
printResult(i, s[i][j], s)
printResult(s[i][j] + 1, j, s)
print(")", end='')
def process():
p = [40, 20, 30, 10, 30]
result, s = multipltMatrix(p)
print(result)
i = 1
j = len(p) - 1
printResult(i, j, s)
if __name__ == "__main__":
process()