问题描述
给定若干个矩阵,寻找最优的相乘次序,使得乘法运算的次数最少,并输出对应的最少运算次数。比如现有三个矩阵ABC,维数分别为A:2×10,B:10×2, C:2×10 。虽然(AB)C=A(BC) 结果是相等的,即与相乘次序没有关系。但是(AB)C乘法运算的次数为2×10×2+2×2×10=802×10×2+2×2×10=80,而A(BC)为10×2×10+2×10×10=40010×2×10+2×10×10=400,显然(AB)C的运算次数更少,即效率更高。
思路
求解的关键在于如何将问题分解为若干子问题。我们想象在各个矩阵之间可以放上隔板,那么只要先分别求解左和右的最少乘法次数,再将隔板左右的两部分相乘,就可以得到当前分隔方法的最优解,最后通过比较各种分隔方法,就能得到当前长度的最优解,如下图中四种分隔中我们取最少的次数,即为长度5(5个连续矩阵)的最优解。由于分隔后的长度必定小于当前长度,如此处的子问题长度必定小于5,而子问题已经在之前的迭代过程求得,无需重复计算。通过下一部分的迭代过程展示能有更直观的理解。
思路1:递归,把原数组从i到j-1做切分,计算切分之后两个子序列连乘的乘积之和和两个子序列总体的乘积
时间复杂度指数级,会有重复计算
代码如下:
#递归 def MatrixChainOrder(p,i,j): if i == j: return 0 mins = 2 ** 32 k = i while k < j: #count分成三份,从k做切分,[i,k]+[k+1,j]+总体 count = MatrixChainOrder(p,k+1,j) + MatrixChainOrder(p,i,k) + p[i-1] * p[k] * p[j] #p[i-1] * p[k] * p[j]是两个子序列连乘的乘积矩阵再相乘的计算量 print(i,k,j,count) if count < mins: mins = count k += 1 return mins
思路2:动态规划
自下而上的方式构造临时数组来保存子问题的中间结果,避免重复计算
时间复杂度O(N^3),空间复杂度O(N^2)
迭代过程:
为了更直观的理解,下图和上面是等效的。(符号说明:m[a,b]代表从序号为a到序号为b的矩阵链所需的最少乘法次数,特别地,m[a,a]代表a号矩阵本身,很明显m[a,a]=0。)
先计算第二行(m[1,2],m[2,3],m[3,4],m[4,5]),然后第三行(m[1,3],m[2,4],m[3,5]),....
算法实现
arr[]数组用于记录矩阵链信息,其中n号矩阵对应的维数是arr[n-1]*arr[n]。动态规划的核心算法利用3个for循环,最外层控制矩阵链长度,下一层控制起始点,再下一层控制隔板的位置。最后左右合并的时候注意下标
的选择,如下图所示。
代码如下:
#动态规划,自下而上 def MatrixChainOrder2(p,n): cost = [([None]*n) for i in range(n)] i = 1 while i < n: cost[i][i] = 0 i += 1 cLen = 2 #i=start,j=end,cLen=length while cLen < n: i = 1 while i < n-cLen+1: j = i + cLen - 1 print(i,j,cLen) cost[i][j] = 2 ** 31 k = i while k <= j-1: q = cost[i][k] + cost[k+1][j] + p[i-1]*p[k]*p[j] if q < cost[i][j]: cost[i][j] = q k += 1 i += 1 cLen += 1 # print(cost) return cost[1][n-1]
结果如下:
1 2 2
2 3 2
3 4 2
4 5 2
1 3 3
2 4 3
3 5 3
1 4 4
2 5 4
1 5 5
res 11875