笔记
给定两个n×n正方矩阵A和B,这两个矩阵的乘法定义为
其中
下面是矩阵乘法的伪代码。
很显然,执行SQUARE-MATRIX-MULTIPLY需要花费Θ(n3)时间。然而,有一种方法可以花费更少的时间,这就是Strassen算法,它本质上也是一种分治法,它的时间复杂度为Θ(nlg7)=O(n2.81)。
在介绍Strassen算法之前,我们先尝试简单的分治法来计算矩阵乘法C=A•B。假定三个矩阵均为n×n正方矩阵。并且为简化分析,假定n为2的幂。我们将A、B和C均分解为4个(n/2)×(n/2)的子矩阵。
于是矩阵乘法可以表示为
上面的矩阵乘法等价于下面4个式子。
上面每个式子都对应2次(n/2)×(n/2)矩阵乘法,以及1次(n/2)×(n/2)矩阵加法。根据以上分析,可以给出一个递归的分治算法。
现在分析这个简单分治法的时间复杂度。调用SQUARE-MATRIX-MULTIPLY-RECURSIVE计算两个n×n矩阵乘法的运行时间用T(n)表示。对于n=1的初始情况,我们只需计算一次标量乘法,因此T(1)=Θ(1)。当n>1时,根据上面的伪代码,T(n)包含8次(n/2)×(n/2)矩阵乘法的时间和4次(n/2)×(n/2)矩阵加法的时间,所以T(n)=8T(n/2)+Θ(n2),这里忽略了分解子矩阵的时间。于是,我们得到SQUARE-MATRIX-MULTIPLY-RECURSIVE的运行时间的递时式为
求解这个递归式得到T(n)=Θ(n3)。可以看到,这个简单的分治法并没有带来渐近运行时间的提升。
下面介绍Strassen算法。Strassen算法同样要将每个矩阵分解为4个(n/2)×(n/2)子矩阵。而与简单分治法不同,Strassen算法只需要递归为7次,而不是8次。下面直接给出Strassen算法的流程。
(1) 将输入矩阵A、B以及输出矩阵C各分解为4个(n/2)×(n/2)子矩阵。
(2) 创建10个(n/2)×(n/2)矩阵S1,S2,…,S10,如下所示。由于需要进行10次(n/2)×(n/2)矩阵的加减法,所以这一步花费Θ(n2)时间。
(3) 用步骤(1)分解得到的子矩阵和步骤(2)中创建的10个矩阵,递归地计算7个矩阵乘积P1,P2,…,P7,如下所示。
(4) 利用矩阵P1,P2,…,P7进行加减运算,得到输出矩阵C的子矩阵C11,C12,C21,C22,如下所示。这一步需要进行8次(n/2)×(n/2)矩阵的加减法,所以花费时为Θ(n2)。
由于Strassen算法只需要递归为7次,因此它的运行时间的递归式为
求解这个递归式,可以得到Strassen算法的运行时间T(n)=Θ(nlg7)。
练习
4.2-1 使用Strassen算法计算如下矩阵乘法:
给出计算过程。
解
(1) 分解输入矩阵
(2) 计算矩阵S1,S2,…,S10
(3) 计算矩阵P1,P2,…,P7
(4) 计算输出矩阵的4个子矩阵
最终结果为
4.2-2 为Strassen算法编写伪代码。
解
这里还是假设了矩阵的宽高n为2的幂。下面给出伪代码。
4.2-3 如何修改Strassen算法,使之适应矩阵规模n不是2的幂的情况?证明:算法的运行时间为Θ(nlg7)。
解
为了保证算法的通用性,需要考虑矩阵的宽高n不为2的幂的情况。分两种情况讨论。
(1) n为偶数
这种情况下n×n矩阵可以直接分解为4个(n/2)×(n/2)的子矩阵,因此可以直接应用Strassen算法。为了计算矩阵乘法Cn×n=An×n•Bn×n,令m=n/2,需要将矩阵分解为
这种情况下,矩阵乘法所花费的时间T(n)=7T(n/2)+Θ(n2)。
(2) n为奇数
这种情况不能直接应用Strassen算法。为了计算矩阵乘法Cn×n=An×n•Bn×n,令m=n−1,将矩阵做如下分解
如上所示,每个n×n矩阵被分解为一个(n−1)×(n−1)矩阵、一个(n−1)×1矩阵、一个1×(n−1)矩阵和一个1×1矩阵。相应地,矩阵乘法Cn×n=An×n•Bn×n可以分解为下面4个式子。
上面4个式子包含了8个不同规模的矩阵乘法,下面逐个进行分析。
1) A11m×m•B11m×m:由于m=n−1是偶数,所以这个矩阵乘法可以直接应用Strassen算法。
这一矩阵乘法所花费的时间为T(n−1)=7T((n−1)/2)+Θ((n−1)2)=7T(⌊n/2⌋)+Θ(n2)。
2) A12m×1•B211×m:采用朴素算法,需要做(n−1)2次乘法,因此运行时间为Θ(n2)。
3) A11m×m•B12m×1:采用朴素算法,需要做(n−1)2次乘法和(n−1)(n−2)次加法,因此运行时间也为Θ(n2)。
4) A12m×1•B221×1:采用朴素算法,需要做(n−1)次乘法,运行时间为Θ(n)。
5) A211×m•B11m×m:采用朴素算法,需要做(n−1)2次乘法,以及(n−1)(n−2)次加法,运行时间为Θ(n2)。
6) A221×1•B211×m:采用朴素算法,需要做(n−1)次乘法,运行时间为Θ(n)。
7) A211×m•B12m×1:采用朴素算法,需要做(n−1)次乘法,以及(n−2)次加法,运行时间为Θ(n)。
8) A221×1•B221×1:这仅仅是两个元素的相乘,只花费Θ(1)时间。
根据以上分析,除去A11m×m•B11m×m之外,其他7个矩阵乘法加起来的运行时间为Θ(n2)。因此,当n为奇数时,n×n矩阵乘法的运行时间为
T(n)=7T(⌊n/2⌋)+Θ(n2)
综合以上两种情况,无论n为奇数还是偶数,矩阵乘法的运行时间都为T(n)=7T(⌊n/2⌋)+Θ(n2)。忽略其中的⌊ ⌋符号,这与之前分析的Strassen算法的运行时间是一样的。
下面给出具备通用性的Strassen算法的伪代码。
4.2-4 如果可以用k次乘法操作(假定乘法的交换律不成立)完成两个3×3矩阵相乘,那么你可以在o(nlg7)时间内完成n×n矩阵相乘,满足这一条件的最大k是多少?此算法的运行时间是怎样的?
解
仍然采用Strassen算法。我们现在分析该算法运行时间的递归式,不过在这里需要以T(3)作为边界条件,递归式如下所示。
如果我们画出递归树,该递归树一共有lg(n/3)层。叶结点对应子问题T(3)。由于每层的结点数是上一层的7倍,因此第i层包含7i个结点。因此,叶结点一共有7lg(n/3)=7lgn−lg3=7lgn/7lg3=nlg7/7lg3个。因此所有叶结点的代价之和为(nlg7/7lg3)•T(3)=k•(nlg7/7lg3)。
如果要在o(nlg7)时间内完成n×n矩阵相乘,那么必然有k•(nlg7/7lg3)<nlg7,于是得到k<7lg3≈21.85。所以k的最大值为21。
4.2-5 V.Pan发现一种方法,可以用132464次乘法操作完成68×68的矩阵相乘,发现另一种方法,可以用143640次乘法操作完成70×70的矩阵相乘,还发现一种方法,可以用155424次乘法操作完成72×72的矩阵相乘。当用于矩阵相乘的分治算法时,上述哪种方法会得到最佳的渐近运行时间?与Strassen算法相比,性能如何?
解
对于采用分治法的矩阵乘法算法来说,其运行时间都为Θ(nd),其中d为一个正常数。现在分析题目所给的3种方法,其渐近运行时间中的d分别为多少。为方便起见,假设3种方法的运行时间分别为T1(n)=nd1,T2(n)=nd2和T3(n)=nd3。
用132464次乘法操作完成68×68的矩阵相乘,于是有
T1(68)=68d1=132464
得到d1=log68132464≈2.795128。
用143640次乘法操作完成70×70的矩阵相乘,于是有
T2(70)=70d2=143640
得到d2=log70143640≈2.795122。
用155424次乘法操作完成72×72的矩阵相乘,于是有
T3(72)=72d3=155424
得到d3=log72155424≈2.795147。
根据以上分析,第(2)种方法的渐近运行时间的指数d2是最小的,所以第(2)种方法会得到最佳的渐近运行时间。
Strassen算法的渐近运行时间为Θ(nlg7),lg7≈2.807355>d2,因此上述第(2)种方法的性能是优于Strassen算法的。
4.2-6 用Strassen算法作为子过程来进行一个kn×n矩阵和一个n×kn矩阵相乘,最快需要花费多长时间?对两个输入矩阵规模互换的情况,回答相同的问题。
解
两个矩阵Akn×n和Bn×kn相乘,得到矩阵Ckn×kn。如果要利用Strassen算法,则需要将矩阵A、B和C按下面的方式分解
矩阵C的任意一个子矩阵Cij=Ai•Bj, 这是一个n×n矩阵乘法,采用Strassen算法,运行时间为Θ(nlg7)。一共有k2个这样的n×n矩阵乘法,所以总的运行时间为Θ(k2•nlg7)。
如果将输入矩阵的规模互换,即矩阵An×kn和Bkn×n相乘,得到矩阵Cn×n,那么需要将矩阵A和B按下面的方式分解
矩阵C=A1•B1+A2•B2+…+Ak•Bk。一共有k个n×n矩阵乘法,并且还有(k−1)个n×n矩阵加法,所以总的运行时间为Θ(k•nlg7)。
4.2-7 设计算法,仅使用三次实数乘法即可完成复数a+bi和c+di相乘。算法需接收a、b、c和d为输入,分别生成实部ac−bd和虚部ad+bc。
解
借鉴Strassen算法的思想,该问题可以按以下步骤解决。
(1) 计算P1、P2和P3
P1=ad
P2=bc
P3=(a–b)(c+d)=ac–bd+ad–bc
(2) 计算实部和虚部
实部:P3–P1+P2=ac−bd
虚部:P1+P2=ad+bc
该算法只需要3次乘法即可。
本节代码链接:
https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter04/Section_4.2