写在前面
我为什么要写这篇博客?
\quad 如果你随便拉几个OI党,问他们最难理解的几个算法,FFT一定榜上有名。我从开始尝试理解FFT算法到真正理解FFT算法之间间隔了一年之久,但是当我真正理解了这个算法之后,我发现在之前无法理解这个算法,是因为只要我一去搜索FFT,铺天盖地的都是信号处理相关的知识,然而很多信号处理领域的名词(时域、频域之类)事实上和FFT算法本身毫无关系,FFT其实只要简单地从数学层面上去理解就可以。
\quad 我一直都希望有一天把自己理解的FFT过程写成一篇blog,让后来的初学者可以不因为网上形形色色的FFT解释而迷茫。但是由于懒(毕竟要写好多的嘛),我一直没有尝试写这篇blog。由于我碰巧需要讲一下FFT算法,因此在准备完课件之后我决定把这篇早就该写的blog补上,希望更多的人可以更快的学会FFT算法。
正文
\quad FFT其实并不是一个特别难的算法,现在我们就来一起看看它的本质。
卷积
\quad FFT是一个用来加速卷积计算的方法,那我们就先来看看,什么是卷积。
\quad 考虑两个多项式相乘,其中 f ( x ) = 1 + 2 x + 3 x 2 + 4 x 3 + 5 x 5 g ( x ) = 1 + 2 x + 3 x 2 f(x)=1+ 2x+ 3x^2 +4x^3 + 5x^5 \quad g(x) = 1+2x+3x^2 f(x)=1+2x+3x2+4x3+5x5g(x)=1+2x+3x2
\quad ps:这里发现了一个小疏漏,我本来想写的是 5 x 4 5x^4 5x4,但是由于图都做好了,就不改了,这里的小笔误不会对后面的过程有任何影响。
\quad 我们的目标是计算 h ( x ) = f ( x ) g ( x ) h(x)=f(x)g(x) h(x)=f(x)g(x)每一项的系数
\quad 它们乘积的过程是怎样的呢?
\quad 首先,乘积多项式的常数项应该由两个常数1相乘得到
\quad 然后,我们把连线系数的乘积加和,得到了乘积多项式的一次项
\quad 得到二次项
\quad 得到三次项,后面的同理
\quad 我们发现这个乘积的关系连成线是扭在一起的,所以我们管这种运算叫做卷积
\quad 多项式乘积的形式可以很自然的类比到整数乘法
\quad 比如:
12345 = 5 + 4 ∗ 10 + 3 ∗ 1 0 2 + 2 ∗ 1 0 3 + 1 ∗ 1 0 4 \quad 12345 = 5+4*10+3*10^2+2*10^3 +1*10^4 12345=5+4∗10+3∗102+2∗103+1∗104
123 = 3 + 2 ∗ 10 + 1 ∗ 100 \quad 123 = 3+ 2*10+1*100 123=3+2∗10+1∗100
\quad 我们只需要把x换成10就会很容易发现多项式乘法和整数乘法之间的关系,最后算出来的结果处理一下进位问题,就是最终整数乘法的结果。
\quad 如果我们把第二个多项式倒过来会发生什么?
\quad 两个1相乘,得到常数项
\quad 得到一次项
\quad 得到二次项
\quad 三次项,后面的同理
\quad 我们发现这样的话就是对应位置相乘然后加和,是不是好看了很多?当然,这对我们求解FFT没有任何帮助,但是这可以帮助你理解在实际应用中为什么很多完全不“卷”东西被称作是卷积。 比如说信号平滑和神经网络中的卷积层,其实你只需要把它“扭一下”,它就是卷积了。
如何加速计算卷积?
\quad 朴素计算卷积的时间复杂度是 O ( n 2 ) O(n^2) O(n2)的(枚举两个多项式的每一项相乘)
\quad 如果停留在这样的形式,我们几乎不可能找到一个加速卷积计算的方法。
\quad 换一个思路,我们知道,对于一个(n-1)次多项式,我们只需要n个点的取值,就可以列出n个方程从而解出n个系数。
\quad 学过高斯消元的小伙伴这个时候可能会说,解方程是 O ( n 3 ) O(n^3) O(n3)的啊,这不是把问题变复杂了吗?这也正是FFT的优雅之处,后面会说明是如何通过奇怪的技巧把反解多项式的时间复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn)
\quad 假定 h ( x ) = f ( x ) g ( x ) h(x)=f(x)g(x) h(x)=f(x)g(x)是一个n-1次多项式
\quad 如上所述,我们只需要知道多项式n个点的取值就可以反解出多项式的系数
\quad 注意到 h ( a ) = f ( a ) g ( a ) h(a) = f(a)g(a) h(a)=f(a)g(a)
\quad 因此我们可以通过 f ( x ) f(x) f(x)和 g ( x ) g(x) g(x)在n个点的取值来算得 h ( x ) h(x) h(x)在n个点的取值,从而反解出 h ( x ) h(x) h(x)
\quad 那么同样是带入n个点,我们带哪n个点会好算呢?
复平面单位根
\quad 这里我们定义 ω 2 n i \omega_{2n}^i ω2ni是 x 2 n = 1 x^{2n}=1 x2n=1,在复平面上2n个解中的第i个。
\quad 这个公式是很多人噩梦的起点,首先我给大家普及一下,第二个等式成立是因为这个是 e i π e^{i \pi} eiπ的定义,所以看到这个式子不要懵,他就是这样的,无需证明。其次,最后的cos+sin看起来很复杂,这样也不好理解它为什么是 x 2 n = 1 x^{2n}=1 x2n=1的解,但是其实我们换一个形式就可以看的很清楚。
\quad [hints] 这个图里面数按照我之前所说的表示方法实际上应该写为 ω 8 i \omega_8^i ω8i,这里是简写的写法
\quad 这是复平面上的单位圆,学过高中复数知识的同学应该多少都听说过,复平面就类似于一个二维坐标轴,坐标(m,n)就代表m+ni这个复数。在复平面上做乘法的规律是,两个复数的模长相乘,极角相加。由于是单位圆,所以圆上每个复数的模长都是1,所谓极角就是复平面上一点与圆心的连线逆时针转到x轴正半轴需要转的度数。就比如说上图中的 ω 1 \omega_1 ω1的极角就是45度,也