感谢傅里叶同学做出的杰出贡献
什么是傅里叶变换?
请看这里
一些先修的概念
复数
就是$\sqrt{-1}$啦,我们写作$i$
单位根
这是整个变换的核心,也就是满足$z^n=1$的所有复数
我们定义$\omega_n=e^{\frac{2\pi i}{n}}$
那么所有的n次单位根就是
$$\omega_n=e^{\frac{2\pi i k}{n}},k=0,1,2\cdots n-1$$
这些单位根为啥长这样呢?
仔细想想发现这些单位根构成复平面上的单位圆的内接正$n$边形
欧拉公式
计算机没法计算复数,我们需要用到欧拉公式:
$$e^{i\theta}=cos(\theta)+isin(\theta)$$
离散傅里叶变换
DFT
定义一个多项式$A(x)$为$A(x)=\sum_{i=0}^{n-1}a_ix^i$
那么其系数向量$(a_0,a_1\cdots a_{n-1})$的离散傅里叶变换为:
将$n$次的单位根向量作为多项式的点值带入多项式计算出来的向量,换句话就是说,系数向量的DFT为,$(A(\omega_n^0),A(\omega_n^1)\cdots , A(\omega_n^{n-1}))$
IDFT
IDFT是逆变换,实际上就是解方程的过程
我们不难发现DFT的过程实际上是下面这个矩阵运算:
$$
\begin{bmatrix}
(\omega_{n}^0)^0 & (\omega_{n}^0)^1 & \cdots & (\omega_{n}^0)^{n-1}\\
(\omega_{n}^1)^0 & (\omega_{n}^1)^1 & \cdots & (\omega_{n}^1)^{n-1}\\
\vdots & \vdots & \ddots & \vdots \\
(\omega_{n}^{n-1})^0 & (\omega_{n}^{n-1})^1 & \cdots & (\omega_{n}^{n-1})^{n-1}
\end{bmatrix}
\begin{bmatrix}
a_0\\
a_1\\
\vdots\\
a_{n-1}
\end{bmatrix}
=
\begin{bmatrix}
A(\omega_n^0)\\
A(\omega_n^1)\\
\vdots\\
A(\omega_n^{n-1})
\end{bmatrix}
$$
我们令系数矩阵为$V$,为了解决这个线性方程,我们必须找到$V$的逆。
我们定义$D$,其中$D$中的每个元素都是$V$中对应位置的共轭。
然后我们考察一下$E=D·V$
仔细计算后发现$E$竟然是$nI_n$,其中$I_n$表示的是$n$阶单位矩阵
这样一来我们就发现$V$的逆矩阵$V^{-1}=\frac{1}{n}D$
所以逆变换和正变换在运算上只是一个$n$的倍数和共轭的关系,下面讨论的都是正变换,逆变换类似。
Cooley-Tukey算法
Cooley-Tukey算法是FFT的一种,也是比较好懂的一种。
算法思想
算法的思想和CDQ分治很类似,都是通过预先处理好的一半来求出另一半的答案
算法将$A$的每个分量的运算,按照指数奇偶分类:
$A(\omega_n^k)=\sum_{i=0}^{n-1}a_i\omega_n^{ki}=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_n^{2ki}+\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_n^{2ki}$
酱紫有什么好处呢?
我们知道$\omega_n^{2k}=e^{\frac{2\pi ki}{n}·2}=e^{\frac{2\pi ki}{\frac{n}{2}}}=\omega_{\frac{n}{2}}^{k}$
再把这个放回和式:
$$A(\omega_n^k)=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}+\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki}$$
另外由于单位根刚好将单位圆分成$n$边形
所以在原点的另一侧
$\omega_n^{k+\frac{n}{2}}=-\omega_n^{k}$
刚好转了180度
说了这么多,到底这些有什么用呢
我们可以发现:
对于$k<\frac{n}{2}$
$$A(\omega_n^k)=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}+\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki}$$
$$A(\omega_n^{k+\frac{n}{2}})=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}-\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki}$$
这样一来如果我们处理好了$A(\omega_n^k)$,那么我们就能$O(1)$求出$A(\omega_n^{k+\frac{n}{2}})$
这样递归下去后,算法的复杂度就变为了$O(nlogn)$
递归实现方法
代码不是我写的,代码是Miskcoo写的,看这里(不是我懒,是我写出来的没他好。。。。
这里$n$必须扩展到2的幂
epsilon
是事先打好的表
迭代实现方法
考察一下整个计算过程,你就会发现,如果将计算的下标都做二进制翻转:
比如讲0100变成0010
整个计算就是挨着挨着算的,从0算到1,从0,1算到2,3,从0,1,2,3算到4,5,6,7。。。
所以我们只需要先整理好计算的顺序,然后就可以迭代计算出来了:
这回是我自己写的了。。
预处理单位根
二进制翻转
变换