FFT多項式計算
傅里葉變換是一個很大的概念,裏面包含太多太多東西,整篇在傅里葉變換中只是一個小應用而已。就我的理解,描述快速傅里葉變換優化多項式乘法的原理和具體實踐。
傅里葉變換
傅里葉變換是一種線性積分變換,用於信號在時域(或空域)和頻域之間的變換。
一般情況下,若傅里葉變換一詞不加任何限定語,則指的是連續傅里葉變換。(連續)傅里葉變換定義如下:傅里葉變換將可積函數f:R→C表示成復指數函數的積分或級數性質。
f^(ξ)=∫−∞∞f(x)e−2πixξdx
其中ξ爲任意實數。自變量x表示時間,變換變量ξ表示頻率。在多數情況下,傅里葉變換是可逆的,即可以通過f^得到其原函數f,定義如下:
f(ξ)=∫−∞∞f^(ξ)e2πiξxdξ
離散傅里葉變換
爲了在科學計算和數字信號處理等領域使用計算機進行傅里葉變換,必須將函數xn定義在離散點而非連續域內,且須滿足有限性和週期性等提交。這種情況下,使用離散傅里葉變換(Discrete Fourier Transform,DFT),DFT是傅里葉變換在時域和頻域上都呈離散的形式,函數定義如下:
x^n=k=0∑N−1xke−iN2πknn=0,⋯,N−1
其逆變換爲:
xk=N1n=0∑N−1x^ne−iN2πnkk=0,⋯,N−1
單位根
數學上,n次單位根是n次冪爲1的負數。定義爲ωn=1(n=1,2,3,⋯),ω爲n次單位根。單位的n次根有n個:e2πki/n(k=0,1,2,⋯,n−1)。我們用ωn0,ωn1,⋯,ωnn−1表示這個n個n次根,ωni=e2πki/n。
結合歐拉公式,可以很容易得看出,ωnk=e2πnki=cos(2πnk)+i⋅sin(2πnk)=1
對於n≥0,k≥0,d≥0,有ωdndk=(e2πi/dn)dk=(e2πi/n)k=ωnk。
對於∀n滿足n>0且2∣n,有ωnk+2n=en2πi(k+2n)=e2πik/neπi=ωnk(cosπ+isinπ)=−ωnk
多項式
形如a0+a1x+⋯+anxn的多項式被稱爲多項式,記爲A(x)=∑i=0n−1aixi,其中ai,i=0,1,⋯,n爲係數,用係數表示多項式爲a=(a0,a1,⋯,an),a就爲多項式的係數表示法。
我們知道通過n+1個不同的點{(x0,y0),⋯,(xn,yn)}就可以唯一確定一個n次多項式,{(x0,y0),⋯,(xn,yn)}爲多項式的點值表示法。
快速傅里葉變換
快速傅里葉變換(Fast Fourier Transform,FFT),是計算離散傅里葉變換及其逆變換的快算算法。按照DFT的定義,計算一個長爲n的序列需要的時間複雜度爲O(n2),而FFT通過把DFT矩陣分解爲稀疏因子之積來快速計算變換,從而將時間複雜度降到O(nlogn)。
庫裏-圖基快速傅里葉變換算法是最常見的FFT算法,基於分治策略,可將時間複雜度降爲O(nlogn)。
假設一個多項式表示爲A(x)=∑i=0n−1aixi,用係數表示法表示就是向量a=(a0,a1,⋯,an)。用點值表示,將n個n次單位根ωn0,ωn1,⋯,ωnn−1帶入到多項式爲A(ωnk)=∑i=0n−1aiωnki,k=0,1,⋯,n−1。現在我們和DFT定義的公式比對,A(ωni)就是是ai的DFT,那麼ai就是A(ωni)就是的IDFT。記爲A(ωni)=DFT(ai)。
下面我們就依據上面陳列的基礎基礎,對A(ωnk)進行公式推導:
A(ωnk)=∑i=0n−1aiωnki=∑i=02n−1a2iωn2ki+ωnk∑i=02n−1a2i+1ωn2ki=∑i=02n−1a2iω2nki+ωnk∑i=02n−1a2i+1ω2nki
又由於
A(ωnk+2n)=∑i=0n−1aiωn(k+2n)i=∑i=02n−1a2iωn2ki+ωnk+2n∑i=02n−1a2i+1ωn2ki=∑i=02n−1a2iω2nki−ωnk∑i=02n−1a2i+1ω2nki
可以看出,對於k<2n時,只要代入ω2n,ω2n2,⋯,ω2n2n−1,就可以求出A(ωnk)和A(ωnk+2n)。
A(ωnk)=A(ωnk+2n)=A1(ω2nk)+A2(ω2nk)A1(ω2nk)−A2(ω2nk)(公式1)
不遞歸的僞代碼如下所示:
void get_rev(int bit)
{
for(int i=0; i<(1<<bit); i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
}
/**
n爲2的冪次方
op==1,DFT
op==-1,IDFT
*/
void fft(cd *a,int n,int op)
{
for(int i=0; i<n; i++)
{
if(i<rev[i])
{
cd tmp = a[i];
a[i] = a[rev[i]];
a[rev[i]] = tmp;
}
}
for(int step=1; step<n; step<<=1)
{
cd wn=exp(cd(0,op*PI/step));
for(int j=0; j<n; j+=step<<1)
{
cd wnk(1,0);
for(int k=j; k<j+step; k++)
{
cd x=a[k];
cd y=wnk*a[k+step];
a[k]=x+y;
a[k+step]=x-y;
wnk*=wn;
}
}
}
if(op==-1)
{
for(int i=0; i<n; i++)
a[i]/=n;
}
}
費了好大經歷才把代碼和原理結合在一起,在網上搜了好久都沒有理解爲何代碼要這樣寫,後來在離散傅里葉變換和快速傅里葉變換課件中搜到一張圖,瞬間明白,其中的子操作被稱爲蝴蝶操作。如下圖所示,展示了一個8個點的DFT是怎樣分解成4個2個點的DFT。
蝴蝶操作見下圖,類似於蝴蝶形狀,是不是和上述公式1的計算相符合。
FFT實現多項式乘法
當現在有多項式A(x)和多項式B(x),令C(x)=A(x)⋅B(x),那麼如何求C的係數cj呢?
先引入一個定理,函數卷積的傅里葉變換是函數傅里葉變換的乘積。$C\left(x\right) 中次數爲j的項由A \left(x\right) 中次數爲k的項和B\left(x\right) 中次數j-k爲的項相乘得到。那麼我們先通過C\left(\omega_{n}\right) = A\left(\omega_{n}\right) B\left(\omega_{n},\right),然後在通過IDFT計算c_{j}$。
fft(a,n,1);
fft(b,n,1);
for(int i=0; i<s; i++) a[i]*=b[i];
fft(a,n,-1);
備註
歐拉公式eix=cosx+i⋅sinx
參考