6 步搞懂 FFT
2019-09-15FFT,启动!
第一步:单位根
如下形式的复数称为 次单位根(其中 是虚数单位):
(这里上标是幂次)
基本性质有:
第二步:DFT
DFT 是一个 元序列到 元序列的映射,如下:
其逆如下:
以下默认:大写字母表示对应小写字母的 DFT,如:
第三歩:循环卷积和多项式乘法
考虑从 和 构造的序列 (用 表示序列按位乘,如 ):
展开定义将各项重新组合可得:
根据单位根基本性质,将最里面的求和式化简:(方括号表示真取 假取 )
故有:
(用类似的思路可以证明 DFT 和 IDFT 互逆,此处略去)
也就是说,等式左侧实现了一个叫做循环卷积的过程,伪代码如下:
c = new array of n zeros
for q:
for r:
c[(q + r) mod n] += a[q] * b[r]
特别的,考虑需要计算两个多项式的乘法:我们有系数序列 和 , 有 项, 有 项。假设系数存的是从常数项在下标 开始,我们给 后补 个 ,给 后补 个 ,计算循环卷积时所有 “溢出” 的项都为 ,也就是说相当于 不需要考虑,那么循环卷积特化为多项式乘法:
c = new array of n zeros
for q:
for r:
c[q + r] += a[q] * b[r]
注意到大整数乘法可以化为多项式乘法加处理进位。
直接计算是 的,我们通过在 时间计算 DFT 和 IDFT,配合 时间按位乘,在 时间完成这个过程。
第四步:DFT 的两个性质
线性性(此处加法是按位加),比较容易得到:
还有一个没那么明显:从 和 构造两个交替为原序列元素和 的序列:
则对 ,利用其中只有一半元素非零,以及单位根的性质可得:
类似的,对 :
(取模的原因是 可能超过 ,但是 DFT 原式代入 和 是一样的)
第五步:FFT
取上面的 得:
利用线性性有:
也就是说,我们可以将 长度的 的 DFT 拆成两个长度为 的 和 的 DFT,和一个 的合并过程(是奇偶拆,不是中间分开)。不断递归进行这个过程(单元素序列的 DFT 是其自身),可以在 时间完成一个长度为 的 DFT,这里 需要是 的幂。
这个方法叫做 FFT。
第六步:二进制重排
考虑分治的“分”过程,我们首先将偶数位置和奇数位置分开,然后各自再拆偶数位和奇数位,依此类推。在二进制中,就是先按最低位 0
1
排序,然后两半分别按照次低位排序,依此类推,也就是按照二进制反转排序(如 000 100 010 ... 001 101 ...
)
如对于 按照分治的顺序排好是:
然后迭代合并:
这样我们可以使用两个数组来保存本次迭代和上次迭代的结果,每次一对一对合并,使用 的内存空间完成 FFT(直接递归需要 ),而且一般更快。
完工撒花!
注
在不同的实现中 DFT 的参数可能不同,如
numpy.fft.fft
的实现中,单位根上的的幂次是我们这里所用的取负, 变为 (或者说单位根取共轭),ifft
相反。类似的方法可以构造拆成大于 份的 FFT,在此略去。
本文使用 Pandoc 从 Markdown 原始文件生成,参数为:
$ pandoc --mathjax -f markdown-auto_identifiers -t html < fft.md > index.html