大家好,我是连人。本期我们分享矩阵连乘问题。
在矩阵乘法中,不同的加括号方式的计算顺序可能会有不同的计算次数。
例如A:10×100,B:100×5,C:5×50
以(AB)C计算时,需要10×100×5+10×5×50=7500
以A(BC)计算时,需要100×5×50+10×100×50=75000
显然第一种计算方式计算次数更少,矩阵连乘问题就是求出最佳匹配的顺序。
我们将矩阵的维数记录到数组p中。为了节省空间,且根据矩阵乘法的规则,我们将前矩阵的列数和后矩阵的行数合并,例如上例的A,B,C,记录到p中就变成了:
p:[10, 100, 5, 50]
p[0],p[1]即为A矩阵的维,p[1],p[2]为B矩阵的维。依此类推。
此外,我们再创建两个二维数组m和s,行号为i,列号为j,m[ i ][ j ]是指从 i 到 j 的最小乘次,s[ i ][ j ]记录这次乘法从哪里断开。
由于j一定要大于i,所以两个数组都使用右上方。本次算法的主要思想是由小区域直接被大区域查找并决定最后采用何处断开才能获取最小乘次。因此方向是由小区域到大区域,从做下到右上。
下面进入正题,我将课本上的例子掏出来,大家也好理解。
A1 | A2 | A3 | A4 | A5 | A6 |
---|---|---|---|---|---|
30×35 | 35×15 | 15×5 | 5×10 | 10×20 | 20×25 |
数组p = [30, 35, 15, 5, 10, 20, 25]
下图是数组m和数组s的计算次序。是由底层到高层的。
为了保证每次是在同一条对角线上,我们引入r作为第一个循环参数,跑动在每一条对角线上的 i 理所应当的成为第二个循环参数。
下表是r, i, j的关系。
r | i | j |
---|---|---|
1 | 1 | 2 |
1 | 2 | 3 |
1 | 3 | 4 |
… | … | … |
4 | 1 | 5 |
4 | 2 | 6 |
5 | 1 | 6 |
按照规律大家应该知道外侧循环该怎么写了8.
接下来介绍m[ i ] [ j ]的计算方法:
若i = j,m[ i ][ j ] = 0;
在其他情况下,取k∈[i, j]循环,取
m[ i ][ k ] + m[ k + 1][ j ] + pi-1*pk*pj的最小值
这个式子的意思是,在当前区域内每个断点都断开一次,计算每种情况取最小值。同时在s[ i ][ j ]中记录在何处断开。
举个例子,要计算A2A3A4A5的最小乘次,即m[2][5]。
A2(A3A4A5) :m[2][2]+m[3][5]+p1p2p5 = 13000
(A2A3)(A4A5):m[2][3]+m[4][5]+p1p3p5 = 7125
(A2A3A4)A5 :m[2][4]+m[5][5]+p1p4p5 = 11375
得知最小值7125,记录到m[2][5]中,同时断点为3,s[2][5] = 3。
在这之中,k成为了第3个循环参数,所以该算法的时间复杂度为o(n3)
接下来是代码部分:
def matrix_chain(p, n, m, s):
for r in range(1, n): # r是作为标记当前对角线方向的线i和j之间的关系
for i in range(1, n-r+1): # i是行号,在每轮对角线中,i所能触及的最大的行号依次递减
j = i + r # j是列号,由i和r共同确定
m[i][j] = m[i+1][j] + p[i-1]*p[i]*p[j] # m[i][j]初始值是自身×之后的结果
s[i][j] = i # s[i][j]初始值是从自身断开
for k in range(i, j): # k在i和j之间循环,找到最小值记录
t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]
if t < m[i][j]:
m[i][j] = t
s[i][j] = k
def traceback(i, j, s):
if i == j:
print(i, end='')
return
print('(', end='')
traceback(i, s[i][j], s)
traceback(s[i][j]+1, j, s)
print(')', end='')
if __name__ == '__main__':
p = [30, 35, 15, 5, 10, 20, 25]
m = []
s = []
for i in range(0, 7):
m.append([])
s.append([])
for j in range(0, 7):
m[i].append(0)
s[i].append(0)
matrix_chain(p, 6, m, s)
traceback(1, 6, s)
运行结果:
转载注明出处。