FFT,即爲快速傅氏變換,是離散傅氏變換的快速算法,它是根據離散傅氏變換的奇、偶、虛、實等特性,對離散傅立葉變換的算法進行改進獲得的。它對傅氏變換的理論並沒有新的發現,但是對於在計算機系統或者說數字系統中應用離散傅立葉變換,可以說是進了一大步。這是360百科中對於fft的一種概念解釋。我們可以這麼理解:FFT(Fast Fourier Transformation)就是“快速傅里葉變換”的意思,它是一種用來計算DFT(離散傅里葉變換)和IDFT(離散傅里葉反變換)的一種快速算法。這種算法運用了一種高深的數學方式、把原來複雜度爲O(n2)的樸素多項式乘法轉化爲了O(nlogn)的算法。
那麼這種快速傅里葉變換是如何實現的呢?由於離散信號是由很多點組成的,所以DFT的公式如公式1所示。而連續信號的傅里葉變換是在連續域中進行的運算,其積分運算公式如公式2所示。離散傅里葉變換將這個積分運算變爲了累加運算,即使如此,實際運算量也依然非常可觀。所以在實際的應用場景中,離散傅里葉變換的實用性較差。因此,FFT作爲新的計算方式被廣泛接受。其本質是將離散傅里葉變換通過它本身的奇、偶、虛、實的性質,對該公式進行推導改進。公式1中的k和t均爲時域中的輸入參數,X(k)以及F[f(t)]即爲相對與x以及t的傅里葉變換。
公式1
公式2
首先我們對於離散傅里葉變換的公式進行轉換推導。離散傅里葉變換實際可以表示爲:
公式3
我們改變一種表示方式,離散傅里葉變換也可以表示爲
公式4
對於這種公式,我們應該如何來進行求解呢。在這裏我們不進行傳統計算方式的介紹,直接按照fft的思想來進行解析推導。
首先,我們對於多項式進行分解,現在我們將該多項式分解爲奇數次項和偶數次項的進行分類表示。而且爲了表現的更清楚,我們先將該多項式的最高次項定爲7,即有8個多項式進行累加,然後重新排列後的多項式爲:
公式5
爲了表示的更清晰,我們分別給奇數次項和偶數次項重新起一個名字,即f(x)和g(x),這兩個多項式依次表示爲:
公式6
公式7
對該公式進行還原,我們又可以還原回原來的總公式。
公式8
之前我們的思維一直停留在實數範疇內,現在我們引入一個複數域內的概念。我們叫它復根用ω 表示。如果=1 ,那麼我們稱“ω 爲1的k次復根”計做 ,單位復根地表示,其中n就是一個序號數,我們把所有的負根按照複數域角度的大小逆時針排序從零開始編號。
如下圖所示,即爲一個四次復根的圖形表示。
圖 1 四次復根
根據該圖我們可以推論,其實k次復根就相當於是將圖中的圓周平均分成k個弧,弧與弧之間的端點就是k次復根。另外,從圖中可以看出 = -1 = = , 是這個圓與“Real”軸即實數軸正半軸的交點,所以無論k取多少, 始終是1。我們只需要知道 ,就能求出 ,所以我們稱 爲“單位復根”。
在正常使用過程中,我們也能夠表示單位復根, 表示的是“單位復根”的“1次方”也就是它本身,其他的就叫做k次單位復根的n次方。
關於單位復根,它還有一些特性,
- n次單位復根的值隨指數變化而循環, 。
- 折半引理: ,這個引理在fft的公式推導優化過程中會使用到,如圖1中,如果將整個圓周劃分爲兩個2個弧,則的位置實際就是現在 的位置,因此該引理成立。
- 消去定理:,這個定理可以由上圖中看出,復根轉了半圈正好變成了-1。
現在我們將單位復根帶入上述的公式中代替x的值,則原來的公式可以表示爲:
公式9
由於需要對n進行分類討論,因爲涉及到奇偶次項的運算,當0 <n <k/2 -1時 ,此時根據之前提到的復根的折半引理,我們可以將該公式推導爲:
公式10
當k/2<n +k/2<k -1時,上述的公式又可以變爲
公式11
根據之前的消去定理以及折半定理,我們可以將公式優化爲:
公式12
所以,fft的計算公式可以總結爲兩個區域內的值表示,即0 <n <k/2 -1以及k/2<n +k/2<k -1的區間範圍內所有Fx 的值都可以表示,另外,可以看出fx 以及 gx 就是 Fx 的一半,因此在程序計算過程中可以按照子問題的方式來遞歸求解。
以下即爲根據上述推導處的最後兩個公式來進行計算的fft代碼表示。代碼中涉及到的子函數均爲複數間的計算。
void fft(complex *a,int n,int dft)//n表示我的多項位數
{
int i = 0, j = 0, k = 0;
int step = 0;
for(i=0;i<n;i++) if(i<rev[i]) Swap(a[i],a[rev[i]]);
for(step=1;step<n;step<<=1)//模擬一個合併的過程
{
complex wn;
Wn_i(n,1,&wn,dft);//計算當前單位復根
for(j=0;j<n;j+=step<<1)
{
complex wnk;
Wn_i(n,1,&wnk,dft);//計算當前單位復根
for(k=j;k<j+step;k++)
{//蝴蝶操作
complex x=a[k];
complex y;
c_mul(a[k+step],wnk,&y);
c_plus(x,y,&a[k]);
c_sub(x,y,&a[k+step]);//後半個“step”中的ω一定和“前半個”中的成相反數
c_mul(wnk,wn,&wnk);
}
}
}
}
爲了得到蝶形計算的參數序號,在進行fft計算之前先要進行參數重新排序,該函數的目的就是將每一位顛倒。如果暴力按位反轉,總歸不夠優雅。所以,我們用一個類似DP動態規劃的方法來實現這個功能。代碼如下所示:
void get_rev(int bit)//bit表示二進制的位數
{
for(int i=0;i<(1<<bit);i++)//我麼要對1~2^bit-1中的所有數做長度爲bit的二進制翻轉
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));//?!! SMG ?!!
}
根據dp的思想,每一個問題都可以由其子問題的解來進行求解。所以我們可以把一個二進制數看成兩部分,它的前bit-1位是一部分,它的最後一位是一部分。全部bit位的數據求解看成是總問題,則前bit-1位可以看成是一個子問題。所以,要求解所有位數據的反轉,我們可以直接利用前bit-1位數據的反轉結果。因此,任意一個數的二進制反轉就相當於是把它的最後一位當成首位,然後在後面接上它前bit-1位的二進制反轉。而且在這個循環中我們能保證,在計算i的二進制反轉之前,1 ~ i-1中的所有數的二進制反轉都已經完成。 i的前bit-1位的數值其實就是i >>1的值,直接調用 i >>1的二進制反轉的結果就相當於調用了i 的前bit-1位二進制反轉的結果。
其實i >>1的反轉與i的前bit-1位的反轉是有一點出入的,因爲我們的二進制反轉始終以bit位爲標準,所以i >>1會比i的前bit-1位多出一個前導零,而反轉之後就會多出一個後綴零,所以i的前bit-1位的反轉要去掉那個後綴零,此處我們通過將結果向右移位來實現,也就是rev[i>>1]>>1。
因此,我們只要把末尾乘上2^(bit-1)變成首位,此時該數據除了最高位有效之外,其餘所有數位全都是向左移位產生的0,所以,再按位或(|)上rev[ i >>1]>>1就是我們要的答案了。
關於快速傅里葉變換,我的學習過程屬於比較坎坷的一類,大學期間曾經學習過一段時間,但那時對於這些概念沒有深入的學習,所以壓根沒有理解。即使後來再次遇到這些概念及公式也是一頭霧水。現在由於工作過程中需要用到這些知識,所以便重新回來翻看。快速傅里葉變換作爲工程上的一種工具,通過很巧妙的手段將公式重組簡化,大大提高了多項式乘法以及離散傅里葉變換的計算速度。學習期間上網查閱過很多前輩總結的學習筆記,在此向各位提供學習心得的前輩表示衷心的感謝!
參考文獻
https://blog.csdn.net/WADuan2/article/details/79529900
https://blog.csdn.net/tf18269639242/article/details/53024276
https://blog.csdn.net/f_zyj/article/details/76037583
https://wenku.baidu.com/view/5cacb2b8bd64783e09122b9a.html
https://blog.csdn.net/chenyujing1234/article/details/7419863
https://www.cnblogs.com/luoqingyu/p/5930181.html
https://blog.csdn.net/ggn_2015/article/details/68922404
https://blog.csdn.net/egean/article/details/53039248
https://blog.csdn.net/ggn_2015/article/details/68922404
https://blog.csdn.net/linwanglian1/article/details/56020221
https://www.cnblogs.com/RabbitHu/p/FFT.html
https://www.cnblogs.com/Lyush/articles/3219196.html
https://blog.csdn.net/zhaopeizhaopeipei/article/details/53908238