FFT(快速傅里叶变换)——从入门到放弃
SuperJvRuo · · 个人记录
FFT(Fast Fourier Transformation)就是“快速傅里叶变换”的意思,它是一种用来计算DFT(离散傅里叶变换)和IDFT(离散傅里叶逆变换)的一种快速算法。这种算法运用了一种高深的数学方式、把原来复杂度为
最常用的FFT算法是Cooley和Turkey提出的Radix-2 FFT(基-2FFT),把一个DFT拆成两个一半大小的DFT。这篇文章只介绍Radix-2 FFT。此方法因为实现简单倍受欢迎。Radix-2 FFT适用于N为2的整数次方的FFT。
此外还有Radix-4 FFT(基-4FFT),Radix-8 FFT(咳咳FFT),它们效率更高,但适用范围太窄。
综合了Radix-2的适用范围和Radix-4的效率的算法是SplitRadix FFT(分裂基FFT),名字听上去就很帅,实际玩起来也挺帅的……效率比Radix2快一点吧大概,但实现起来复杂很多。
一、朴素的多项式乘法
设
将所有的两项系数相乘后再相加,利用这种方法计算多项式乘法复杂度为
二、多项式的点值表示
将
已知
但是对于系数表示的多项式,在转换为点值表示时,由于要选
可以优化吗?
三、复数
可以借助复数优化。
i的定义
单位根的求法
所以,以原点为起点,圆的
对三角函数稍有常识的人就能看出,
单位根的性质
1.
证明:
2.
证明:
四、DFT(离散傅里叶变换)
我们可以把
且慢,一共
其实是可以优化的:
按奇偶分组:
设:
显然,
将
再将
两者实部相同,只有虚部相反。因此在算第一个时可以顺便
可以改成非递归版吗?
观察分组过程:
| 原数组 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 分组 | 0 | 2 | 4 | 6 | 1 | 3 | 5 | 7 |
| 再分组 | 0 | 4 | 2 | 6 | 1 | 5 | 3 | 7 |
转换成二进制:
| 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
|---|---|---|---|---|---|---|---|
| 000 | 010 | 100 | 110 | 001 | 011 | 101 | 111 |
| 000 | 100 | 010 | 110 | 001 | 101 | 011 | 111 |
在这个过程结束后,下标的二进制恰好与原来相反!
那么我们就可以用下面的方法
for(int i=0;i<limit;i++)
r[i]= ( r[i>>1]>>1 )| ( (i&1)<<(l-1) ) ;
这也就是所谓的蝴蝶运算。
原理:
我们可以把一个二进制数看成两部分,它的前bit-1位是一部分,它的最后一位是一部分。
一个数的二进制翻转就相当于是把它的最后一位当成首位,然后在后面接上它前bit-1为的二进制翻转。而且在这个循环中我们能保证,在计算“i”的二进制翻转之前1~i-1中的所有数的二进制翻转都已经完成。“i”的前bit-1位的数值其实就是i>>1的值,直接调用i>>1的二进制翻转的结果就相当于调用了“i”的前bit-1位二进制翻转的结果。
至此,我们已经求出了两个多项式的点值表示。然后即可在
五、IDFT(离散傅里叶逆变换)
可是,我们要的是系数啊!
所以我们要考虑如何把点值表示法转换为系数表示法,这个过程就是IDFT。利用单位根的一些性质,可以加速这个转换的过程。
先给出结论:
其中
我们试着来证明一下:考虑式子右上边的
将
显然
所以原式
设
首项为
显然
我们惊讶地发现:
但是别忘了:我们没有考虑
将这个结果回带到
只有
因此:
只要把多项式的点值表示当做系数,代入相应单位根的倒数,用DFT的方法求出结果,所得的结果再除以
如何求出单位根的倒数呢?只要找到另一个复数,让两个复数模长相同,幅角相加等于
六、总结
七、扩展:傅里叶变换应用初探
MP3作为一种有损压缩格式,通过剔除掉音乐中特定人耳听觉不敏感的频段,减少数据量,获得更大的压缩比。这个“剔除”的过程就用了DFT。
基于Vocaloid歌声合成软件的初音未来,其合成过程用到了大量的DFT。Vocaloid的合成原理基于STFT(短时傅立叶变换),单音轨合成,每秒至少要进行344次2048点FFT,从而修改信号的频谱特征,Vocaloid合成时大部分时间花在FFT上。所以,你的老婆就是DFT!
汤姆猫之类的变声器使用DFT改变声音的频谱特征。
均衡器,它通过DFT增强或削弱声音中某个特定频率范围的强度。提高高频和低频部分可以让音乐更明亮有节奏感。
降噪器通过DFT减弱声音中含有噪音的频段,从而实现降噪。我们的手机、电脑的声卡也一般具有降噪器,让你的麦克风输出质量更高。
DFT不仅可以用于音频处理,还可以用于图像处理:给图片加一个水印,直接贴在图片上,很容易被抹掉。一种合理的方法是DFT,在频谱上添加高频水印,再IDFT。
八、代码
#include<cstdio>
#include<cmath>
#include<cctype>
const int MAXN=1e7+10;
inline int Read()
{
char c=getchar();
int x=0;
while(!isdigit(c))
{
c=getchar();
}
while(isdigit(c))
{
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x;
}
const double Pi=acos(-1.0);
struct complex
{
double x,y;
complex (double xx=0,double yy=0){x=xx,y=yy;}
}a[MAXN],b[MAXN];
void swap(complex &a,complex &b)
{
complex c=a;
a=b;
b=c;
}
complex operator + (complex a,complex b)
{
return complex(a.x+b.x , a.y+b.y);
}
complex operator - (complex a,complex b)
{
return complex(a.x-b.x , a.y-b.y);
}
complex operator * (complex a,complex b)
{
return complex(a.x*b.x-a.y*b.y , a.x*b.y+a.y*b.x);
}
int N,M;
int l,r[MAXN];
int limit=1;
void FFT(complex *A,int opt)
{
for(int i=0;i<limit;i++)
{
if(i<r[i])
{
swap(A[i],A[r[i]]);
}
}
for(int mid=1;mid<limit;mid<<=1)
{
complex Wn( cos(Pi/mid) , opt*sin(Pi/mid) );
for(int R=mid<<1,j=0;j<limit;j+=R)
{
complex w(1,0);
for(int k=0;k<mid;k++,w=w*Wn)
{
complex x=A[j+k],y=w*A[j+mid+k];
A[j+k]=x+y;
A[j+mid+k]=x-y;
}
}
}
}
int main()
{
int N=Read(),M=Read();
for(int i=0;i<=N;i++) a[i].x=Read();
for(int i=0;i<=M;i++) b[i].x=Read();
while(limit<=N+M)
{
limit<<=1;
++l;
}
for(int i=0;i<limit;i++)
{
r[i]= ( r[i>>1]>>1 )| ( (i&1)<<(l-1) ) ;
}
FFT(a,1);
FFT(b,1);
for(int i=0;i<=limit;i++)
{
a[i]=a[i]*b[i];
}
FFT(a,-1);
for(int i=0;i<=N+M;i++)
{
printf("%d ",(int)(a[i].x/limit+0.5));
}
return 0;
}