FFT 入门

转载自:http://www.gatevin.moe/acm/fft%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

写在前面

    关于学习FFT算法的资料个人最推荐的还是算法导论上的第30章(第三版), 多项式与快速傅里叶变换, 基础知识都讲得很全面

    这篇文章即是博主在阅读FFT算法的相关学习资料之后的一些总结, 作为日后复习查阅的材料来源, 同时也可以作为读者学习FFT算法的一份资料

FFT算法基本概念

    FFT(FastFourierTransformation)

即快速傅里叶变换, 是离散傅里叶变换的加速算法, 可以在 O(nlogn) 的时间里完成 DFT , 利用相似性也可以在同样复杂度的时间里完成逆 DFT

    DFT(DiscreteFourierTransform)

即离散傅里叶变换, 这里主要就是多项式的系数向量转换成点值表示的过程

    在ACM-ICPC竞赛中, FFT算法常被用来为多项式乘法加速, 即在 O(nlogn)

复杂度内完成多项式乘法, 当然实际应用不仅仅限于这些, 时常出现需要构造多项式相乘来进行计数的问题, 也需要用FFT算法来解决, 相关的几个问题在本文中也会提及

FFT算法需要的基础数学知识

    多项式相关:

     多项式相关的定义:一个以 x

为变量的多项式定义在代数域F上, 将函数 A(x)

表示为形式和

Σj=0n1ajxj
, 称 aj 为系数, 所有系数属于代数域 F , 如果一个多项式的最高非零系数是 ak , 那么成这个多项式的 次数 k , 记做 degree(A)=k , 任何一个严格大于一个多项式次数的整数都是该多项式的 次数界

    关于多项式的加法和乘法相信看到这篇博客的读者都会最基本的中学的算法, 在计算的时候, 如果采用传统的中学的计算方法, 多项式加法的时间复杂度是 O(n)

, 乘法的时间复杂度是 O(n2)

(n是两个多项式A和B的次数).

    多项式的表示

    在平常的学习中, 最常见的是多项式的系数表达方式

    次数界为n的多项式 A(x)=a0+a1x+a2x2+...+an1xn1

的系数表达为一个由系数组成的向量 a=(a1,a2,...an1)


    但是多项式还有一个比较常用的表示方法, 即多项式的点值表达方式

    一个次数界为n的多项式 A(x)=a0+a1x+a2x2+...+an1xn1

的点值表达就是一个由n个点对组成的集合

{(x0,y0),(x1,y1),...,(xn1,yn1)}
使得对任意的整数 k=0,1,..n1 , xk 各不相同, 且 yk=A(xk)

    关于点值表达的正确性证明:

    对于任意n个点值对组成的集合 {(x0,y0),(x1,y1),...,(xn1,yn1)}

, 如果存在一个次数界为n的多项式 A(x)

过这n个点, 那么

111x0x1xn1x20x21x2n1xn10xn11xn1n1a0a1an1=y0y1yn1

最左边这个n*n的矩阵称为范德蒙德矩阵, 可以用数学归纳法证明它的行列式值为

Π0j<kn1(xkxj)
xk 两两不相同时明显这个行列式的值不为0, 该矩阵可逆, 于是存在唯一解, 所以多项式的点值表达是合理的

    相应的通过n个点的坐标直接确定多项式各个系数的值的方法是存在的, 感兴趣的读者可以查询拉格朗日公式的相关资料, 利用拉格朗日插值公式可以在 O(n2)

的时间复杂度内得到多项式的系数表达, 这个也是算法导论中的一个习题

    点值表达方式下多项式的乘法, 不难发现, 如果多项式 A(x),B(x)

的点值表示分别是

{(x0,ya0),(x1,ya1),...,(xn1,yan1)}

{(x0,yb0),(x1,yb1),...,(xn1,ybn1)}
那么如果多项式 C(x)=A(x)B(x) , 那么 C(x) 的点值表达是

{(x0,ya0yb0),(x1,ya1yb1),...,(xn1,yan1ybn1)}

卷积

    对于两个多项式的系数向量 a=(a0,a1,..,an1)

b=(b0,b1,..,bn1) , 两个多项式相乘得到的多项式的系数向量 c=(c0,c1,..,c2n2) 满足 cj=Σjk=0 akbjk , 称系数向量c是输入向量a和b的卷积, 记作 c=ab

     在简单的多项式乘法的计算方法中, 每一个多项式的系数都通过系数表示方式下卷积的方式来进行计算, 时间复杂度是 O(n2)

, 但是FFT是先将多项式的从系数表示法转换成点值表示法(可以在 O(nlogn) 的时间复杂度下完成, 也就是加速的DFT变换, 然后在点值表示法下进行乘积计算, 在 O(n) 的时间复杂度内得到结果的点值表示法, 然后进行逆DFT变换, 在 O(nlogn)

的时间复杂度下完成逆DFT变换得到系数表示法

而要理解DFT, 则需要一定复数上的数学知识:

复数相关的基础知识:

单位复数根:n次单位复数根指的是满足 ωn=1

的所有复数 ω , n次单位复数根刚好有n个, 他们是 e2kπni , 其中i是复数单位, k=0,1,2...n1

, 在复平面上这n个根均匀的分布在半径为1的圆上, 关于复数指数的定义如下:

eui=cos(u)+sin(u)i
其中 ωn=e2iπn 倍称为主n次单位根(这个定义好像接下来没用到= =)

关于复数根的几个定理和引理:

消去引理: 对任何整数 n0,k0,d0

ωdkdn=ωkn

证明: ωdkdn=(e2iπdn)dk=(e2iπn)k=ωkn

一个推论: 对任意偶数 n > 0 有 ωn2n=ω2=1

证明:设n = 2*k那么  ωn2n=ωk2k=ω2=eπ=cos(π)+sin(π)i=1

折半引理:如果n > 0是偶数, 那么n个n次单位复数根的平方的集合就是n/2个n/2次单位复数根的集合

证明:实际上这个引理就是证明了 (ωk+n2n)2=ω2k+nn=ω2knωnn=(ωkn)2

折半引理对于采用分治对多项式系数向点值表达的转换有很大作用, 保证了递归的子问题是原问题规模的一半


求和引理:对任意整数 n1

和不能被n整除的非负整数k, 有

Σj=0n1(ωkn)j=0

这个问题通过等比数列求和公式就可以得到: Σn1j=0(ωkn)j=(ωkn)k1ωkn1=1k1ωkn1=0

DFT与FFT, 以及逆DFT

DFT

在DFT变换中, 希望计算多项式A(x)在复数根 ω0n,ω1n,...,ωn1n

处的值, 也就是求

yk=A(ωkn)=Σj=0n1ajωkjn
称向量 y=(y0,y1,...,yn1) 是系数向量 a=(a0,a1,...,an1) 的离散傅里叶变换, 记为 y=DFTn(a)

FFT

直接计算DFT的复杂度是 O(n2)

, 而利用复数根的特殊性质的话, 可以在 O(nlogn)

的时间内完成, 这个方法就是FFT方法, 在FFT方法中采用分治策略来进行操作, 主要利用了消去引理之后的那个推论

在FFT的策略中, 多项式的次数是2的整数次幂, 不足的话再前面补0, 每一步将当前的多项式A(x), 次数是2的倍数, 分成两个部分:

A[0](x)=a0+a2x+a4x2+...+an2xn21

A[1](x)=a1+a3x1+a5x2+...+an1xn21
于是就有了

A(x)=A[0](x2)+xA[1](x2)
那么我们如果能求出次数界是 n2 的多项式 A[0](x) A[1](x) 在n个n次单位复数根的平方处的取值就可以了, 即在

(ω0n)2,(ω1n)2,(ω2n)2,...,(ωn1n)2
处的值, 那么根据折半引理, 这n个数其实只有 n2 个不同的值, 也就是说, 对于每次分出的两个次数界 n2 的多项式, 只需要求出其 n2 个不同的值即可, 那么问题就递归到了原来规模的一半, 也就是说如果知道了两个子问题的结果, 当前问题可以在两个子问题次数之和的复杂度内解决, 那么这样递归问题的复杂度将会是O(nlogn)的, 用 a=(a0,a1,...,an1) 表示系数向量, y=(y0,y1,...,yn1) 表示离散变换之后的向量,  这里给出将算导上的代码翻译出来的C++代码实现(以解决算法第三版大论第三十章的一个习题, 求(0, 1, 2, 3)的DFT为例):

你可能感兴趣的:(FFT)