1. 程式人生 > >【知識總結】快速傅立葉變換(FFT)

【知識總結】快速傅立葉變換(FFT)

這可能是我第五次學FFT了……菜哭qwq

先給出一些個人認為非常優秀的參考資料:

一小時學會快速傅立葉變換(Fast Fourier Transform) - 知乎

小學生都能看懂的FFT!!! - 胡小兔 - 部落格園

快速傅立葉變換(FFT)用於計算兩個\(n\)次多項式相乘,能把複雜度從樸素的\(O(n^2)\)優化到\(O(nlog_2n)\)。一個常見的應用是計算大整數相乘。

本文中所有多項式預設\(x\)為變數,其他字母均為常數。所有角均為弧度制。

一、多項式的兩種表示方法

我們平時常用的表示方法稱為“係數表示法”,即

\[A(x)=\sum _{i=0}^n a_ix^i\]

上面那個式子也可以看作一個以\(x\)為自變數的\(n\)次函式。用\(n+1\)個點可以確定一個\(n\)次函式(自行腦補初中學習的二次函式)。所以,給定\(n+1\)\(x\)和對應的\(A(x)\),就可以求出原多項式。用\(n+1\)個點表示一個\(n\)次多項式的方式稱為“點值表示法”。

在“點值表示法”中,兩個多項式相乘是\(O(n)\)的。因為對於同一個\(x\),把它代入\(A\)\(B\)求值的結果之積就是把它帶入多項式\(A\times B\)求值的結果(這是多項式乘法的意義)。所以把點值表示法下的兩個多項式的\(n+1\)個點的值相乘即可求出兩多項式之積的點值表示。

線性複雜度點值表示好哇好

但是,把係數表示法轉換成點值表示法需要對\(n+1\)個點求值,而每次求值是\(O(n)\)的,所以複雜度是\(O(n^2)\)。把點值表示法轉換成係數表示法據說也是\(O(n^2)\)的(然而我只會\(O(n^3)\)的高斯消元qwq)。所以暴力取點然後算還不如直接樸素演算法相乘……

但是有一種神奇的演算法,通過取一些具有特殊性質的點可以把複雜度降到\(O(nlog_2n)\)

二、單位根

從現在開始,所有\(n\)都預設是\(2\)的非負整數次冪,多項式次數為\(n-1\)。應用時如果多項式次數不是\(2\)的非負整數次冪減\(1\),可以加係數為\(0\)

的項補齊。

先看一些預備知識:

複數\(a+bi\)可以看作平面直角座標系上的點\((a,b)\)。這個點到原點的距離稱為模長,即\(\sqrt{a^2+b^2}\);原點與\((a,b)\)所連的直線與實軸正半軸的夾角稱為輻角,即\(sin^{-1}\frac{b}{a}\)。複數相乘的法則:模長相乘,輻角相加

把以原點為圓心,\(1\)為半徑的圓(稱為“單位圓”)\(n\)等分,\(n\)個點中輻角最小的等分點(不考慮\(1\))稱為\(n\)單位根,記作\(\omega_n\),則這\(n\)個等分點可以表示為\(\omega_n^k(0\leq k < n)\)

這裡如果不理解,可以考慮周角是\(2\pi\)\(n\)次單位根的輻角是\(\frac{2\pi}{n}\)\(w_n^k=w_n^{k-1}\times w_n^1\),複數相乘時模長均為\(1\),相乘仍為\(1\)。輻角\(\frac{2\pi (k-1)}{n}\)加上單位根的輻角\(\frac{2\pi}{n}\)變成\(\frac{2\pi k}{n}\)

單位根具有如下性質:

1.折半引理

\[w_{2n}^{2k}=w_n^k\]

模長都是\(1\),輻角\(\frac{2\pi \times 2k}{2n}=\frac{2\pi k}{n}\),故相等。

2.消去引理

\[w_n^{k+\frac{n}{2}}=-w_n^k\]

這個從幾何意義上考慮,\(w_n^{k+\frac{n}{2}}\)的輻角剛好比\(w_n^k\)多了\(\frac{2\pi \times \frac{n}{2}}{n}=\pi\),剛好是一個平角,所以它們關於原點中心對稱。互為相反數的複數關於原點中心對稱。

3.(不知道叫什麼的性質)其中\(k\)是整數

\[w_n^{a+kn}=w_n^a\]

這個也很好理解:\(w_n^n\)的輻角是\(2\pi\),也就是轉了一整圈回到了實軸正半軸上,這個複數就是實數\(1\)。乘上一個\(w_n^n\)就相當於給輻角加了一個周角,不會改變位置。

三、離散傅立葉變換(DFT)

DFT把多項式從係數表示法轉換到點值表示法。

我們大力嘗試把\(n\)次單位根的\(0\)\(n-1\)次冪分別代入\(n-1\)次多項式\(A(x)\)。但首先先對\(A(x)\)進行奇偶分組,得到:

\[A_1(x)=\sum_{i=0}^{\frac{n-1}{2}}a_{2i}·x^i\]

\[A_2(x)=\sum_{i=0}^{\frac{n-1}{2}}a_{2i+1}·x^i\]

則有:

\[A(x)=A_1(x^2)+x·A_2(x^2)\]

\(w_n^k\)代入,得:

\[A(w_n^k)=A_1(w_n^{2k})+w_n^k·A_2(w_n^{2k})\]

根據折半引理,有:

\[A(w_n^k)=A_1(w_{\frac{n}{2}}^k)+w_n^k·A_2(w_{\frac{n}{2}}^k)\]

此時有一個特殊情況。當\(\frac{n}{2}\leq k < n\),記\(a=k-\frac{n}{2}\),則根據消去引理和上面第三個性質,有:

\[w_n^a=-w_n^k\]

\[w_{\frac{n}{2}}^a=w_{\frac{n}{2}}^k\]

所以

\[A(w_n^k)=A_1(w_{\frac{n}{2}}^a)-w_n^a·A_2(w_{\frac{n}{2}}^a)\]

這樣變換主要是為了防止右側式子裡出現\(w_n\)的不同次冪。

按照這個式子可以遞迴計算。共遞迴\(O(log_2n)\)層,每層需要\(O(n)\)列舉\(k\),因此可以在\(O(nlog_2n)\)內把係數表示法變為點值表示法。

四、離散傅立葉反變換(IDFT)

\(w_n^k(0\leq k<n)\)代入多項式\(A(x)\)後得到的點值為\(b_k\),令多項式\(B(x)\)

\[B(x)=\sum_{i=0}^{n-1}b_ix^i\]

一個結論:設\(w_n^{-k}(0\leq k<n)\)代入\(B(x)\)後得到的點值為\(c_k\),則多項式\(A(x)\)的係數\(a_k=\frac{c_k}{n}\)。下面來證明這個結論。

\[ \begin{aligned} c_k&=\sum_{i=0}^{n-1}b_i·w_n^{-ik}\\ &=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j·w_n^{ij}·w_n^{-ik}\\ &=\sum_{j=0}^{n-1}a_j\sum_{i=0}^{n-1}w_n^{i(j-k)} \end{aligned} \]

腦補一下\(\sum_{i=0}^{n-1}w_n^{i(j-k)}\)怎麼求。可以看出這是一個公比為\(w_n^{j-k}\)的等比數列。

\(j=k\)\(w_n^0=1\),所以上式的值是\(n\)

否則,根據等比數列求和公式,上式等於\(w_n^{j-k}·\frac{w_n^{n(j-k)}-1}{w_n^{j-k}-1}\)\(w_n^{n(j-k)}\)相當於轉了整整\((j-k)\)圈,所以值為\(1\),這個等比數列的和為\(0\)

由於當\(j \neq k\)時上述等比數列值為\(0\),所以\(c_k=a_kn\),即\(a_k=\frac{c_k}{n}\)

至此,已經可以寫出遞迴的FFT程式碼了。(常數大的一批qwq

五、優化

(未完待續……