15. 快速傅里叶变换(FFT)原理 [学习笔记]

简单回顾

快速傅里叶变换,其实是离散傅里叶变换的一种改进算法

我们先来回忆一下离散傅里叶变换(DFT):

(1)Ff[m]_=n=0N1f[n]_ω[m]_n

其中ω[m]_=e2πimN

对应的矩阵形式为:

[Ff[0]_Ff[1]_Ff[N1]_]=[1111ω_11ω_1(N1)1ω_(N1)1ω_(N1)2][f[0]_f[1]_f[N1]_]

不难发现,如果要对N个采样点进行DFT,则时间复杂度为o(N2)

接下来,我们将讲解时间复杂度为o(NlogN)的FFT算法

为了书写方便,我们记

(2)ω_[p,q]=e2πiqp

这样,ω[m]_就可以改写为

ω[m]_=e2πimN=ω_[N,m]

同时,不难发现,

ω_[N,n+m]=e2πin+mN=e2πinNe2πimN=ω_[N,n]ω_[N,m]

ω_[N2,nm]=e2πinmN2=e2πi2nmN=ω_[N,2nm]


FFT推导

FFT的核心思想就是:将DFT的N×N矩阵该写为对几个N2×N2的矩阵的操作,并用这种方式进行递归,最后得到FFT

首先,我们将(1)式拆分为奇数、偶数两部分,即

Ff[m]_=n=0N1f[n]_ω[m]_n=(f[0]_ω[m]_0+f[2]_ω[m]_2++f[N2]_ω[m]_(N2))+(f[1]_ω[m]_1+f[3]_ω[m]_3++f[N1]_ω[m]_(N1))=n=0N21f[2n]_ω[m]_2n+n=0N21f[2n+1]_ω[m]_(2n+1)

利用(2)式,可将上式改写为

Ff[m]_=n=0N21f[2n]_ω_[N,2nm]+n=0N21f[2n+1]_ω_[N,(2n+1)m]=n=0N21f[2n]_ω_[N,2nm]+n=0N21f[2n+1]_ω_[N,2nm]ω_[N,m](3)=n=0N21f[2n]_ω_[N2,nm]+ω_[N,m]n=0N21f[2n+1]_ω_[N2,nm]

注意我们要使用递归来计算的话,必须把式子写成矩阵形式,那么(3)式就可以写为:

(4)F_Nf[m]_=FN2feven[m]_+ω_[N,m]FN2fodd[m]_

我们仔细看看从(3)式的求和形式到(4)的矩阵矩阵形式,(3)式完全是没有问题的,但是(4)式却有问题:
(4)式中的F_N2N2×N2的矩阵,那么与之相乘的矩阵必然有N2行,也就是说mN21。换句话说,(4)式计算出的其实是F_N的前半部分。

那么对于m[N2,N1]的情况,应该如何写出相应的矩阵形式呢?我们将(3)式左边的系数写成m+N2,这样一来,m的取值依然是[0,N21]。于是就有:

Ff[m+N2]_=n=0N21f[2n]_ω_[N2,n(m+N2)]+ω_[N,(m+N2)]n=0N21f[2n+1]_ω_[N2,n(m+N2)]=n=0N21f[2n]_ω_[N2,nm]ω_[N2,nN2](5)+ω_[N,m]ω_[N,N2]n=0N21f[2n+1]_ω_[N2,nm]ω_[N2,nN2]

注意到,

ω_[N2,nN2]=e2πin=1

ω_[N,N2]=eπi=1

带入(5)式,有:

Ff[m+N2]_=n=0N21f[2n]_ω_[N2,nm]ω_[N,m]n=0N21f[2n+1]_ω_[N2,nm]

写成矩阵的形式,即

(6)F_Nf[m+N2]_=FN2feven[m]_ω_[N,m]FN2fodd[m]_

结合(4)(6)两式,我们可以得到FFT的矩阵递归式:

F_Nf[m]_=FN2feven[m]_+ω_[N,m]FN2fodd[m]_

F_Nf[m+N2]_=FN2feven[m]_ω_[N,m]FN2fodd[m]_

其中m[0,N21],第一个等式为前N2项,第二个等式为后N2

这个式子很明确地告诉我们:

FFT实际上就是将DFT的矩阵,通过一定的方式进行拆分,从而降低时间复杂度的一种算法


FFT的时间复杂度

有了递归式,我们就可以来算时间复杂度。

方便起见,我们将矩阵写成[n×m],其中nm分别表示行列数。并设N=2k

  1. 那么前N2项就可以写成

    [N2×N2]+CN[N2×N2]

    其中CN为一常数,该式花费的时间为o([N2×N2])+1+o([N2×N2]),其中1就是常数相乘的操作

    对于后N2项,由于对应的矩阵、常数都是相同的,因此只需要额外进行1次操作即可

  2. 我们把n×n的矩阵记为[n],并把m个不同的常数相乘记为Cm,则如下图

    15. 快速傅里叶变换(FFT)原理 [学习笔记]

    注意图中1+2+2k1是前文提到的后N2项的计算量,需要进行累加计算。到最后一项时,它的累积值为2n1

不难发现,递归至最后一项时,总计算次数应为(图中最后一行以及虚线框内):

(7)0(0k)+1(1k)++k(kk)+(2k)+(2k1)+(2k)

简单解释一下上式

  • 前面的各项排列组合表示的是各个常数Cx所需的计算时间,如系数Ck只在最后一个一阶矩阵前出现,计算量为k(kk)=k次,又如系数C1必然会出现(1k)次,因此计算量为1(1k)=k
  • 第一个2n表示的是所有1阶矩阵的计算时间总和;
  • 2n1表示的是后N2项的计算量;
  • 第二个2n表示的是计算所有+(加号)花费的时间

(7)式不容易计算,但是我们可以直观地估计一下,对于前面的阶乘项,最多用时为k次,最少为1次,那么不严格地估计一下,我们取平均k+12次,这样共需要计算N次(有N[1]),因此时间复杂度应为:

o((Nk2)+3N1)=o(12Nlog2N+3N1)=o(Nlog2N)

因此,FFT的时间复杂度就是

o(NlgN)