我們知道,兩個 N 位數字的整數的乘法,如果使用常規的算法,時間復雜度是 O(N2)。然而,使用快速傅裡葉變換,時間復雜度可以降低到 O(N logN loglogN)。
假設我們要計算以下兩個 N 位數字的乘積:
a = (aN-1aN-2...a1a0)10 = aN-1x10N-1 + aN-2x10N-2 + ... + a1x101 + a0x100
b = (bN-1bN-2...b1b0)10 = bN-1x10N-1 + bN-2x10N-2 + ... + b1x101 + b0x100
將上面兩個式子相乘,得到以下公式 (共 2N - 1 項):
c = a x b = c2N-2x102N-2 + c2N-3x102N-3 + ... + c1x101 + c0x100
非常容易驗證,上式中的 ck ( 0 ≤ k ≤ 2N-2 ) 滿足以下公式:
ck = a0xbk + a1xbk-1 + ... + ak-2xb2 + ak-1xb1
+ akxb0 + ak+1xb-1 + ... + aN-2xb-(N-2-k) + aN-1xb-(N-1-k)
上式共有 N 項,ai 和 bj 的下標 i 和 j 滿足 i + j = k。若不滿足 0 ≤ i, j ≤ N-1 時,則令 ai = bj = 0。
我們以兩個 3 ( N = 3 ) 位數 a = 678 和 b = 432 的乘積來說明以上過程吧。
a = (678)10 = 6x102 + 7x101 + 8x100
b = (432)10 = 4x102 + 3x101 + 2x100
由此:
c0 = a0xb0 + a1xb-1 + a2xb-2 = 8x2 + 7x0 + 6x0 = 16 + 0 + 0 = 16
c1 = a0xb1 + a1xb0 + a2xb-1 = 8x3 + 7x2 + 6x0 = 24 + 14 + 0 = 38
c2 = a0xb2 + a1xb1 + a2xb0 = 8x4 + 7x3 +6x2 = 32 + 21 + 12 = 65
c3 = a0xb3 + a1xb2 + a2xb1 = 8x0 + 7x4 + 6x3 = 0 + 28 + 18 = 46
c4 = a0xb4 + a1xb3 + a2xb2 = 8x0 + 7x0 + 6x4 = 0 + 0 + 24 = 24
最後:
c = a x b = 104xc4 + 103xc3 + 102xc2 + 101xc1 + 100xc0
= 10000x24 + 1000x46 + 100x65 + 10x38 + 1x16
= 292896
如果按以上方法計算大整數的乘法,時間復雜度是 O(N2)。
但是,我們注意到,向量 {ck} 是向量 {ai} 和向量 {bj} 的卷積。根據卷積定理,向量卷積的離散傅裡葉變換是向量離散傅裡葉變換的乘積。於是,我們可以按照以下步驟來計算大整數乘法:
對於復數向量 { xN-1, ..., x1, x0 },離散傅裡葉變換公式為:
離散傅裡葉逆變換公式為:
注意到離散傅裡葉逆變換除了指數的符號相反以及結果需要乘以歸一化因子 1/N 外,與離散傅裡葉變換是相同的。所以計算離散傅裡葉變換的程序稍做修改也可以用於計算逆變換。
在我們的例子中,乘積 c = 292896,共 6 位數字,N 需要擴展到 23 = 8。那麼,向量 {ai} 和向量 {bj} 如下所示:
{ a7, a6, a5, a4, a3, a2, a1, a0 } = { 0, 0, 0, 0, 0, 6, 7, 8 }
{ b7, b6, b5, b4, b3, b2, b1, b0 } = { 0, 0, 0, 0, 0, 4, 3, 2 }
為了求出以上向量的離散傅裡葉變換,我們令
ω = e-2πi/N = e-2πi/8 = e-πi/4 = cos(-π/4) + i sin(-π/4) = √2 / 2 - i √2 / 2 ≈ 0.7-0.7i
為了方便計算,我們預先求出 ω 的各次方,如下:
注意到當 n > 2 時,an = 0,於是:
A0 = a0xω0x0 + a1xω1x0 + a2xω2x0 = 8xω0 + 7xω0 + 6xω0 = 8x1 + 7x1 + 6x1 = 21
A1 = a0xω0x1 + a1xω1x1 + a2xω2x1 = 8xω0 + 7xω1 + 6xω2 ≈ 8x1 + 7x(0.7 - 0.7i) + 6x(-i) = 12.9-10.9i
A2 = a0xω0x2 + a1xω1x2 + a2xω2x2 = 8xω0 + 7xω2 + 6xω4 = 8x1 + 7x(-i) + 6x(-1) = 2-7i
A3 = a0xω0x3 + a1xω1x3 + a2xω2x3 = 8xω0 + 7xω3 + 6xω6 ≈ 8x1 + 7x(-0.7 - 0.7i) + 6xi = 3.1+1.1i
A4 = a0xω0x4 + a1xω1x4 + a2xω2x4 = 8xω0 + 7xω4 + 6xω8 = 8x1 + 7x(-1) + 6x1 = 7
A5 = a0xω0x5 + a1xω1x5 + a2xω2x5 = 8xω0 + 7xω5 + 6xω10 ≈ 8x1 + 7x(-0.7 + 0.7i) + 6x(-i) = 3.1-1.1i
A6 = a0xω0x6 + a1xω1x6 + a2xω2x6 = 8xω0 + 7xω6 + 6xω12 = 8x1 + 7xi + 6x(-1) = 2+7i
A7 = a0xω0x7 + a1xω1x7 + a2xω2x7 = 8xω0 + 7xω7 + 6xω14 ≈ 8x1 + 7x(0.7 + 0.7i) + 6xi = 12.9+10.9i
同樣,當 n > 2 時,bn = 0,於是:
B0 = b0xω0x0 + b1xω1x0 + b2xω2x0 = 2xω0 + 3xω0 + 4xω0 = 2x1 + 3x1 + 4x1 = 9
B1 = b0xω0x1 + b1xω1x1 + b2xω2x1 = 2xω0 + 3xω1 + 4xω2 ≈ 2x1 + 3x(0.7 - 0.7i) + 4x(-i) = 4.1-6.1i
B2 = b0xω0x2 + b1xω1x2 + b2xω2x2 = 2xω0 + 3xω2 + 4xω4 = 2x1 + 3x(-i) + 4x(-1) = -2-3i
B3 = b0xω0x3 + b1xω1x3 + b2xω2x3 = 2xω0 + 3xω3 + 4xω6 ≈ 2x1 + 3x(-0.7 - 0.7i) + 4xi = -0.1+1.9i
B4 = b0xω0x4 + b1xω1x4 + b2xω2x4 = 2xω0 + 3xω4 + 4xω8 = 2x1 + 3x(-1) + 4x1 = 3
B5 = b0xω0x5 + b1xω1x5 + b2xω2x5 = 2xω0 + 3xω5 + 4xω10 ≈ 2x1 + 3x(-0.7 + 0.7i) + 4x(-i) = -0.1-1.9i
B6 = b0xω0x6 + b1xω1x6 + b2xω2x6 = 2xω0 + 3xω6 + 4xω12 = 2x1 + 3xi + 4x(-1) = -2+3i
B7 = b0xω0x7 + b1xω1x7 + b2xω2x7 = 2xω0 + 3xω7 + 4xω14 ≈ 2x1 + 3x(0.7 + 0.7i) + 4xi = 4.1+6.1i
這樣,向量 {ai} 和向量 {bj} 的離散傅裡葉變換 {Ai} 和 {Bj} 如下所示:
{ A7, A6, A5, A4, A3, A2, A1, A0 } = { 12.9+10.9i, 2+7i, 3.1-1.1i, 7, 3.1+1.1i, 2-7i, 12.9-10.9i, 21 }
{ B7, B6, B5, B4, B3, B2, B1, B0 } = { 4.1+6.1i, -2+3i, -0.1-1.9i, 3, -0.1+1.9i, -2-3i, 4.1-6.1i, 9 }
現在,將她們逐項相乘得到向量 {Ck},即 { C7, C6, C5, C4, C3, C2, C1, C0 }
= { -13.6+123.4i, -25-8i, -2.4-5.8i, 21, -2.4+5.8i, -25+8i, -13.6-123.4i, 189 }
為了求出向量 {Ck} 的離散傅裡葉逆變換,我們令
ω = e2πi/N = e2πi/8 = eπi/4 = cos(π/4) + i sin(π/4) = √2 / 2 + i √2 / 2 ≈ 0.7+0.7i
為了方便計算,我們預先求出 ω 的各次方(注意 ωk+8 = ωk),如下:
於是:
c0 = (1/N) x ( C0xω0x0 + C1xω1x0 + C2xω2x0 + C3xω3x0
+ C4xω4x0 + C5xω5x0 + C6xω6x0 + C7xω7x0 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω0 + (-25+8i)xω0 + (-2.4+5.8i)xω0
+ 21xω0 + (-2.4-5.8i)xω0 + (-25-8i)xω0 + (-13.6+123.4i)xω0 )
= 0.125 x ( 189x1 + (-13.6-123.4i)x1 + (-25+8i)x1 + (-2.4+5.8i)x1
+ 21x1 + (-2.4-5.8i)x1 + (-25-8i)x1 + (-13.6+123.4i)x1 )
= 0.125 x 128 = 16
c1 = (1/N) x ( 8xc1 = C0xω0x1 + C1xω1x1 + C2xω2x1 + C3xω3x1
+ C4xω4x1 + C5xω5x1 + C6xω6x1 + C7xω7x1 )
= (1/8) x ( 189xω0 + ( -13.6-123.4i)xω1 + (-25+8i)xω2 + (-2.4+5.8i)xω3
+ 21xω4 + (-2.4-5.8i)xω5 + (-25-8i)xω6 + (-13.6+123.4i)xω7 )
≈ 0.125 x ( 189x1 + (-13.6-123.4i)x(0.7+0.7i) + (-25+8i)x(i) + (-2.4+5.8i)x(-0.7+0.7i)
+ 21x(-1) + (-2.4-5.8i)x(-0.7-0.7i) + (-25-8i)x(-i) + (-13.6+123.4i)x(0.7-0.7i) )
= 0.125 x 300.96 = 37.62 ≈ 38
c2 = (1/N) x ( C0xω0x2 + C1xω1x2 + C2xω2x2 + C3xω3x2
+ C4xω4x2 + C5xω5x2 + C6xω6x2 + C7xω7x2 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω2 + (-25+8i)xω4 + (-2.4+5.8i)xω6
+ 21xω8 + (-2.4-5.8i)xω10 + (-25-8i)xω12 + (-13.6+123.4i)xω14 )
= 0.125 x ( 189x1 + (-13.6-123.4i)x(i) + (-25+8i)x(-1) + (-2.4+5.8i)x(-i)
+ 21x1 + (-2.4-5.8i)x(i) + (-25-8i)x(-1) + (-13.6+123.4i)x(-i) )
≈ 0.125 x 518.4 = 64.8 ≈ 65
c3 = (1/N) x ( C0xω0x3 + C1xω1x3 + C2xω2x3 + C3xω3x3
+ C4xω4x3 + C5xω5x3 + C6xω6x3 + C7xω7x3 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω3 + (-25+8i)xω6 + (-2.4+5.8i)xω9
+ 21xω12 + (-2.4-5.8i)xω15 + (-25-8i)xω18 + (-13.6+123.4i)xω21 )
≈ 0.125 x ( 189x1 + (-13.6-123.4i)x(-0.7+0.7i) + (-25+8i)x(-i) + (-2.4+5.8i)x(0.7+0.7i)
+ 21x(-1) + (-2.4-5.8i)x(0.7-0.7i) + (-25-8i)x(i) + (-13.6+123.4i)x(-0.7-0.7i) )
= 0.125 x 364.32 = 45.54 ≈ 46
c4 = (1/N) x ( C0xω0x4 + C1xω1x4 + C2xω2x4 + C3xω3x4
+ C4xω4x4 + C5xω5x4 + C6xω6x4 + C7xω7x4 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω4 + (-25+8i)xω8 + (-2.4+5.8i)xω12
+ 21xω16 + (-2.4-5.8i)xω20 + (-25-8i)xω24 + (-13.6+123.4i)xω28 )
= 0.125 x ( 189x1 + (-13.6-123.4i)x(-1) + (-25+8i)x1 + (-2.4+5.8i)x(-1)
+ 21x1 + (-2.4-5.8i)x(-1) + (-25-8i)x1 + (-13.6+123.4i)x(-1) )
= 0.125 x 192 = 24
c5 = (1/N) x ( C0xω0x5 + C1xω1x5 + C2xω2x5 + C3xω3x5
+ C4xω4x5 + C5xω5x5 + C6xω6x5 + C7xω7x5 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω5 + (-25+8i)xω10 + (-2.4+5.8i)xω15
+ 21xω20 + (-2.4-5.8i)xω25 + (-25-8i)xω30 + (-13.6+123.4i)xω35 )
≈ 0.125 x ( 189x1 + (-13.6-123.4i)x(-0.7-0.7i) + (-25+8i)x(i) + (-2.4+5.8i)x(0.7-0.7i)
+ 21x(-1) + (-2.4-5.8i)x(0.7+0.7i) + (-25-8i)x(-i) + (-13.6+123.4i)x(-0.7+0.7i) )
= 0.125 x 3.04 = 0.38 ≈ 0
c6 = (1/N) x ( C0xω0x6 + C1xω1x6 + C2xω2x6 + C3xω3x6
+ C4xω4x6 + C5xω5x6 + C6xω6x6 + C7xω7x6 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω6 + (-25+8i)xω12 + (-2.4+5.8i)xω18
+ 21xω24 + (-2.4-5.8i)xω30 + (-25-8i)xω36 + (-13.6+123.4i)xω42 )
= 0.125 x ( 189x1 + (-13.6-123.4i)x(-i) + (-25+8i)x(-1) + (-2.4+5.8i)x(i)
+ 21x1 + (-2.4-5.8i)x(-i) + (-25-8i)x(-1) + (-13.6+123.4i)x(i) )
= 0.125 x 1.6 = 0.2 ≈ 0
c7 = (1/N) x ( C0xω0x7 + C1xω1x7 + C2xω2x7 + C3xω3x7
+ C4xω4x7 + C5xω5x7 + C6xω6x7 + C7xω7x7 )
= (1/8) x ( 189xω0 + (-13.6-123.4i)xω7 + (-25+8i)xω14 + (-2.4+5.8i)xω21
+ 21xω28 + (-2.4-5.8i)xω35 + (-25-8i)xω42 + (-13.6+123.4i)xω49 )
≈ 0.125 x ( 189x1 + (-13.6-123.4i)x(0.7-0.7i) + (-25+8i)x(-i) + (-2.4+5.8i)x(-0.7-0.7i)
+ 21x(-1) + (-2.4-5.8i)x(-0.7+0.7i) + (-25-8i)x(i) + (-13.6+123.4i)x(0.7+0.7i) )
= 0.125 x 3.68 = 0.46 ≈ 0
這樣,我們就使用離散傅裡葉變換和逆變換計算出了向量 {ai} 和向量 {bj} 的卷積向量 {ck},如下所示:
{ c7, c6, c5, c4, c3, c2, c1, c0 } = { 0, 0, 0, 0, 24, 46, 65, 38, 16 }
這和我們在前面直接使用向量 {ai} 和向量 {bj} 來計算卷積的結果是一樣的。
但是,這個算法的時間復雜度還是 O(N2)。我們繞了這麼一大圈,不是白費勁了嗎?
現在就到了關鍵時刻,關鍵在於:直接進行離散傅裡葉變換的計算復雜度是 O(N2)。快速傅裡葉變換可以計算出與直接計算相同的結果,但只需要 O(N logN) 的計算復雜度。 N logN 和 N2 之間的差別是巨大的。例如,當 N = 106 時,在一個每秒運算百萬次的計算機上,粗略地說,它們之間就是占用 30 秒 CPU 時間和兩星期 CPU 時間的差別。
快速傅裡葉變換的要點如下:一個界長為 N 的離散傅裡葉變換可以重新寫成兩個界長各為 N/2 的離散傅裡葉變換之和。其中一個變換由原來 N 個點中的偶數點構成,另一個變換由奇數點構成。這個過程可以遞歸地進行下去,直到我們將全部數據細分為界長為 1 的變換。什麼是界長為 1 的傅裡葉變換呢?它正是把一個輸入值復制成它的一個輸出值的恆等運算。要實現以上算法,最容易的情況是原始的 N 為 2 的整冪次項,如果數據集的界長不是 2 的冪次時,則可添上一些零值,直到 2 的下一冪次。在這個算法中,每遞歸一次需 N 階運算,共需要 log N 次遞歸,所以快速傅裡葉變換算法的時間復雜度是 O(N logN)。
由於快速傅裡葉變換是采用了浮點運算,因此我們需要足夠的精度,以使在出現捨入誤差時,結果中每個組成部分的准確整數值仍是可辨認的。長度為 N 的 B 進制數可產生大到 B2N 階的卷積分量。我們知道,雙精度浮點數的尾數是 53 個二進位,所以:
2 x log2B + log2N + 幾個 x log2log2N < 53
上式中左邊最後一項是為了快速傅裡葉變換的捨入誤差。
所以,為了能夠計算盡量大的整數,一般 B 不會取得太大。在計算機程序中經常使用 256 進制進行運算。但是如果經常需要將計算結果和十進制互相轉換,則往往使用 100 進制進行運算。
關於快速傅裡葉變換以及卷積定理的更深入的知識,請參閱文末的參考文獻。這一篇隨筆主要是講述相關的原理,在下一篇隨筆中,我將給出一個使用快速傅裡葉變換進行任意精度的算術運算的 C# 程序。
順便說一句,我在准備正文的例題的時候,是使用 google 計算器來進行復雜的復數運算的。發現她非常好用。以計算 c2 為例, 只要將要計算的表達式復制到 goole 搜索欄,然後按回車,就能得到計算結果: