矩阵乘法的顺序安排问题 Python简单实现
矩阵乘法的顺序安排问题
问题背景
设矩阵 A、B 大小分别 , ,则矩阵乘积 AB 需要做的标量乘法次数为 。我们知道矩阵的乘法运算是不可交换的,但它是可结合的。因此对于多个矩阵的连乘,我们可以以任意顺序添加括号改变其中相邻矩阵乘法的优先级。不同计算顺序下总的标量乘法运算次数是不同的,我们的目标是找到一个最优的矩阵乘法计算顺序。
给定矩阵乘法序列 ,将乘法序列以第 个矩阵分为前后两部分,则方案数为前后两部分方案数之积。因此乘法计算的顺序个数为
T(n) 的解为 Catalan数,这里不加证明给出结果为
由此可见的矩阵乘法顺序个数为问题规模 的指数级,显然通过枚举找到最优的乘法顺序是不合适的。
暴力算法
首先还是试探一下如何用最朴素的方式解决。
设 表示 第 个矩阵到第 个矩阵的最少乘法运算次数,用数学化的语言表达我们的目标,即
其中 p、q、r为最后两个矩阵的大小。
代码很容易实现:
def minMatrixMultiplication(Mats):
"""
:param Mats: Mat类型的list
:return: 矩阵乘法的最小乘法次数,及对应的括号位置
"""
if len(Mats)==1:
return 0, '[%d,%d]' % (Mats[0].n, Mats[0].m)
import math
minCost = math.inf
bestSeq = '' # 记录添加的括号位置
for i in range(0, len(Mats)-1):
leftCost, leftSeq = minMatrixMultiplication(Mats[:i+1])
rightCost, rightSeq = minMatrixMultiplication(Mats[i+1:])
tmpCost = leftCost + rightCost + Mats[0].n * Mats[i].m * Mats[-1].m
if tmpCost < minCost:
minCost = tmpCost
bestSeq = '(' + leftSeq + '*' + rightSeq + ')'
return minCost, bestSeq
测试用的矩阵类型Mat定义如下:
class Mat:
def __init__(self, mat=None):
if mat and isinstance(mat[0], list):
self.mat = mat
self.n = len(mat)
self.m = len(mat[0])
else:
self.mat = [[]]
self.n = 0
self.m = 0
def __init__(self, n, m):
self.mat = [[]]
self.n = n
self.m = m
以上算法的函数调用次数 ,
容易验证得到, 即该算法的复杂度为 O(),这是不可接受的。
记忆化
分析一番可以发现,对于矩阵序列 i~j 之间乘法的最优结果 只有 种,那么上述代码的中间很多段都进行了重复计算。如果把中间得到的答案记录下来,可以大大减少计算量。
在不改变上述算法的框架下,将 i~j 之间的结果 定义Python嵌套的内部函数。新增了变量 invokeCnt
统计递归函数需要重新计算 的次数。
def minMatrixMultiplication2(Mats):
siz = len(Mats) + 1
# 血的教训:不要使用下面的方法定义二维数组
# minCostMem = [[-1]*siz]*siz
# bestSeqMem = [['']*siz]*siz
minCostMem = [[-1]*siz for i in range(siz)]
bestSeqMem = [['']*siz for i in range(siz)]
invokeCnt = 0 # 统计递归函数重新执行次数
def helper(s, t):
if s==t:
return 0, '[%d,%d]' % (Mats[s].n, Mats[s].m)
if minCostMem[s][t]!=-1:
return minCostMem[s][t], bestSeqMem[s][t]
nonlocal invokeCnt
invokeCnt += 1
import math
minCost = math.inf
bestSeq = ''
for i in range(s, t):
leftCost, leftSeq = helper(s, i)
rightCost, rightSeq = helper(i+1, t)
tmpCost = leftCost + rightCost + Mats[s].n * Mats[i].m * Mats[t].m
if tmpCost < minCost:
minCost = tmpCost
bestSeq = '(' + leftSeq + '*' + rightSeq + ')'
minCostMem[s][t] = minCost
bestSeqMem[s][t] = bestSeq
return minCost, bestSeq
return helper(0, len(Mats)-1), invokeCnt
动态规划
(待补充。。。)
运行对比
Mats = [Mat(2,3), Mat(3,5), Mat(5,8), Mat(8,2), Mat(2,3), Mat(3,2), Mat(2,5), Mat(5, 3)]
print(minMatrixMultiplication(Mats))
# (184, '((([2,3]*([3,5]*([5,8]*[8,2])))*([2,3]*[3,2]))*([2,5]*[5,3]))')
# 调用次数 3^8 = 2187
print(minMatrixMultiplication2(Mats))
# ((184, '((([2,3]*([3,5]*([5,8]*[8,2])))*([2,3]*[3,2]))*([2,5]*[5,3]))'), 28)
注意事项
Python 定义二维矩阵,千万不要使用注释写法。调试了很久才发现问题。 T^T
正确的写法为
- matrix = [[0]*m for i in range(n)]
- 或使用numpy库
import numpy
matrix = numpy.zeros((n, m))
原因可以简单理解为
n = 5
m = 3
matrix = [[0]*m]*n
# 相当于
"""
array = [0 0 0]
matrix = [array]*5
# matrix内的5个元素都是同一个列表引用
# 当使用 matrix[3][2] = 1 赋值
# 则 array[2] = 1
# 所以 matrix[0~4][2]都为 1
"""
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构