FFT多项式计算
傅里叶变换是一个很大的概念,里面包含太多太多东西,整篇在傅里叶变换中只是一个小应用而已。就我的理解,描述快速傅里叶变换优化多项式乘法的原理和具体实践。
傅里叶变换
傅里叶变换是一种线性积分变换,用于信号在时域(或空域)和频域之间的变换。
一般情况下,若傅里叶变换一词不加任何限定语,则指的是连续傅里叶变换。(连续)傅里叶变换定义如下:傅里叶变换将可积函数f:R→C表示成复指数函数的积分或级数性质。
f^(ξ)=∫−∞∞f(x)e−2πixξdx
其中ξ为任意实数。自变量x表示时间,变换变量ξ表示频率。在多数情况下,傅里叶变换是可逆的,即可以通过f^得到其原函数f,定义如下:
f(ξ)=∫−∞∞f^(ξ)e2πiξxdξ
离散傅里叶变换
为了在科学计算和数字信号处理等领域使用计算机进行傅里叶变换,必须将函数xn定义在离散点而非连续域内,且须满足有限性和周期性等提交。这种情况下,使用离散傅里叶变换(Discrete Fourier Transform,DFT),DFT是傅里叶变换在时域和频域上都呈离散的形式,函数定义如下:
x^n=k=0∑N−1xke−iN2πknn=0,⋯,N−1
其逆变换为:
xk=N1n=0∑N−1x^ne−iN2πnkk=0,⋯,N−1
单位根
数学上,n次单位根是n次幂为1的负数。定义为ωn=1(n=1,2,3,⋯),ω为n次单位根。单位的n次根有n个:e2πki/n(k=0,1,2,⋯,n−1)。我们用ωn0,ωn1,⋯,ωnn−1表示这个n个n次根,ωni=e2πki/n。
结合欧拉公式,可以很容易得看出,ωnk=e2πnki=cos(2πnk)+i⋅sin(2πnk)=1
对于n≥0,k≥0,d≥0,有ωdndk=(e2πi/dn)dk=(e2πi/n)k=ωnk。
对于∀n满足n>0且2∣n,有ωnk+2n=en2πi(k+2n)=e2πik/neπi=ωnk(cosπ+isinπ)=−ωnk
多项式
形如a0+a1x+⋯+anxn的多项式被称为多项式,记为A(x)=∑i=0n−1aixi,其中ai,i=0,1,⋯,n为系数,用系数表示多项式为a=(a0,a1,⋯,an),a就为多项式的系数表示法。
我们知道通过n+1个不同的点{(x0,y0),⋯,(xn,yn)}就可以唯一确定一个n次多项式,{(x0,y0),⋯,(xn,yn)}为多项式的点值表示法。
快速傅里叶变换
快速傅里叶变换(Fast Fourier Transform,FFT),是计算离散傅里叶变换及其逆变换的快算算法。按照DFT的定义,计算一个长为n的序列需要的时间复杂度为O(n2),而FFT通过把DFT矩阵分解为稀疏因子之积来快速计算变换,从而将时间复杂度降到O(nlogn)。
库里-图基快速傅里叶变换算法是最常见的FFT算法,基于分治策略,可将时间复杂度降为O(nlogn)。
假设一个多项式表示为A(x)=∑i=0n−1aixi,用系数表示法表示就是向量a=(a0,a1,⋯,an)。用点值表示,将n个n次单位根ωn0,ωn1,⋯,ωnn−1带入到多项式为A(ωnk)=∑i=0n−1aiωnki,k=0,1,⋯,n−1。现在我们和DFT定义的公式比对,A(ωni)就是是ai的DFT,那么ai就是A(ωni)就是的IDFT。记为A(ωni)=DFT(ai)。
下面我们就依据上面陈列的基础基础,对A(ωnk)进行公式推导:
A(ωnk)=∑i=0n−1aiωnki=∑i=02n−1a2iωn2ki+ωnk∑i=02n−1a2i+1ωn2ki=∑i=02n−1a2iω2nki+ωnk∑i=02n−1a2i+1ω2nki
又由于
A(ωnk+2n)=∑i=0n−1aiωn(k+2n)i=∑i=02n−1a2iωn2ki+ωnk+2n∑i=02n−1a2i+1ωn2ki=∑i=02n−1a2iω2nki−ωnk∑i=02n−1a2i+1ω2nki
可以看出,对于k<2n时,只要代入ω2n,ω2n2,⋯,ω2n2n−1,就可以求出A(ωnk)和A(ωnk+2n)。
A(ωnk)=A(ωnk+2n)=A1(ω2nk)+A2(ω2nk)A1(ω2nk)−A2(ω2nk)(公式1)
不递归的伪代码如下所示:
void get_rev(int bit)
{
for(int i=0; i<(1<<bit); i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
}
/**
n为2的幂次方
op==1,DFT
op==-1,IDFT
*/
void fft(cd *a,int n,int op)
{
for(int i=0; i<n; i++)
{
if(i<rev[i])
{
cd tmp = a[i];
a[i] = a[rev[i]];
a[rev[i]] = tmp;
}
}
for(int step=1; step<n; step<<=1)
{
cd wn=exp(cd(0,op*PI/step));
for(int j=0; j<n; j+=step<<1)
{
cd wnk(1,0);
for(int k=j; k<j+step; k++)
{
cd x=a[k];
cd y=wnk*a[k+step];
a[k]=x+y;
a[k+step]=x-y;
wnk*=wn;
}
}
}
if(op==-1)
{
for(int i=0; i<n; i++)
a[i]/=n;
}
}
费了好大经历才把代码和原理结合在一起,在网上搜了好久都没有理解为何代码要这样写,后来在离散傅里叶变换和快速傅里叶变换课件中搜到一张图,瞬间明白,其中的子操作被称为蝴蝶操作。如下图所示,展示了一个8个点的DFT是怎样分解成4个2个点的DFT。
蝴蝶操作见下图,类似于蝴蝶形状,是不是和上述公式1的计算相符合。
FFT实现多项式乘法
当现在有多项式A(x)和多项式B(x),令C(x)=A(x)⋅B(x),那么如何求C的系数cj呢?
先引入一个定理,函数卷积的傅里叶变换是函数傅里叶变换的乘积。$C\left(x\right) 中次数为j的项由A \left(x\right) 中次数为k的项和B\left(x\right) 中次数j-k为的项相乘得到。那么我们先通过C\left(\omega_{n}\right) = A\left(\omega_{n}\right) B\left(\omega_{n},\right),然后在通过IDFT计算c_{j}$。
fft(a,n,1);
fft(b,n,1);
for(int i=0; i<s; i++) a[i]*=b[i];
fft(a,n,-1);
备注
欧拉公式eix=cosx+i⋅sinx
参考