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),但FFT能O(nlog2n)的时间解决
FFT名字逼格高,也难懂,其他教程写得让人看不太懂,于是自己随便写一下
建议对复数、三角函数相关知识有所耳闻
(不会也无所谓)下面难懂的点我会从网上盗
多项式的系数表示法和点值表示法
-
FFT其实是一个用O(nlog2n)的时间将一个用系数表示的多项式转换成它的点值表示的算法
-
多项式的系数表示和点值表示可以互相转换
系数表示法
一个n-1次n项多项式f(x)可以表示为f(x)=∑i=0n−1aixi
也可以用每一项的系数来表示f(x),即f(x)={a0,a1,a2,...,an−1}
这就是系数表示法,也就是平时数学课上用的方法
点值表示法
-
把多项式放到平面直角坐标系里面,看成一个函数
-
把n个不同的x代入,会得出n个不同的y,在坐标系内就是n个不同的点
-
那么这n个点唯一确定该多项式,也就是有且仅有一个多项式满足∀k,f(xk)=yk
-
理由很简单,把n条式子联立起来成为一个有n条方程的n元方程组,每一项的系数都可以解出来
那么f(x)还可以用f(x)={(x0,f(x0)),(x1,f(x1)),(x2,f(x2)),...,(xn−1,f(xn−1))}来表示
这就是点值表示法
高精度乘法下两种多项式表示法的区别
对于两个用系数表示的多项式,我们把它们相乘
设两个多项式分别为A(x),B(x)
我们要枚举A每一位的系数与B每一位的系数相乘
那么系数表示法做多项式乘法时间复杂度O(n2)
但两个用点值表示的多项式相乘,只需要O(n)的时间
什么意思呢?
设两个点值多项式分别为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)),...,(xn−1,g(xn−1))}
设它们的乘积是h(x),那么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(n2)的,逆操作类似
-
朴素系数转点值的算法叫DFT(离散傅里叶变换),点值转系数叫IDFT(离散傅里叶逆变换)
-
难道高精度乘法只能O(n2)了吗?
DFT前置知识&技能
复数
毕竟高中有所以不多说
我们把形如a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位。当虚部等于零时,这个复数可以视为实数;当z的虚部不等于零时,实部等于零时,常称z为纯虚数。复数域是实数域的代数闭包,也即任何复系数多项式在复数域中总有根。 复数是由意大利米兰学者卡当在十六世纪首次引入,经过达朗贝尔、棣莫弗、欧拉、高斯等人的工作,此概念逐渐为数学家所接受。
——百度百科
初中数学老师会告诉你没有−1,但仅限R
扩展至复数集C,定义i2=−1,一个复数z可以表示为z=a+bi(a,b∈R)
其中a称为实部,b称为虚部,i称为虚数单位
- 在复数集中就可以用i表示负数的平方根,如−7=7i
还可以把复数看成复平面直角坐标系上的一个点,比如下面
x轴就是实数集中的坐标轴,y轴就是虚数单位i轴
这个点(2,3)表示的复数就是2+3i,或者想象它代表的向量为(2,3)
其实我们还可以自己想象 (其实没有这种表达方式) 它可以表示为(13,θ)
一个复数z的模定义为它到原点的距离,记为∣z∣=a2+b2
一个复数z=a+bi的共轭复数为a−bi(虚部取反),记为z=a−bi
复数的运算
复数不像点或向量,它和实数一样可以进行四则运算
设两个复数分别为z1=a+bi,z2=c+di,那么
z1+z2=(a+c)+(b+d)iz1z2=(ac−bd)+(ad+bc)i
复数相加也满足平行四边形法则
这张是从网上盗的
即AB+AD=AC
复数相乘还有一个值得注意的小性质
(a1,θ1)∗(a2,θ2)=(a1a2,θ1+θ2)
即模长相乘,极角相加
DFT(离散傅里叶变换)
- 一定注意从这里开始所有的n都默认为2的整数次幂
对于任意系数多项式转点值,当然可以随便取任意n个x值代入计算
但是暴力计算xk0,xk1,...,xkn−1(k∈[0,n))当然是O(n2)的时间
其实可以代入一组神奇的x,代入以后不用做那么多的次方运算
这些x当然不是乱取的,而且取这些x值应该就是 傅里叶 的主意了
考虑一下,如果我们代入一些x,使每个x的若干次方等于1,我们就不用做全部的次方运算了
±1是可以的,考虑虚数的话±i也可以,但只有这四个数远远不够
- 傅里叶说:这个圆圈上面的点都可以做到
以原点为圆心,画一个半径为1的单位圆
那么单位圆上所有的点都可以经过若干次次方得到1
傅里叶说还要把它给n等分了,比如此时n=8
橙色点即为n=8时要取的点,从(1,0)点开始,逆时针从0号开始标号,标到7号
记编号为k的点代表的复数值为ωnk,那么由模长相乘,极角相加可知(ωn1)k=ωnk
其中ωn1称为n次单位根,而且每一个ω都可以求出 (我三角函数不好)
ωnk=cosnk2π+isinnk2π
或者说也可以这样解释这条式子
注意sin2θ+cos2θ=1什么的,就容易理解了
那么ωn0,ωn1,...,ωnn−1即为我们要代入的x0,x1,...,xn−1
单位根的一些性质
推FFT的过程中需要用到ω的一些性质
ωnk=ω2n2k
-
它们表示的点(或向量)表示的复数是相同的
-
证明
-
ωnk=cosnk2π+isinnk2π=cos2n2k2π+isin2n2k2π=ω2n2k
ωnk+2n=−ωnk
-
它们表示的点关于原点对称,所表示的复数实部相反,所表示的向量等大反向
-
证明
-
ωn2n=cosn2n2π+isinn2n2π=cosπ+isinπ=−1
-
(这个东西和eix=cosx+isinx与eiπ+1=0有点关系,
我不会就不讲了)
ωn0=ωnn
- 都等于1,或1+0i
FFT(快速傅里叶变换)
虽然DFT搞出来一堆很牛逼的ω作为代入多项式的x值
但只是代入这类特殊x值法的变换叫做DFT而已,还是要代入单位根暴力计算
- DFT还是暴力O(n2)…
但DFT可以分治来做,于是 FFT(快速傅里叶变换) 就出来了
首先设一个多项式A(x)
A(x)=i=0∑n−1aixi=a0+a1x+a2x2+...+an−1xn−1
按A(x)下标的奇偶性把A(x)分成两半,右边再提一个x
A(x)=(a0+a2x2+...+an−2xn−2)+(a1x+a3x3+...+an−1xn−1)
=(a0+a2x2+...+an−2xn−2)+x(a1+a3x2+...+an−1xn−2)
两边好像非常相似,于是再设两个多项式A1(x),A2(x),令
A1(x)=a0+a2x+a4x2+...+an−2x2n−1A2(x)=a1+a3x+a5x2+...+an−1x2n−1
比较明显得出
A(x)=A1(x2)+xA2(x2)
再设k<2n,把ωnk作为x代入A(x)(接下来几步变换要多想想单位根的性质)
A(ωnk)=A1((ωnk)2)+ωnkA2((ωnk)2)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)
那么对于A(ωnk+2n)的话,代入ωnk+2n
A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ωn2kωnn)−ωnkA2(ωn2kωnn)=A1(ωn2k)−ωnkA2(ωn2k)=A1(ω2nk)−ωnkA2(ω2nk)
- 发现了什么?
A(ωnk)和A(ωnk+2n)两个多项式后面一坨东西只有符号不同
就是说,如果已知A1(ω2nk)和A2(ω2nk)的值,我们就可以同时知道A(ωnk)和A(ωnk+2n)的值
现在我们就可以递归分治来搞FFT了
每一次回溯时只扫当前前面一半的序列,即可得出后面一半序列的答案
n==1时只有一个常数项,直接return
时间复杂度O(nlog2n)
IFFT(快速傅里叶逆变换)
想一下,我们不仅要会FFT,还要会IFFT(快速傅里叶逆变换)
我们把两个多项式相乘 (也叫求卷积),做完两遍FFT也知道了积的多项式的点值表示
可我们平时用系数表示的多项式,点值表示没有意义
-
怎么把点值表示的多项式快速转回系数表示法?
-
IDFT暴力O(n2)做?其实也可以用FFT用O(nlog2n)的时间搞
你有没有想过为什么傅里叶是把ωnk作为x代入而不是别的什么数?
原因是有的但是有我也看不懂
由于我是沙雕所以只用记住一个结论
- 一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以n即为原多项式的每一项系数
意思就是说FFT和IFFT可以一起搞
朴素版FFT板子
c++有自带的复数模板complex库
a.real()即表示复数a的实部
#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,b相乘再转系数多项式c,通常只用打这么一小段
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只能处理n为2的整数次幂的多项式
所以在FFT前一定要把n调到2的次幂
这个板子看着好像很优美,但是
递归常数太大,要考虑优化…
FFTの优化——迭代版FFT
这个图也是盗的
这个很容易发现点什么吧?
- 每个位置分治后的最终位置为其二进制翻转后得到的位置
这样的话我们可以先把原序列变换好,把每个数放在最终的位置上,再一步一步向上合并
一句话就可以O(n)预处理第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
NTT我来了
相关阅读
本文是从最基础的知识开始讲解,力求用最通俗易懂的文字将问题将的通俗易懂,大神勿
FFT导论 转载自FFT导论 FFT是离散傅立叶变换的快速算法,可以将一个信号变换到频域。 有些信号在时域上是很难看出什么特征的,但是
使用matlab进行傅里叶变换 模拟信号进行奈奎斯特采样后成为离散的数字信号,时域分析不太方便,通常要进行频域变换更好的分析信号,mat
原文 定义: MATLAB帮助文件原文 The 'i' in the 'Nth root of unity' 是虚数单位 调用: 1. Y = fft(y);2. Y = fft(y,N);式
前言 由于本人学习FFT时第一份代码并不是从普通的FFT学起的,而是直接从一个经过优化的版本开始学。这是一个利用到共轭复数性质的