模板:快速傅里叶变换(FFT)

参考:http://blog.csdn.net/f_zyj/article/details/76037583
原文即是一篇很好的FFT入门博客,但是笔者打算为了日后的学习,则将原篇章的结构删改增添一下,如有思路上的雷同十分正常。
“是时候打开FFT的大门了!”

预备知识:

1.至少知道基础数论与一定解三角形知识(大概是高中水平)。
2.定义 i=1 i = − 1
3.引入复数(即形如 a+bi a + b i (a,b均为实数)的数的集合)
4. (cosθ+i×sinθ)k=cos(kθ)+i×sin(kθ) ( c o s θ + i × s i n θ ) k = c o s ( k θ ) + i × s i n ( k θ )
5.显然我们对多项式FFT之后得到的答案不是我们想要的,那么这时候就需要反着用FFT把式子再变回去(本文记做IFFT)。

这里证明一下第四条,用归纳法。
显然当 k=1 k = 1 时成立。
k k 成立时,我们有:
(cosθ+i×sinθ)k+1 ( c o s θ + i × s i n θ ) k + 1
=(cosθ+i×sinθ)k×(cosθ+i×sinθ) = ( c o s θ + i × s i n θ ) k × ( c o s θ + i × s i n θ )
=(cos(kθ)+i×sin(kθ))×(cosθ+i×sinθ) = ( c o s ( k θ ) + i × s i n ( k θ ) ) × ( c o s θ + i × s i n θ )
=cos(kθ)cosθ+i×sin(kθ)cosθ+i×cos(kθ)sinθ+i2×sin(kθ)sinθ = c o s ( k θ ) c o s θ + i × s i n ( k θ ) c o s θ + i × c o s ( k θ ) s i n θ + i 2 × s i n ( k θ ) s i n θ
=cos(kθ)cosθsin(kθ)sinθ+i×(sin(kθ)cosθ+cos(kθ)sinθ) = c o s ( k θ ) c o s θ − s i n ( k θ ) s i n θ + i × ( s i n ( k θ ) c o s θ + c o s ( k θ ) s i n θ )
=cos((k+1)θ)+i×sin((k+1)θ) = c o s ( ( k + 1 ) θ ) + i × s i n ( ( k + 1 ) θ )
得证。

问题引入:

A(x)=n1i=0aixi,B(x)=n1i=0bixi A ( x ) = ∑ i = 0 n − 1 a i x i , B ( x ) = ∑ i = 0 n − 1 b i x i ,求 A(x)×B(x) A ( x ) × B ( x ) 后的多项式系数。

初探:

显然我们有一个 O(n2) O ( n 2 ) 的解法,但是实在是太慢了。
考虑到一个 n1 n − 1 次多项式可以看做是定义在复数域上的函数,则我们一定可以找到n个点来唯一确定这个函数。
当然我们也可以通过这些点来表示这个多项式。
假设:
A(x) A ( x ) 被表示为: <(x0,ya0),(x1,ya1),,(x2n2,ya2n2)> < ( x 0 , y a 0 ) , ( x 1 , y a 1 ) , … , ( x 2 n − 2 , y a 2 n − 2 ) >
B(x) B ( x ) 被表示为: <(x0,yb0),(x1,yb1),,(x2n2,yb2n2)> < ( x 0 , y b 0 ) , ( x 1 , y b 1 ) , … , ( x 2 n − 2 , y b 2 n − 2 ) >
显然 A(x)×B(x) A ( x ) × B ( x ) 被表示为: <(x0,ya0yb0),(x1,ya1yb1),,(x2n2,ya2n2yb2n2)> < ( x 0 , y a 0 y b 0 ) , ( x 1 , y a 1 y b 1 ) , … , ( x 2 n − 2 , y a 2 n − 2 y b 2 n − 2 ) >

这里多取了点的原因在于 A(x)×B(x) A ( x ) × B ( x ) 是一个 2n2 2 n − 2 次多项式,则至少要取 2n1 2 n − 1 个点才能保证正确。

但是显然还是 O(n2) O ( n 2 ) 的。

再试:

考虑设 A(xi)=A0(x2i)+xiA1(x2i) A ( x i ) = A 0 ( x i 2 ) + x i A 1 ( x i 2 ) ,其中:
A0(x)=a0+a2x+a4x2++an2xn2+1 A 0 ( x ) = a 0 + a 2 x + a 4 x 2 + … + a n − 2 x n 2 + 1
A1(x)=a1+a3x+a5x2++an1xn2+1 A 1 ( x ) = a 1 + a 3 x + a 5 x 2 + … + a n − 1 x n 2 + 1

其实就是按照系数下标的奇偶性分类了一下。

此时我们再令取点的 x x 值为 <x0,x1,,xn21,x0,x1,,xn21> < x 0 , x 1 , … , x n 2 − 1 , − x 0 , − x 1 , … , − x n 2 − 1 >
我们发现把 x x 平方后我们的取值瞬间缩小了一半,而原式唯一变化的就是 A1(x) A 1 ( x ) 前的符号。
看起来我们似乎找到了 O(nlogn) O ( n l o g n ) 的可行方案。
但是很可惜,这样优秀的 x x 取值的性质只会保留一次,也就是说我们只是得到了一个 O(n22) O ( n 2 2 )
如何才能每次将问题的规模缩小一半是我们的目标。

插曲:

有个人告诉你:不如试试 Xn=cos2πn+i×sin2πn X n = c o s 2 π n + i × s i n 2 π n 0n1 0 … n − 1 次方作为 x x 的取值。

这块大家一直有个疑惑:这是怎么构造出来的啊?
事实上傅里叶变换最早是应用于信号处理上的,傅里叶提出:任何连续周期信号可以由一组适当的正弦曲线组合而成。
多项式可以看做非连续周期信号,然后通过各种奇妙的姿势让它逼近正弦曲线的组合形,详情可以看松松松WC2018的课件。
“逼近”显然用到了微积分,不适合初学者,所以就直接跳过了。(其实我也不会……)
(再多说一点吧,其实上面和下面的数学推理完全可以从物理层面理解,还是可以参考松松松WC2018的课件)

继续:

那么令取点的 x x 值为 <X0n,X1n,,Xn1n> < X n 0 , X n 1 , … , X n n − 1 >
我们可知:
(Xkn)2 ( X n k ) 2

=X2kn = X n 2 k

=cos2k×2πn+i×sin2k×2πn = c o s 2 k × 2 π n + i × s i n 2 k × 2 π n

=cos2kπn2+i×sin2kπn2 = c o s 2 k π n 2 + i × s i n 2 k π n 2

=Xkn2 = X n 2 k


Xkn X n k

=cosk×2πn+i×sink×2πn = c o s k × 2 π n + i × s i n k × 2 π n

根据三角函数的周期性可知, k k n n 取模显然不会对答案造成影响。
于是我们有 Xkn=Xk%nn X n k = X n k % n

那么显然对于 <(X0n)2,(X1n)2,,(Xn1n)2> < ( X n 0 ) 2 , ( X n 1 ) 2 , … , ( X n n − 1 ) 2 >

它等效于 <X0n2,X1n2,,Xn21n2,X0n2,X1n2,,Xn21n2> < X n 2 0 , X n 2 1 , … , X n 2 n 2 − 1 , X n 2 0 , X n 2 1 , … , X n 2 n 2 − 1 >

我们好像看到了 O(nlogn) O ( n l o g n ) 的曙光了。

尾声:

显然我们可以对 x x 的取值折半,然后对于左右区间的 x x 值递归下去即可。
Q1:诶等等,“再试”里面的内容好像没有应用上啊……

A1:那就转化一下,其实我们只需要求一个区间的 A0(x) A 0 ( x ) A1(x) A 1 ( x ) 值递归下去求 A(x) A ( x ) 即可。
也就是说其实我们是得到了:
<(A0)0,(A0)1,,(A0)n21,(A1)0,(A1)1,,(A1)n21> < ( A 0 ) 0 , ( A 0 ) 1 , … , ( A 0 ) n 2 − 1 , ( A 1 ) 0 , ( A 1 ) 1 , … , ( A 1 ) n 2 − 1 >

Q2:这好像是画蛇添足……

A2:emmm……我说这个可以用于常数优化你信吗……
显然 A(Xkn)=(A0)k%n2+Xkn(A1)k%n2 A ( X n k ) = ( A 0 ) k % n 2 + X n k ( A 1 ) k % n 2

取模是因为,不要忘了我们的取值是由两个一样的左右区间合并在一起的。

那么我们得到了 <A0,A1,,An1> < A 0 , A 1 , … , A n − 1 >

(其中 Ak=A(Xkn) A k = A ( X n k )

我们好像把这个序列的长度减少了一半诶!那自然是快了二倍啊。

不要忘了n要满足始终是2的倍数,所以n要取2的整数次幂,同时将没用的次幂的系数填成0。

Q3:IFFT怎么做啊?

A3:继续看下去……?

补遗:

略讲一下IFFT。
显然我们可以把FFT的最初算法(也就是DFT)看做两个矩阵相乘。

两个矩阵分别一个填 (Xkn)m ( X n k ) m ,一个填系数,可以上参考处原博客看矩阵。

那么我们把第一个矩阵变成逆矩阵岂不是为IFFT?
其实就是这样,并且事实上就是填 ((Xkn)m)/n ( ( X n − k ) m ) / n ,具体证明过程看参考处原博客。
剩下的做法就和FFT一样啦。

谢幕:

(是的我没有讲实现,因为我也是今天才学完)
(然而学完和代码能写出来是两个概念,所以先咕咕咕了)

你可能感兴趣的:(模板)