注:本文爲《算法導論》中分治相關內容的筆記。對此感興趣的讀者還望支持原作者。
矩陣乘法
接觸過線性代數的讀者,對於矩陣乘法想必一定不陌生。若A=(aij)和B=(bij)是n∗n的方陣,則對i,j,…,n,定義乘積C=A⋅B中的元素cij爲:
cij=k=1∑naikbkj
因此,我們可以根據矩陣乘法的定義給出矩陣乘法的僞代碼。它接收n∗n的矩陣A和B,返回它們的乘積——n∗n的矩陣C,並且假設每個矩陣都有一個屬性rows,表示矩陣的行數。
![樸素算法]()
不難看出,由於三重for循環都恰好執行n步,而第7行每次執行都花費常量時間。因此,SQUARE-MATRIX-MULTIPLY的時間複雜度爲θ(n3),即矩陣乘法的樸素實現需要花費θ(n3)時間。你可能因此認爲任何矩陣乘法都要花費Ω(n3)時間,因爲矩陣乘法的自然定義就需要進行這麼多次的標量乘法。而在學術界,也的確在很長一段時間內,很少人敢設想一個算法能漸近快於平凡算法SQUARE-MATRIX-MULTIPLY,直至Strassen大神的出現。
算法流程
Strassen算法採用分治法解決矩陣乘積問題,並通過排列組合的技巧使得分治法產生的遞歸樹不那麼“茂盛”以減少矩陣乘法的次數。Strassen算法並不直觀,它包含4個步驟:
-
將輸入矩陣A、B和輸出矩陣C通過以下方式分解爲2n∗2n的子矩陣;
A=[A11A21A12A22],B=[B11B21B12B22],C=[C11C21C12C22]
-
創建10個2n∗2n的矩陣S1,S2,…,S10,每個矩陣保存步驟1中創建的兩個子矩陣的和或差,時間複雜度爲Θ(n2);
-
用步驟1中創建的子矩陣和步驟2中創建的10個矩陣,遞歸地計算7個矩陣積P1,P2,…,P7。每個矩陣Pi都是2n∗2n的;
-
通過Pi矩陣的不同組合進行加減計算,計算出矩陣C的子矩陣C11,C12,C21,C22,時間複雜度爲Θ(n2)。
是不是感覺很抽象?一頓猛如虎的操作,就能完成矩陣乘積計算了?沒錯,就是這麼秀。接下來,爲了幫助大家掌握這種操作,就再看看Strassen算法的細節。在步驟2中,創建如下10個矩陣:
S1=B12−B22
S2=A11+A12
S3=A21+A22
S4=B21−B11
S5=A11+A22
S6=B11+B22
S7=A12−A22
S8=B21+B22
S9=A11−A21
S10=B11+B22
由於必須進行10次2n∗2n的加減法,因此,該步驟花費Θ(n2)。
在步驟三中,遞歸地計算7次2n∗2n矩陣的乘法,如下所示:
P1=A11⋅S1=A11⋅B12−A11⋅B22
P2=S2⋅B22=A11⋅B22+A12⋅B22
P3=S3⋅B11=A21⋅B11+A22⋅B11
P4=A22⋅S4=A22⋅B21−A22⋅B11
P5=S5⋅S6=A11⋅B11+A11⋅B22+A22⋅B11+A22⋅B22
P6=S7⋅S8=A12⋅B21+A12⋅B22−A22⋅B21−A22⋅B22
P7=S9⋅S10=A11⋅B11+A11⋅B12−A21⋅B11−A21⋅B12
步驟4對步驟3創建的Pi矩陣進行加減法運算,計算出C的4個2n∗2n的子矩陣。
C11=P5+P4−P2+P6=A11⋅B11+A12⋅B21
C12=P1+P2=A11⋅B12+A12⋅B22
C21=P3+P4=A21⋅B11+A22⋅B21
C22=P5+P1−P3−P7=A22⋅B22+A21⋅B12
如此,我們便獲得矩陣A和B的乘積——矩陣C。
算法分析
之前說過,Strassen算法的時間複雜度是優於樸素計算的,可是,它到底是多少呢?我們不妨再回到Strassen算法的流程。當n>1時,步驟1、2和4共花費θ(n2)時間,步驟3要求7次2n∗2n矩陣的乘法。因此,我們得到如下描述Strassen算法運行時間T(n)的遞歸式:
T(n)={θ(1)7T(n/2)+θ(n2)若n=1若n>1
求解上式可得,T(n)=θ(nlg7)。
算法實現
廢話千句,不如代碼兩行,接下來直接上Strassen算法的實現。(注意,如果n不是2的冪,可以採取對原矩陣填充0的方式,使n擴展到2的冪)。
![Strassen算法]()
算法總結
Strassen算法發表於1969年,它的發表引起了很大的轟動。在此之前,很少人敢設想一個算法能漸近快於平凡算法SQUARE-MATRIX-MULTIPLY。矩陣乘法的上界自此被改進了。到目前爲止,n∗n矩陣相乘的漸近複雜性最優的算法是Coppersmith和Winograd提出的,運行時間是O(n2.376)。