任意长度FFT

针对长度 nn 为合数和质数的情况,分别使用 Cooley-Tukey 和 Rader 算法进行实现。

Cooley-Tukey


Cooley-Tukey 是一种求解长度为合数的 FFT 算法。基 2 的 FFT 是一种特殊的 Cooley Tukey,简单回顾一下:

(E0,E1,…,En/2−1)=FFT(x0,x2,…,xn−2)(O0,O1,…,On/2−1)=FFT(x1,x3,…,xn−1)for k=0,1,…,n/2−1: Xk=Ek+wnkOkfor k=n/2,n/2+1,…,n−1: Xk=Ek−n/2+wnkOk−n/2\begin{aligned} &(E_0,E_1,\dots,E_{n/2-1})=\text{FFT}(x_0,x_2,\dots,x_{n-2})\\ &(O_0,O_1,\dots,O_{n/2-1})=\text{FFT}(x_1,x_3,\dots,x_{n-1})\\ &\text{for}\ k=0,1,\dots,n/2-1:\ X_k=E_k+w_{n}^kO_k\\ &\text{for}\ k=n/2,n/2+1,\dots,n-1:\ X_k=E_{k-n/2}+w_{n}^kO_{k-n/2} \end{aligned}

简单来说,就是将长度为 nn 的 DFT 拆分成两个长度为 n/2n/2 的 DFT,从而使得计算的时间复杂度降低。

Cooley-Tukey 也是使用了这种分而治之的思想,根据长度的因数对序列进行拆分。假设 xx 的长度为 NN,其中 N=N1N2N=N_1N_2,那么 DFT 的公式为

Xk=∑n=0N−1exp⁡(−i2πNnk)xnX_k=\sum_{n=0}^{N-1}\exp(-i\frac{2\pi}{N}nk)x_n

将以下两个式子代入上式

k=N2k1+k2, k1∈{0,1,…,N1−1}, k2∈{0,1,…,N2−1}n=N1n2+n1, n1∈{0,1,…,N1−1}, n2∈{0,1,…,N2−1}k=N_2k_1+k_2,\ k_1\in\{0,1,\dots,N_1-1\},\ k_2\in\{0,1,\dots,N_2-1\}\\ n=N_1n_2+n_1,\ n_1\in\{0,1,\dots,N_1-1\},\ n_2\in\{0,1,\dots,N_2-1\}

可以得到

XN2k1+k2=∑n1=0N1−1∑n2=0N2−1exp⁡(−i2πN1N2(N1n2+n1)(N2k1+k2))xN1n2+n1=∑n1=0N1−1exp⁡(−i2πN1n1k1)exp⁡(−i2πNn1k2)∑n2=0N2−1xN1n2+n1exp⁡(−i2πN2(n2k2))\begin{aligned} X_{N_2k_1+k_2}&=\sum_{n_1=0}^{N_1-1}\sum_{n_2=0}^{N_2-1}\exp\left(-i\frac{2\pi}{N_1N_2}(N_1n_2+n_1)(N_2k_1+k_2)\right)x_{N_1n_2+n_1}\\ &=\sum_{n_1=0}^{N_1-1}\exp\left(-i\frac{2\pi}{N_1}n_1k_1\right)\exp(-i\frac{2\pi}{N}n_1k_2)\sum_{n_2=0}^{N_2-1}x_{N_1n_2+n_1}\exp\left(-i\frac{2\pi}{N_2}(n_2k_2)\right) \end{aligned}

分析一下以上式子,∑n2=0N2−1xN1n2+n1exp⁡(−i2πN2(n2k2))\sum\limits_{n_2=0}^{N_2-1}x_{N_1n_2+n_1}\exp\left(-i\dfrac{2\pi}{N_2}(n_2k_2)\right) 正是长度为 N2N_2 的 DFT,对应的序列为 xN1n2+n1, n2∈{0,1,…,N2−1}x_{N_1n_2+n_1},\ n_2\in\{0,1,\dots,N_2-1\}

记 yn1,k2=∑n2=0N2−1xN1n2+n1exp⁡(−i2πN2(n2k2)),zn1,k2=exp⁡(−i2πNn1k2)yn1,k2y_{n_1,k_2}=\sum\limits_{n_2=0}^{N_2-1}x_{N_1n_2+n_1}\exp\left(-i\dfrac{2\pi}{N_2}(n_2k_2)\right),z_{n_1,k_2}=\exp(-i\dfrac{2\pi}{N}n_1k_2)y_{n_1,k_2},则有

XN2k1+k2=∑n1=0N1−1exp⁡(−i2πN1n1k1)zn1,k2X_{N_2k_1+k_2}=\sum_{n_1=0}^{N_1-1}\exp\left(-i\frac{2\pi}{N_1}n_1k_1\right)z_{n_1,k_2}

这正是长度为 N1N_1 的 DFT。

根据以上的分析,对于 NN 为合数的情况,我们可以先做 N1N_1 个长度为 N2N_2 的 DFT,然后每个元素乘上一个系数,再做 N2N_2 个长度为 N1N_1 的 DFT。那么时间复杂度可由下式表示:

T(N)=N1T(N2)+N2T(N1)+Θ(N)T(N)=N_1T(N_2)+N_2T(N_1)+\Theta(N)

具体的算法步骤如下:

  • 将长为 NN 序列(向量)按列排列变为 N1×N2N_1\times N_2 的矩阵
  • 对矩阵的每一行分别做 DFT
  • 矩阵的每一个元素乘上系数 exp⁡(−i2πNn1n2)\exp(-i\dfrac{2\pi}{N}n_1n_2),n1,n2n_1,n_2 分别表示该元素所处的行和列(从 0 开始)
  • 对矩阵的每一列分别做 DFT
  • 将 N1×N2N_1\times N_2 矩阵按行排列变为长为 NN 的向量

推导思路如下:

记 x[n1][n2]=x[N1n2+n1]x[n_1][n_2]=x[N_1n_2+n_1],对每一行进行 DFT 变换,即固定 n1n_1,得到 X1[n1][k2]X_1[n_1][k_2],乘上系数后得到 X2[n1][k2]X_2[n_1][k_2],再对每一列进行 DFT 变换,固定k2k_2,得到 X[k1][k2]=X[N2k1+k2]X[k_1][k_2]=X[N_2k_1+k_2],所以最后按行排列即可得到原序列对应的 DFT。

Rader


对于长度为质数的情况,就不能将其简单的拆分了。注意到假如 NN 为质数的话,那么 N−1N-1 一定为合数,如果能把一个元素单独抽出来,那么剩下就可以用 Cooley Tukey 进行求解了。

Rader 算法需要用到数论的知识,首先介绍相关的知识。由于相关内容超出这门课的范围,就不详细展开证明,只进行叙述。

假设 NN 为质数,定义 a,ba,b 之间的运算 ⋅\cdot 为 (ab)mod  N(ab)\mod N,则 {0,1,…,N−1}\{0,1,\dots,N-1\} 和 ⋅\cdot 可以构成一个群,这个群叫做整数模 NN 乘法群。根据数论知识,这样的群存在一个整数 gg,也叫做原根,使得对于任意非零的 nn 都有唯一对应的 q∈{0,1,…,N−2}q\in\{0,1,\dots,N-2\},对应关系为 n=gpmod  Nn=g^{p}\mod N。同样对于任意非零的 kk 都有唯一对应的 p∈{0,1,…,N−2}p\in\{0,1,\dots,N-2\},使得 k=g−pmod  Nk=g^{-p}\mod N。此处,k=g−pmod  Nk=g^{-p}\mod N 等价于 kgpmod  N=1kg^{p}\mod N=1。

有了以上的结论,就可以将 {1,…,N−1}\{1,\dots,N-1\} 映射到 {0,…,N−1}\{0,\dots,N-1\}。 将上述 n, kn,\ k 代入 DFT 的公式中,单独写出第 0 项,可以得到

X0=∑n=0N−1xnXg−p(modN)=x0+∑q=0N−2xgq(modN)exp⁡(−i2πNgq−p(modN))\begin{aligned} X_0&=\sum_{n=0}^{N-1}x_n\\ X_{g^{-p}(\text{mod}N)}&=x_0+\sum_{q=0}^{N-2}x_{g^{q}(\text{mod}N)}\exp\left(-i\frac{2\pi}{N}g^{q-p}(\text{mod}N)\right) \end{aligned}

令 aq=xgq(modN)a_q=x_{g^{q}(\text{mod}N)},bq=exp⁡(−i2πNg−q(modN))b_q=\exp\left(-i\dfrac{2\pi}{N}g^{-q}(\text{mod}N)\right),q∈{0,…,N−2}q\in\{0,\dots,N-2\},那么 ∑q=0N−2xgq(modN)exp⁡(−i2πNgq−p(modN))\sum\limits_{q=0}^{N-2}x_{g^{q}(\text{mod}N)}\exp\left(-i\dfrac{2\pi}{N}g^{q-p}(\text{mod}N)\right) 实际上是 {aq}\{a_q\} 和 {bq}\{b_q\} 的循环卷积。根据循环卷积定理,有

a∗b=(a^⋅b^)ˇa*b=\check{(\hat{a}\cdot\hat{b})}

这样循环卷积就可以转化为两个 DFT 和 一个 IDFT,这就转化成了我们熟悉的问题。

对于这种情况,为了方便进行 DFT,可以将其填充到 2 的幂次方,使用 基-2 的 FFT 算法进行求解。如果不将其补到 2 的幂次,使用 Rader 算法进行求解的过程中可能还会产生大的质数,这种情况下时间也会很大。

对于长度为 NN 的两个序列,按以下方法进行补充:

  • 首先找到一个最小的 m∈Zm\in\Z,使得 M=2m>2NM=2^{m}>2N
  • 令 a~=[a0,0,…,0⏟M−N,a1,…,aN−1],\tilde a=[a_0,\underbrace{0,\dots,0}_{M-N},a_1,\dots,a_{N-1}], b~=[b0,…,bN−1,b0,… ]\tilde b=[b_0,\dots,b_{N-1},b_0,\dots] (一直重复 bb,直到长度为 MM,对超出的部分直接截断)
  • 求 a~∗b~\tilde a*\tilde b,并取前 NN 项,则得到 a∗ba*b

证明:

(a~∗b~)k=∑j=0M−1a~jb~k−j=a0bk+∑j=M−NM−1a~jb~k−j=a0bk+∑j=1N−1ajb~k+N−j=∑j=0N−1ajbk−j=(a∗b)k(\tilde a*\tilde b)_k=\sum\limits_{j=0}^{M-1}\tilde{a}_j\tilde b_{k-j}=a_0b_k+\sum\limits_{j=M-N}^{M-1}\tilde a_j\tilde b_{k-j}=a_0b_k+\sum\limits_{j=1}^{N-1}a_j\tilde b_{k+N-j}=\sum\limits_{j=0}^{N-1}a_jb_{k-j}=(a*b)_k


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!