参考: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×(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)=∑n−1i=0aixi,B(x)=∑n−1i=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
)
的解法,但是实在是太慢了。
考虑到一个
n−1
n
−
1
次多项式可以看做是定义在复数域上的函数,则我们一定可以找到n个点来唯一确定这个函数。
当然我们也可以通过这些点来表示这个多项式。
假设:
A(x)
A
(
x
)
被表示为:
<(x0,ya0),(x1,ya1),…,(x2n−2,ya2n−2)>
<
(
x
0
,
y
a
0
)
,
(
x
1
,
y
a
1
)
,
…
,
(
x
2
n
−
2
,
y
a
2
n
−
2
)
>
<script type="math/tex" id="MathJax-Element-144"><(x_0,y_{a_0}),(x_1,y_{a_1}),\ldots,(x_{2n-2},y_{a_{2n-2}})></script>
B(x)
B
(
x
)
被表示为:
<(x0,yb0),(x1,yb1),…,(x2n−2,yb2n−2)>
<
(
x
0
,
y
b
0
)
,
(
x
1
,
y
b
1
)
,
…
,
(
x
2
n
−
2
,
y
b
2
n
−
2
)
>
<script type="math/tex" id="MathJax-Element-146"><(x_0,y_{b_0}),(x_1,y_{b_1}),\ldots,(x_{2n-2},y_{b_{2n-2}})></script>
显然
A(x)×B(x)
A
(
x
)
×
B
(
x
)
被表示为:
<(x0,ya0yb0),(x1,ya1yb1),…,(x2n−2,ya2n−2yb2n−2)>
<
(
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
)
>
<script type="math/tex" id="MathJax-Element-148"><(x_0,y_{a_0}y_{b_0}),(x_1,y_{a_1}y_{b_1}),\ldots,(x_{2n-2},y_{a_{2n-2}}y_{b_{2n-2}})></script>
这里多取了点的原因在于 A(x)×B(x) A ( x ) × B ( x ) 是一个 2n−2 2 n − 2 次多项式,则至少要取 2n−1 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+…+an−2xn2+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+…+an−1xn2+1
A
1
(
x
)
=
a
1
+
a
3
x
+
a
5
x
2
+
…
+
a
n
−
1
x
n
2
+
1
其实就是按照系数下标的奇偶性分类了一下。
此时我们再令取点的
x
x
值为
我们发现把
x
x
平方后我们的取值瞬间缩小了一半,而原式唯一变化的就是前的符号。
看起来我们似乎找到了
O(nlogn)
O
(
n
l
o
g
n
)
的可行方案。
但是很可惜,这样优秀的
x
x
取值的性质只会保留一次,也就是说我们只是得到了一个。
如何才能每次将问题的规模缩小一半是我们的目标。
插曲:
有个人告诉你:不如试试 Xn=cos2πn+i×sin2πn X n = c o s 2 π n + i × s i n 2 π n 的 0…n−1 0 … n − 1 次方作为 x x 的取值。
这块大家一直有个疑惑:这是怎么构造出来的啊?
事实上傅里叶变换最早是应用于信号处理上的,傅里叶提出:任何连续周期信号可以由一组适当的正弦曲线组合而成。
多项式可以看做非连续周期信号,然后通过各种奇妙的姿势让它逼近正弦曲线的组合形,详情可以看松松松WC2018的课件。
“逼近”显然用到了微积分,不适合初学者,所以就直接跳过了。(其实我也不会……)
(再多说一点吧,其实上面和下面的数学推理完全可以从物理层面理解,还是可以参考松松松WC2018的课件)
继续:
那么令取点的值为
<X0n,X1n,…,Xn−1n>
<
X
n
0
,
X
n
1
,
…
,
X
n
n
−
1
>
<script type="math/tex" id="MathJax-Element-274">
</script>
我们可知:
(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
对取模显然不会对答案造成影响。
于是我们有
Xkn=Xk%nn
X
n
k
=
X
n
k
%
n
那么显然对于 <(X0n)2,(X1n)2,…,(Xn−1n)2> < ( X n 0 ) 2 , ( X n 1 ) 2 , … , ( X n n − 1 ) 2 > <script type="math/tex" id="MathJax-Element-285"><(X_n^0)^2,(X_n^1)^2,\ldots,(X_n^{n-1})^2></script>
它等效于 <X0n2,X1n2,…,Xn2−1n2,X0n2,X1n2,…,Xn2−1n2> < 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 > <script type="math/tex" id="MathJax-Element-286"> </script>
我们好像看到了 O(nlogn) O ( n l o g n ) 的曙光了。
尾声:
显然我们可以对
x
x
的取值折半,然后对于左右区间的值递归下去即可。
Q1:诶等等,“再试”里面的内容好像没有应用上啊……
A1:那就转化一下,其实我们只需要求一个区间的
A0(x)
A
0
(
x
)
和
A1(x)
A
1
(
x
)
值递归下去求
A(x)
A
(
x
)
即可。
也就是说其实我们是得到了:
<(A0)0,(A0)1,…,(A0)n2−1,(A1)0,(A1)1,…,(A1)n2−1>
<
(
A
0
)
0
,
(
A
0
)
1
,
…
,
(
A
0
)
n
2
−
1
,
(
A
1
)
0
,
(
A
1
)
1
,
…
,
(
A
1
)
n
2
−
1
>
<script type="math/tex" id="MathJax-Element-293"><(A_0)_0,(A_0)_1,\ldots,(A_0)_{\frac{n}{2}-1},(A_1)_0,(A_1)_1,\ldots,(A_1)_{\frac{n}{2}-1}></script>
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,…,An−1> < A 0 , A 1 , … , A n − 1 > <script type="math/tex" id="MathJax-Element-295"> </script>
(其中 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?
其实就是这样,并且事实上就是填
((X−kn)m)/n
(
(
X
n
−
k
)
m
)
/
n
,具体证明过程看参考处原博客。
剩下的做法就和FFT一样啦。
谢幕:
(是的我没有讲实现,因为我也是今天才学完)
(然而学完和代码能写出来是两个概念,所以先咕咕咕了)