筆記
給定兩個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