必威体育Betway必威体育官网
当前位置:首页 > IT技术

十分简明易懂的FFT(快速傅里叶变换)

时间:2019-09-27 10:15:36来源:IT技术作者:seo实验室小编阅读:89次「手机版」
 

fft

FFT前言

快速傅里叶变换 (fast Fourier transform),即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,简称FFT。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,fft算法计算量的节省就越显著。

FFT(Fast Fourier Transformation) 是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。

——百度百科

FFT(Fast Fourier Transformation),中文名快速傅里叶变换,用来加速多项式乘法

朴素高精度乘法时间O(n2)O(n^2)O(n2),但FFTFFTFFT能O(nlog2n)O(n\log_2 n)O(nlog2​n)的时间解决

FFTFFTFFT名字逼格高,也难懂,其他教程写得让人看不太懂,于是自己随便写一下

  • 建议对复数三角函数相关知识有所耳闻 (不会也无所谓)

  • 下面难懂的点我会从网上盗


多项式的系数表示法和点值表示法

  • FFTFFTFFT其实是一个用O(nlog2n)O(n\log_2n)O(nlog2​n)的时间将一个用系数表示的多项式转换成它的点值表示的算法

  • 多项式的系数表示和点值表示可以互相转换

系数表示法

一个n-1次n项多项式f(x)f(x)f(x)可以表示为f(x)=i=0n1aixif(x)=\sum^{n-1}_{i=0}a_ix^if(x)=∑i=0n−1​ai​xi

也可以用每一项的系数来表示f(x)f(x)f(x),即f(x)={a0,a1,a2,...,an1}f(x)=\{a_0,a_1,a_2,...,a_{n-1} \}f(x)={a0​,a1​,a2​,...,an−1​}

这就是系数表示法,也就是平时数学课上用的方法

点值表示法

  • 把多项式放到平面直角坐标系里面,看成一个函数

  • nnn个不同的xxx代入,会得出nnn个不同的yyy,在坐标系内就是nnn个不同的点

  • 那么这nnn个点唯一确定该多项式,也就是有且仅有一个多项式满足k,f(xk)=yk∀k,f(x_k)=y_k∀k,f(xk​)=yk​

  • 理由很简单,把nnn条式子联立起来成为一个有n条方程的n元方程组,每一项的系数都可以解出来

那么f(x)f(x)f(x)还可以用f(x)={(x0,f(x0)),(x1,f(x1)),(x2,f(x2)),...,(xn1,f(xn1))}f(x)=\{(x_0,f(x_0)),(x_1,f(x_1)),(x_2,f(x_2)),...,(x_{n-1},f(x_{n-1}))\}f(x)={(x0​,f(x0​)),(x1​,f(x1​)),(x2​,f(x2​)),...,(xn−1​,f(xn−1​))}来表示

这就是点值表示法


高精度乘法下两种多项式表示法的区别

对于两个用系数表示的多项式,我们把它们相乘

设两个多项式分别为A(x),B(x)A(x),B(x)A(x),B(x)

我们要枚举AAA每一位的系数与BBB每一位的系数相乘

那么系数表示法做多项式乘法时间复杂度O(n2)O(n^2)O(n2)

但两个用点值表示的多项式相乘,只需要O(n)O(n)O(n)的时间

什么意思呢?

设两个点值多项式分别为f(x)={(x0,f(x0)),(x1,f(x1)),(x2,f(x2)),...,(xn1,f(xn1))}f(x)=\{(x_0,f(x_0)),(x_1,f(x_1)),(x_2,f(x_2)),...,(x_{n-1},f(x_{n-1}))\}f(x)={(x0​,f(x0​)),(x1​,f(x1​)),(x2​,f(x2​)),...,(xn−1​,f(xn−1​))}g(x)={(x0,g(x0)),(x1,g(x1)),(x2,g(x2)),...,(xn1,g(xn1))}g(x)=\{(x_0,g(x_0)),(x_1,g(x_1)),(x_2,g(x_2)),...,(x_{n-1},g(x_{n-1}))\}g(x)={(x0​,g(x0​)),(x1​,g(x1​)),(x2​,g(x2​)),...,(xn−1​,g(xn−1​))}

设它们的乘积是h(x)h(x)h(x),那么h(x)={(x0,f(x0)g(x0)),(x1,f(x1)g(x1)),...,(xn1,f(xn1)g(xn1))}h(x)=\{(x_0,f(x_0)·g(x_0)),(x_1,f(x_1)·g(x_1)),...,(x_{n-1},f(x_{n-1})·g(x_{n-1}))\}h(x)={(x0​,f(x0​)⋅g(x0​)),(x1​,f(x1​)⋅g(x1​)),...,(xn−1​,f(xn−1​)⋅g(xn−1​))}

所以这里的时间复杂度只有一个枚举的O(n)O(n)O(n)

  • 突然感觉高精度乘法能O(n)O(n)O(n)暴艹一堆题?

  • 但是朴素的系数表示法转点值表示法的算法还是O(n2)O(n^2)O(n2)的,逆操作类似

  • 朴素系数转点值的算法叫DFT(离散傅里叶变换),点值转系数叫IDFT(离散傅里叶逆变换)

  • 难道高精度乘法只能O(n2)O(n^2)O(n2)了吗?


DFT前置知识&技能

复数

毕竟高中有所以不多说

我们把形如a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位。当虚部等于零时,这个复数可以视为实数;当z的虚部不等于零时,实部等于零时,常称z为纯虚数。复数域是实数域的代数闭包,也即任何复系数多项式在复数域中总有根。 复数是由意大利米兰学者卡当在十六世纪首次引入,经过达朗贝尔、棣莫弗、欧拉、高斯等人的工作,此概念逐渐为数学家所接受。

——百度百科

初中数学老师会告诉你没有1\sqrt{-1}−1​,但仅限RRR

扩展至复数集CCC,定义i2=1i^2=-1i2=−1,一个复数zzz可以表示为z=a+bi(a,bR)z=a+bi(a,b\in R)z=a+bi(a,b∈R)

其中aaa称为实部bbb称为虚部iii称为虚数单位

  • 在复数集中就可以用iii表示负数的平方根,如7=7i\sqrt{-7}=\sqrt{7}i−7​=7​i

还可以把复数看成平面直角坐标系上的一个点,比如下面

xxx轴就是实数集中的坐标轴,yyy轴就是虚数单位iii轴

这个点(2,3)(2,3)(2,3)表示的复数就是2+3i2+3i2+3i,或者想象它代表的向量(2,3)(2,3)(2,3)

其实我们还可以自己想象 (其实没有这种表达方式 它可以表示为(13,θ)(\sqrt{13},\theta)(13​,θ)

一个复数zzz的定义为它到原点的距离,记为z=a2+b2|z|=\sqrt{a^2+b^2}∣z∣=a2+b2

一个复数z=a+biz=a+biz=a+bi的共轭复数abia-bia−bi(虚部取反),记为z=abi\overline{z}=a-biz=a−bi

复数的运算

复数不像点或向量,它和实数一样可以进行四则运算

设两个复数分别为z1=a+bi,z2=c+diz_1=a+bi,z_2=c+diz1​=a+bi,z2​=c+di,那么

z1+z2=(a+c)+(b+d)iz_1+z_2=(a+c)+(b+d)iz1​+z2​=(a+c)+(b+d)iz1z2=(acbd)+(ad+bc)iz_1z_2=(ac−bd)+(ad+bc)iz1​z2​=(ac−bd)+(ad+bc)i

复数相加也满足平行四边形法则

这张是从网上盗的

AB+AD=ACAB+AD=ACAB+AD=AC

复数相乘还有一个值得注意的小性质

(a1,θ1)(a2,θ2)=(a1a2,θ1+θ2)(a_1,\theta_1)*(a_2,\theta_2)=(a_1a_2,\theta_1+\theta_2)(a1​,θ1​)∗(a2​,θ2​)=(a1​a2​,θ1​+θ2​)

模长相乘,极角相加


DFT(离散傅里叶变换)

  • 一定注意从这里开始所有的nnn都默认为222的整数次幂

对于任意系数多项式转点值,当然可以随便取任意nnn个xxx值代入计算

但是暴力计算xk0,xk1,...,xkn1(k[0,n))x_k^0,x_k^1,...,x_k^{n-1}(k\in[0,n))xk0​,xk1​,...,xkn−1​(k∈[0,n))当然是O(n2)O(n^2)O(n2)的时间

其实可以代入一组神奇xxx,代入以后不用做那么多的次方运算

这些xxx当然不是乱取的,而且取这些xxx值应该就是 傅里叶 的主意了

考虑一下,如果我们代入一些xxx,使每个xxx的若干次方等于111,我们就不用做全部的次方运算了

±1±1±1是可以的,考虑虚数的话±i±i±i也可以,但只有这四个数远远不够

  • 傅里叶说:这个圆圈上面的点都可以做到

以原点为圆心,画一个半径为111的单位圆

那么单位圆上所有的点都可以经过若干次次方得到111

傅里叶说还要把它给nnn等分了,比如此时n=8n=8n=8

橙色点即为n=8n=8n=8时要取的点,从(1,0)(1,0)(1,0)点开始,逆时针从000号开始标号,标到777号

记编号为kkk的点代表的复数值为ωnk\omega_n^kωnk​,那么由模长相乘,极角相加可知(ωn1)k=ωnk(\omega_n^1)^k=\omega_n^k(ωn1​)k=ωnk​

其中ωn1\omega_n^1ωn1​称为nnn次单位根,而且每一个ω\omegaω都可以求出 (我三角函数不好)

ωnk=coskn2π+isinkn2π\omega_n^k=\cos{k\over n}2π+i\sin{k\over n} 2πωnk​=cosnk​2π+isinnk​2π

或者说也可以这样解释这条式子

注意sin2θ+cos2θ=1sin^2\theta+cos^2\theta=1sin2θ+cos2θ=1什么的,就容易理解了

那么ωn0,ωn1,...,ωnn1\omega^0_n,\omega^1_n,...,\omega^{n-1}_nωn0​,ωn1​,...,ωnn−1​即为我们要代入的x0,x1,...,xn1x_0,x_1,...,x_{n-1}x0​,x1​,...,xn−1​


单位根的一些性质

FFTFFTFFT的过程中需要用到ω\omegaω的一些性质

ωnk=ω2n2k\omega^k_n=\omega^{2k}_{2n}ωnk​=ω2n2k​

  • 它们表示的点(或向量)表示的复数是相同的

  • 证明

  • ωnk=coskn2π+isinkn2π=cos2k2n2π+isin2k2n2π=ω2n2k\omega^k_n=cos{k\over n}2π+isin{k\over n} 2π=cos{2k\over 2n}2π+isin{2k\over 2n} 2π=\omega^{2k}_{2n}ωnk​=cosnk​2π+isinnk​2π=cos2n2k​2π+isin2n2k​2π=ω2n2k​

ωnk+n2=ωnk\omega^{k+{n \over 2}}_n=-\omega_n^kωnk+2n​​=−ωnk​

  • 它们表示的点关于原点对称,所表示的复数实部相反,所表示的向量等大反向

  • 证明

  • ωnn2=cosn2n2π+isinn2n2π=cosπ+isinπ=1\omega^{n\over 2}_n=cos{{n\over 2}\over n}2\pi+isin{{n\over 2}\over n}2\pi=cos\pi+isin\pi=-1ωn2n​​=cosn2n​​2π+isinn2n​​2π=cosπ+isinπ=−1

  • (这个东西和eix=cosx+isinxe^{ix}=cosx+isinxeix=cosx+isinx与eiπ+1=0e^{i\pi}+1=0eiπ+1=0有点关系,我不会就不讲了

ωn0=ωnn\omega^0_n=\omega^n_nωn0​=ωnn​

  • 都等于111,或1+0i1+0i1+0i

FFT(快速傅里叶变换)

虽然DFTDFTDFT搞出来一堆很牛逼ω\omegaω作为代入多项式的xxx值

但只是代入这类特殊xxx值法的变换叫做DFTDFTDFT而已,还是要代入单位根暴力计算

  • DFT还是暴力O(n2)O(n^2)O(n2)

DFTDFTDFT可以分治来做,于是 FFT(快速傅里叶变换) 就出来了

首先设一个多项式A(x)A(x)A(x)

A(x)=i=0n1aixi=a0+a1x+a2x2+...+an1xn1A(x)=\sum^{n-1}_{i=0}a_ix^i=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}A(x)=i=0∑n−1​ai​xi=a0​+a1​x+a2​x2+...+an−1​xn−1

A(x)A(x)A(x)下标的奇偶性A(x)A(x)A(x)分成两半,右边再提一个xxx

A(x)=(a0+a2x2+...+an2xn2)+(a1x+a3x3+...+an1xn1)A(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})A(x)=(a0​+a2​x2+...+an−2​xn−2)+(a1​x+a3​x3+...+an−1​xn−1)

=(a0+a2x2+...+an2xn2)+x(a1+a3x2+...+an1xn2)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+x(a_1+a_3x^2+...+a_{n-1}x^{n-2})=(a0​+a2​x2+...+an−2​xn−2)+x(a1​+a3​x2+...+an−1​xn−2)

两边好像非常相似,于是再设两个多项式A1(x),A2(x)A_1(x),A_2(x)A1​(x),A2​(x),令

A1(x)=a0+a2x+a4x2+...+an2xn21A_1(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{{n\over 2}-1}A1​(x)=a0​+a2​x+a4​x2+...+an−2​x2n​−1A2(x)=a1+a3x+a5x2+...+an1xn21A_2(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{{n \over 2}-1}A2​(x)=a1​+a3​x+a5​x2+...+an−1​x2n​−1

比较明显得出

A(x)=A1(x2)+xA2(x2)A(x)=A_1(x^2)+xA_2(x^2)A(x)=A1​(x2)+xA2​(x2)

再设k&lt;n2k&lt;{n\over 2}k<2n​,把ωnk\omega^k_nωnk​作为xxx代入A(x)A(x)A(x)(接下来几步变换要多想想单位根的性质)

A(ωnk)=A1((ωnk)2)+ωnkA2((ωnk)2)A(\omega^k_n)=A_1((\omega^k_n)^2)+\omega^k_nA_2((\omega^k_n)^2)A(ωnk​)=A1​((ωnk​)2)+ωnk​A2​((ωnk​)2)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ωn2k)+ωnkA2(ωn2k)=A_1(\omega^{2k}_n)+\omega^k_nA_2(\omega^{2k}_n)=A_1(\omega^k_{n\over2})+\omega^k_nA_2(\omega^k_{n\over 2})=A1​(ωn2k​)+ωnk​A2​(ωn2k​)=A1​(ω2n​k​)+ωnk​A2​(ω2n​k​)

那么对于A(ωnk+n2)A(\omega^{k+{n\over2}}_n)A(ωnk+2n​​)的话,代入ωnk+n2\omega^{k+{n \over 2}}_nωnk+2n​​

A(ωnk+n2)=A1(ωn2k+n)+ωnk+n2A2(ωn2k+n)A(\omega^{k+{n\over 2}}_n)=A_1(\omega^{2k+n}_n)+\omega^{k+{n\over 2}}_nA_2(\omega^{2k+n}_n)A(ωnk+2n​​)=A1​(ωn2k+n​)+ωnk+2n​​A2​(ωn2k+n​)=A1(ωn2kωnn)ωnkA2(ωn2kωnn)=A_1(\omega^{2k}_n\omega^n_n)-\omega^k_nA_2(\omega^{2k}_n\omega^n_n)=A1​(ωn2k​ωnn​)−ωnk​A2​(ωn2k​ωnn​)=A1(ωn2k)ωnkA2(ωn2k)=A1(ωn2k)ωnkA2(ωn2k)=A_1(\omega^{2k}_n)-\omega^k_nA_2(\omega^{2k}_n)=A_1(\omega^k_{n\over2})-\omega^k_nA_2(\omega^k_{n\over2})=A1​(ωn2k​)−ωnk​A2​(ωn2k​)=A1​(ω2n​k​)−ωnk​A2​(ω2n​k​)

  • 发现了什么?

A(ωnk)A(\omega^k_n)A(ωnk​)和A(ωnk+n2)A(\omega^{k+{n\over2}}_n)A(ωnk+2n​​)两个多项式后面一坨东西只有符号不同

就是说,如果已知A1(ωn2k)A_1(\omega^k_{n\over 2})A1​(ω2n​k​)和A2(ωn2k)A_2(\omega^k_{n\over 2})A2​(ω2n​k​)的值,我们就可以同时知道A(ωnk)A(\omega^k_n)A(ωnk​)和A(ωnk+n2)A(\omega^{k+{n\over2}}_n)A(ωnk+2n​​)的值

现在我们就可以递归分治来搞FFTFFTFFT了

每一次回溯时只扫当前前面一半的序列,即可得出后面一半序列的答案

n==1n==1n==1时只有一个常数项,直接returnreturnreturn

时间复杂度O(nlog2n)O(n\log_2n)O(nlog2​n)


IFFT(快速傅里叶逆变换)

想一下,我们不仅要会FFTFFTFFT,还要会IFFT(快速傅里叶逆变换)

我们把两个多项式相乘 (也叫求卷积),做完两遍FFTFFTFFT也知道了积的多项式的点值表示

可我们平时用系数表示的多项式,点值表示没有意义

  • 怎么把点值表示的多项式快速转回系数表示法?

  • IDFTIDFTIDFT暴力O(n2)O(n^2)O(n2)做?其实也可以用FFTFFTFFT用O(nlog2n)O(n\log_2n)O(nlog2​n)的时间搞

你有没有想过为什么傅里叶是把ωnk\omega^k_nωnk​作为xxx代入而不是别的什么数?

原因是有的但是有我也看不懂

由于我是沙雕所以只用记住一个结论

  • 一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以nnn即为原多项式的每一项系数

意思就是说FFTFFTFFT和IFFTIFFTIFFT可以一起搞


朴素版FFT板子

c++c++c++有自带的复数模板complexcomplexcomplex库

a.real()a.real()a.real()即表示复数aaa的实部

#include<complex>
#define cp complex<double>

void fft(cp *a,int n,int inv)//inv是取共轭复数的符号
{
    if (n==1)return;
    int mid=n/2;
    static cp b[MAXN];
    fo(i,0,mid-1)b[i]=a[i*2],b[i+mid]=a[i*2+1];
    fo(i,0,n-1)a[i]=b[i];
    fft(a,mid,inv),fft(a+mid,mid,inv);//分治
    fo(i,0,mid-1)
    {
        cp x(cos(2*pi*i/n),inv*sin(2*pi*i/n));//inv取决是否取共轭复数
        b[i]=a[i]+x*a[i+mid],b[i+mid]=a[i]-x*a[i+mid];
    }
    fo(i,0,n-1)a[i]=b[i];
}

两个多项式a,ba,ba,b相乘再转系数多项式ccc,通常只用打这么一小段

	cp a[MAXN],b[MAXN];
	int c[MAXN];
	fft(a,n,1),fft(b,n,1);//1系数转点值
	fo(i,0,n-1)a[i]*=b[i];
	fft(a,n,-1);//-1点值转系数
	fo(i,0,n-1)c[i]=(int)(a[i].real()/n+0.5);//注意精度

很明显,FFT只能处理nnn222的整数次幂的多项式

所以在FFTFFTFFT前一定要把nnn调到222的次幂

这个板子看着好像很优美,但是

递归常数太大,要考虑优化


FFTの优化——迭代版FFT

这个图也是盗的

这个很容易发现点什么吧?

  • 每个位置分治后的最终位置为其二进制翻转后得到的位置

这样的话我们可以先把原序列变换好,把每个数放在最终的位置上,再一步一步向上合并

一句话就可以O(n)O(n)O(n)预处理第iii位最终的位置rev[i]rev[i]rev[i]

fo(i,0,n-1)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));

至于蝴蝶变换它死了其实是我不会


真·FFT板子

void fft(cp *a,int n,int inv)
{
    int bit=0;
    while ((1<<bit)<n)bit++;
    fo(i,0,n-1)
    {
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        if (i<rev[i])swap(a[i],a[rev[i]]);//不加这条if会交换两次(就是没交换)
    }
    for (int mid=1;mid<n;mid*=2)//mid是准备合并序列的长度的二分之一
    {
    	cp temp(cos(pi/mid),inv*sin(pi/mid));//单位根,pi的系数2已经约掉了
        for (int i=0;i<n;i+=mid*2)//mid*2是准备合并序列的长度,i是合并到了哪一位
		{
            cp omega(1,0);
            for (int j=0;j<mid;j++,omega*=temp)//只扫左半部分,得到右半部分的答案
            {
                cp x=a[i+j],y=omega*a[i+j+mid];
                a[i+j]=x+y,a[i+j+mid]=x-y;//这个就是蝴蝶变换什么的
            }
        }
    }
}

这个板子好像不是那么好

至少这个板子已经很优美


FFT后记

本人版权意识薄弱……

  • 博客部分知识学习于

  • https://www.cnblogs.com/RabbitHu/p/FFT.html

  • https://www.cnblogs.com/zwfymqz/p/8244902.html?mType=Group#_label3

  • https://blog.csdn.net/ggn_2015/article/details/68922404

NTTNTTNTT我来了

相关阅读

离散傅里叶变换-DFT(FFT基础)

本文是从最基础的知识开始讲解,力求用最通俗易懂的文字将问题将的通俗易懂,大神勿

FFT算法(Java实现)

FFT导论 转载自FFT导论 FFT是离散傅立叶变换的快速算法,可以将一个信号变换到频域。 有些信号在时域上是很难看出什么特征的,但是

matlab进行傅里叶变换fft和shiftfft

使用matlab进行傅里叶变换 模拟信号进行奈奎斯特采样后成为离散的数字信号,时域分析不太方便,通常要进行频域变换更好的分析信号,mat

MATLAB快速傅里叶变换(fft)函数详解

原文 定义: MATLAB帮助文件原文 The 'i' in the 'Nth root of unity' 是虚数单位 ​调用: ​​1.  Y = fft(y);2.  Y = fft(y,N);式

FFT算法的优化——提高FFT算法一倍的时空效率

前言 由于本人学习FFT时第一份代码并不是从普通的FFT学起的,而是直接从一个经过优化的版本开始学。这是一个利用到共轭复数性质的

分享到:

栏目导航

推荐阅读

热门阅读