淺談FFT

Fast Fourier Transportation(FFT)

·多項式的表達

係數表達

對於一個次數界爲n的多項式A(x)=j=0n1ajxjA(x)=\sum_{j=0}^{n-1}{a_jx^j}​而言,其係數表達是由一個係數組成的向量a=(a0,a1,...,an1)a=(a_0,a_1,...,a_{n-1})​

點值表達

一個次數界爲n的多項式A(x)的點值表達就是一個由n個點值對所組成的集合
(x0,y0),(x1,y1),...,(xn1,yn1) {(x_0,y_0),(x_1,y_1),...,(x_{n-1},y_{n-1})}
使得對k=0,1,…,n-1,所有xkx_k​各不相同,
yk=A(xk) y_k=A(x_k)
簡單的求點值運算我們可以隨意代入n個不相同的數,然後得出點對,時間複雜度Θ(n2)\Theta(n^2)​。後面可以看到,如果我們用一點巧妙的取值,可以使時間複雜度優化到Θ(nlg2n)\Theta(n\lg_2n)​

求值計算的逆(從一個多項式的點值表達確定的係數表達形式)稱爲插值

·多項式運算

Cj=k=0jAkBjk C_j=\sum_{k=0}^{j}{A_kB_{j-k}}

上述是多項式的乘法,我們把C稱爲A和B的卷積(convolution),表示成C=ABC=A\bigotimes B

FFT的主要思路是首先把A和B轉成點值表達,然後得到C的點值表達,再逆着做一遍,得到C的係數表達。

·DFT與FFT

上述做法太慢,我們要用Θ(n2)\Theta(n^2)的時間把每個多項式轉成點值表達,然後再用Θ(n2)\Theta(n^2)的時間轉回來,明顯很慢,還不如暴力。

我們想,可不可以使用某些特殊的數,使得每次可以做一次運算就可以得到多個數的呢?答案是有的:單位根複數根

n次單位複數根是滿足ωn=1\omega^n=1的複數ω\omega。n次單位複數根恰好有n個,對於k=0,1,…,n-1,這些根是eπik/ne^{\pi ik/n}。爲了解釋這個表達式,我們用複數的指數形式來定義:
eiu=cos(u)+isin(u) e^{iu}=cos(u)+isin(u)

也就是給定一個單位圓,上面均勻地分佈着n個向量,如圖:
在這裏插入圖片描述

·關於n次單位復根

以上圖爲例我們可以發現,每一個n(這裏是8)次單位復根都是一個向量,他們在乘法意義下形成一個羣。

引理1:(消去引理)
n0,k0,d>0, 對於任意整數n\geqslant0,k\geqslant0,以及d>0,

ωdkdk=ωnk \omega^{dk}_{dk}=\omega^{k}_{n}

證明
ωdkdk=(e2πi/dn)dk=(e2πi/n)k=ωnk \omega^{dk}_{dk}=(e^{2\pi i/dn})^{dk}=(e^{2\pi i/n})^k=\omega^{k}_{n}
引理2:(折半引理)
n>0nnn/2n/2 如果n>0爲偶數,那麼n個n次單位根的平方集合就是n/2個n/2次單位根的集合
證明
(ωnk+n/2)2=ωn2k+n=ωn2kωnn=(ωnk)2 (\omega^{k+n/2}_{n})^2=\omega^{2k+n}_n=\omega^{2k}_n\omega^n_n=(\omega^k_n)^2
因此ωnk\omega^k_nωnk+n/2\omega^{k+n/2}_n平方相等。

引理3:(求和引理)
n0nk 對於任意整數n\geqslant0和不能被n整除的非負整數k,有

j=0n1(ωnk)j=0 \sum_{j=0}^{n-1}(\omega^k_n)^j=0

證明
j=0n1(ωnk)j=(ωnk)0(1(ωnk)n)1ωnk=(ωnn)k1ωnk1=(1)k1ωnk1=0 \sum_{j=0}^{n-1}(\omega^k_n)^j=\frac{(\omega^k_n)^0(1-(\omega^k_n)^n)}{1-\omega^{k}_{n}}=\frac{(\omega^n_n)^k-1}{\omega^{k}_{n}-1}=\frac{(1)^k-1}{\omega^{k}_{n}-1}=0
因爲要求k不能被n整除,而且僅當k被n整除時ωnk=1\omega^k_n=1成立,同時保證分母不爲0。

DFT

回顧一下,我們希望計算次數界爲n的多項式
A(x)=j=0n1ajxj A(x)=\sum_{j=0}^{n-1}{a_jx^j}
ωn0,ωn1,...,ωnn1\omega^0_n,\omega^1_n,...,\omega^{n-1}_n處的值。假設A以係數形式給出,接下來定義結果yky_k:
yk=A(ωnk)=j=0n1ajωnkj y_k=A(\omega^k_n)=\sum_{j=0}^{n-1}a_j\omega^{kj}_n
向量y=(y0,y1,...,yn1)y=(y_0,y_1,...,y_{n-1})就是係數向量a=(a0,a1,...,an1)a=(a_0,a_1,...,a_{n-1})離散傅里葉變換(DFT)。我們也記作y=DFTn(a)y=DFT_n(a)

FFT

通過使用一種稱爲快速傅里葉變換(FFT)的方法,利用復根的特殊性質,我們就可以在Θ(nlgn)\Theta(n\lg n)的時間內計算出DFTn(a)DFT_n(a)

注意:通篇的n我們假設是2的整數次冪。

FFT利用分治策略,採用A(x)中偶數下標的係數與奇數下標的係數,分別定義兩個新的次數界爲n/2的多項式

A[0](x)=a0+a2x+a4x2+...+an2xn/21 A^{[0]}(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{n/2-1}

A[1](x)=a1+a3x+a5x2+...+an1xn/21 A^{[1]}(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{n/2-1}

可以很容易發現
A(x)=A[0](x2)+xA[1](x2) A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2)
所以原問題轉化爲求兩個次數界爲n/2的多項式A[0](x)A^{[0]}(x)A[1](x)A^{[1]}(x)在點(ωn0)2,(ωn1)2,...,(ωnn1)2(\omega^0_n)^2,(\omega^1_n)^2,...,(\omega^{n-1}_n)^2的取值。

所以我們可以發現在求出A[0](x2)A^{[0]}(x^2)A[1](x2)A^{[1]}(x^2)以後,可以算出兩個復根的結果:
yk=yk[0]+ωnkyk[1]=A[0](ωn2k)+ωnkA[1](ωn2k)=A(ωnk) y_k=y^{[0]}_k+\omega^k_ny^{[1]}_k =A^{[0]}(\omega^{2k}_n)+\omega^k_nA^{[1]}(\omega^{2k}_n) =A(\omega^k_n)
還有
yk+(n/2)=yk[0]ωnkyk[1]=yk[0]+ωnk+(n/2)yk[1]=A[0](ωn2k)+ωk+(n/2)A[1](ωn2k) y_{k+(n/2)}=y^{[0]}_k-\omega^{k}_ny^{[1]}_k =y^{[0]}_k+\omega^{k+(n/2)}_ny^{[1]}_k =A^{[0]}(\omega^{2k}_n)+\omega^{k+(n/2)}A^{[1]}(\omega^{2k}_n)
=A[0](ωn2k+n)+ωk+(n/2)A[1](ωn2k+n)=A(ωnk+(n/2)) =A^{[0]}(\omega^{2k+n}_n)+\omega^{k+(n/2)}A^{[1]}(\omega^{2k+n}_n) =A(\omega^{k+(n/2)}_n)

所以就有代碼:

void FFT(comp *a,int n,int inv){
	if(n==1) return;
	int mid=n/2;
	for (int i=0;i<mid;++i) c[i]=a[i*2],c[i+mid]=a[i*2+1];
	for	(int i=0;i<n;++i) a[i]=c[i];
	FFT(a,mid,inv);
	FFT(a+mid,mid,inv);
	comp wn={cos(2.0*pi/n),inv*sin(2.0*pi/n)},w={1,0};
	for (int i=0;i<mid;++i,w=w*wn){
		c[i]=a[i]+w*a[i+mid];
		c[i+mid]=a[i]-w*a[i+mid];
	} 
	for	(int i=0;i<n;++i) a[i]=c[i];
}

·在單位複數根的插值

現在我們展示如何在單位複數根處插值來完成多項式乘法方案,使得我們把一個多項式從點值表達轉換回係數表達。
我們可以把DFT寫成矩陣乘積
[y0y1y2yn1]=[111111ωnωn2ωn3ωnn11ωn2ωn4ωn6ωn2(n1)1ωn3ωn6ωn9ωn3(n1)1ωnn1ωn2(n1)ωn3(n1)ωn(n1)(n1)][a0a1a2an1] \left[ \begin{matrix} y_0\\ y_1\\ y_2\\ \vdots\\ y_{n-1} \end{matrix} \right]= \left[ \begin{matrix} 1 &amp; 1 &amp; 1 &amp; 1 &amp;\cdots&amp; 1\\ 1 &amp; \omega_n &amp; \omega^2_n &amp; \omega^3_n &amp;\cdots&amp; \omega^{n-1}_n\\ 1 &amp; \omega^2_n &amp; \omega^4_n &amp; \omega^6_n &amp;\cdots&amp; \omega^{2(n-1)}_n\\ 1 &amp; \omega^3_n &amp; \omega^6_n &amp; \omega^9_n &amp;\cdots&amp; \omega^{3(n-1)}_n\\ \vdots &amp; \vdots&amp; \vdots&amp; \vdots&amp; \ddots&amp; \vdots\\ 1 &amp; \omega^{n-1}_n &amp; \omega^{2(n-1)}_n &amp; \omega^{3(n-1)}_n &amp;\cdots&amp; \omega^{(n-1)(n-1)}_n \end{matrix} \right] \left[ \begin{matrix} a_0\\ a_1\\ a_2\\ \vdots\\ a_{n-1} \end{matrix} \right]
尷尬的是跑得賊慢:
在這裏插入圖片描述
隨便卡卡就爆了…
分治難免遞歸,遞歸常數大。
於是,考慮改進。

·蝴蝶變換

在這裏插入圖片描述
盜圖一張
可以發現,每個下標的二進制形式反過來就是它們最後在序列中的位置。
於是就有了迭代打法。

void FFT(Moon *a,int inv){
    int i,j,len;
    for (i=0;i<n;++i)
        if(i<R[i])swap(a[i],a[R[i]]);
    for (len=2;len<=n;len<<=1){
        int half=len/2;
        Moon w,wn={cos(Pi/half),inv*sin(Pi/half)};
        for (j=0;j<n-i;j+=len,w={1,0}){
        	for (i=0;i<half;++i,w=w*wn){
                Moon q=w*a[j+half+i],Q=a[j+i];
                a[j+half+i]=Q-q;
                a[j+i]=Q+q;
            }
        }
    }
}
int main(){
    for (i=0;i<n;++i) R[i]=(R[i>>1]>>1)|((i&1)<<(p-1));
} 

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章