快速傅里叶变换(FFT)(未完)

快速傅立叶变换(FFT)

FFT的里有许多地方我也搞不懂,我不想懂也不需要懂,知道结论能用就行了。。。看了好多天的鬼东西,本来觉得好难,看完之后觉得也不过如此。

快速傅立叶变换FFT,是一种用来加速多项式相乘(卷积)的算法O(n*logn)。

离散傅里叶变换DFT: 传统暴力方式下实现多项式相乘O(n^{2})。

对于一个多项式:F(x) = a_{0},a_{1},a_{2},  + a_{1} x^{1}a_{2}  x^{2} +.......+ a_{n-1} x^{n-1},   当x=10 时 F (x) 的值 即是一个10进制数。也就是说高精度相乘是多项式相乘的一个特例。

傅里叶变换的大致过程:

1.   系数表示法 -> 点值表示法(称为傅里叶变换DFT)。

2.   在点值表示法下相乘,合并两个多项式为一个多项式。

3.   点值表示法->系数表示法 (称为傅里叶逆变换IDFT)。

多项式系数表示法:

形如 F(x) = a_{0},a_{1},a_{2},  + a_{1} x^{1}a_{2}  x^{2} +.......+ a_{n-1} x^{n-1},由系数{a_{0},a_{1},a_{2},a_{1}a_{2},...     a_{n-1} } n个系数来表示的叫做系数表示法。

点值表示法:

(两点确定一条直线,三点确定一条抛物线,n个点确定一个n-1次多项式?好像大概应该可能是这个意思)

取n个互不相同的 x 值   { x_{0}x_{1}x_{2}  ...   x_{n-1}   }    分别带入多项式F(x)  ,

得到n个点 {  ( x_{0} , F(x_{0})  )  ,    ( x_{1} ,  F(x_{1}) )   ,( x_{2} , F(x_{2}) )  ........   ( x_{n-1},  F( x_{n-1} ) )  },这是它的点值表示法。

1.系数表示法 -> 点值表示法(DFT)

取n个互不相同的 x 带入多项式即取得n个点的集合就是DFT的过程。

2.点值相乘

将两个在点值表示法下的多项式,按相同位置上的两个多项式的两点相乘,得到新的多项式的这个位置上的点,最终即能合并成为一个点值表示法下的多项式。

3.点值表示法->系数表示法(IDFT)

在上述DFT的过程中动些手脚,就能实现IDFT逆变换。

 

以上便是大致过程,接下来是详细描述。

 

能够实现O(n*logn)的原因:在选取n个互不相同的点带入多项式时,多项式有n项,每个点代入求F ( x ) 的复杂度为O(n),代入n个点为O(n^{2}) 。而取一些特殊的点,即能实现O(n*logn)复杂度,单位复根就能满足这样的要求。

单位复根:

 

            ( 复数 a+bi  ,  i^{2} =-1 )   ,一个复数 w, 满足w^{n}=1,则称 w 是 n 次 单位根,单位根都是在半径为1的单位圆上。

(下面将会出现这样的表达形式:w_{n}^{m}  ,表示(复数w的n次方根)的m次方    )

 

 

                                                        旋转因子,乘一个旋转因子取得下一个复数。

                       以w的4次方根为旋转因子。                                                                           以w的8次方根为旋转因子。

                       w_{4}^{4}=(1,0)                                                                                                             w_{8}^{8}=(1,0)

                       w_{4}^{1}=w_{4}^{4} * w_{4}^{1}                                                                                                        w_{8}^{1}=w_{8}^{8} * w_{8}^{1}

                       w_{4}^{2}=w_{4}^{1} * w_{4}^{1}                                                                                                        w_{8}^{2}=w_{8}^{1} * w_{8}^{1}

                       w_{4}^{3}=w_{4}^{2} * w_{4}^{1}                                                                                                        w_{8}^{3}=w_{8}^{2} * w_{8}^{1}

                       w_{4}^{4}=w_{4}^{3} * w_{4}^{1}                                                                                                                        ...............

可以注意到:n次方根,它的幂就会有n个不同的取值,方便的让我们选择n个不同的值代入n次多项式进行点值表达式转换。

同是它还有两个比较好的性质( 观察上面两个图 ):

1. w_{n}^{m}=w_{kn}^{km}            ,比如:  w_{4}^{1} =   w_{8}^{2}  =  w_{12}^{3}   .....   (消去引理? 能够实现递归分治的依据)

2. w_{n}^{m}=  − w_{n}^{m+n/2}   ,比如: w_{4}^{1} = − w_{4}^{3}  .....               (折半引理?能将问题规模减半)

 

对于多项式:F(x)= a_{0},a_{1},a_{2},  + a_{1} x^{1}a_{2}  x^{2} +.......+ a_{n-1} x^{n-1}

提取它的偶次项系数组成新的多项式:F_{0} (x)= a_{0},a_{1},a_{2},  + a_{2} x^{1}a_{4}  x^{2} +.......+ a_{n-2} x^{n/2}

提取它的奇次项系数组成新的多项式:F_{1} (x)= a_{1}  + a_{3} x^{1}a_{5}  x^{2} +.......+ a_{n-1} x^{n/2}

它将会有如下的性质:

F(x) = F_{0}x^{2})+ x *  F_{1}x^{2}{\color{Red} }                  (  简单地代入 x^{2},然后合并就等于 F(x) 了 )

 

接下来就是需要稍微理解的东西了。

将 w_{n}^{m} 代入F( x ):

F(w_{n}^{m})= a_{0},a_{1},a_{2},   +  a_{1} w_{n}^{m}  +  a_{2}  w_{n}^{2m}  +  .......  +  a_{n-1} w_{n}^{(n-1)*m}

 

根据上面推出的性质得:

1. F(w_{n}^{m})= F_{0} (w_{n}^{m}^2  )+ w_{n}^{m} F_{1}w_{n}^{m}^2  )  

                = F_{0} (w_{n}^{2*m}  )+ w_{n}^{m} F_{1}w_{n}^{2*m}  )  

                =  F_{0} (w_{n/2}^{m}  )+ w_{n}^{m} F_{1}w_{n/2}^{m}  )                                    (消去引理,w_{n}^{2*m} =  w_{n/2}^{m}  

 

2. F(w_{n}^{m+n/2})= F_{0} (w_{n}^{m+n/2}  ^2)+  w_{n}^{m+n/2} F_{1}w_{n}^{m+n/2} ^2 )     

                        =  F_{0} (w_{n}^{m}^2  )      +  w_{n}^{m+n/2} F_{1}w_{n}^{m}^2  )           (折半引理,w_{n}^{m+n/2} = −  w_{n}^{m})    

                        =  F_{0} (w_{n}^{2*m}  )        +  w_{n}^{m+n/2} F_{1}w_{n}^{2*m}  )                         

                        =  F_{0} (w_{n}^{2*m}  )        −    w_{n}^{m} F_{1}w_{n}^{2*m}  )                 (折半引理,w_{n}^{m+n/2}  = −  w_{n}^{m} 

总结以上两条:想要将n个系数的多项式F(x)转换为点值表达式,先将 F(x)中的偶次项系数 和 奇次项系数 取出组合成两个新的n / 2个系数的多项式,递归分治地求解这两个子问题,再将他们合并去求解 n 个 系数的 多项式 F(x)。

 

递归的形式:

void FFT(complex<double> a[],int n){
    if(n==1) return;
    complex<double> *a0=new complex<double>[n/2];
    complex<double> *a1=new complex<double>[n/2];
    for(int i=0;i<n;i+=2){                                //用奇偶次项系数组成新的多项式
        a0[i/2]=a[i];
        a1[i/2]=a[i+1];
    }
    FFT(a0,n/2);FFT(a1,n/2);                            //递归分治问题 
    complex<double> wn(cos(2*M_PI/n),sin(2*M_PI/n));   //取n次方根 
    complex<double> w(1,0);
    for(int i=0;i<(n/2);i++){                         //蝶形操作,由下一层的a[i],a[i+n/2]
        a[i]=a0[i]+w*a1[i];                          //更新本层的 a[i],a[i+n/2]
        a[i+n/2]=a0[i]-w*a1[i];
        w=w*wn;                                      //旋转因子 
    }
    return;
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
int a[100];
int b[100];
void FFT(complex<double> a[],int n,int op){
    if(n==1) return;
    complex<double> *a0=new complex<double>[n/2];
    complex<double> *a1=new complex<double>[n/2];
    for(int i=0;i<n;i+=2){                                //用奇偶次项系数组成新的多项式
        a0[i/2]=a[i];
        a1[i/2]=a[i+1];
    }
    FFT(a0,n/2,op);FFT(a1,n/2,op);                            //递归分治问题 
    complex<double> wn(cos(2*M_PI/n),sin(2*M_PI*op/n));   //取n次方根 
    complex<double> w(1,0);
    for(int i=0;i<(n/2);i++){                         //蝶形操作,由下一层的a[i],a[i+n/2]
        a[i]=a0[i]+w*a1[i];                          //更新本层的 a[i],a[i+n/2]
        a[i+n/2]=a0[i]-w*a1[i];
        w=w*wn;                                      //旋转因子 
    }
    return;
}
int main()
{
	int m1,m2;
	scanf("%d%d",&m1,&m2);
	for(int i=0;i<m1;i++){
		scanf("%d",&a[i]);
	}
	for(int i=0;i<m1;i++){
		scanf("%d",&b[i]);
	}
	int n=1;
	while(n<m1+m2){                      //不能小于两个多项式相乘的长度m1+m2
		n<<=1;
	}
	complex<double> *c=new complex<double> [n+1];
	complex<double> *d=new complex<double> [n+1];
	for(int i=0;i<m1;i++){
		c[i]=complex<double>(a[i],0);
	}
	for(int i=0;i<m2;i++){
		d[i]=complex<double>(b[i],0);
	}
	FFT(c,n,1);                //DFT变换 
	FFT(d,n,1);               //DFT变换 
	for(int i=0;i<n;i++){
		c[i]=c[i]*d[i];       //点值相乘 
	}
	FFT(c,n,-1);             //IDFT逆变换 
	
	for(int i=0;i<n;i++){
		a[i]=(c[i].real()/n+0.5);       // n倍结果 
	}
	for(int i=0;i<n;i++){
		printf("%d ",a[i]);
	}
	return 0;
} 
/*
测试数据,
输入: 
5 5
1 1 0 0 0
1 0 1 1 1
输出:
1 1 1 2 2 1 0 0 0 0 0 0 0 0 0 0 

解释:输入数据为高低位颠倒的两个数
(00011) * (11101)= (0000000000122111) 
(11) * (11101)= (122111) 
*/

注意:

1.复数的n次方根=( cos(2*\pi/n) , sin(2*\pi/n) )。

2.蝶形操作是依据于上面的式子1,2 的结果。

3.complex<double>是属于系统库提供的复数类,效率低,有BUG,可能会产生错误。

4.每次递归都要分成两个一半的子问题,所以多项式系数个数(长度)必须严格为2的幂,不足则补零。

5.多项式长度不仅要2的幂,还不能小于两个多项式相乘的结果的长度。

6.逆变换是将单位复根的虚部变为负数( 或者......),逆变换的实部 / n,才是多项式相乘的结果。

7.大数运算要高低位颠倒,结果要进位。

 

 

迭代替代递归:

 

减少一次DFT变换的方式:

 

尽力优化double精度误差问题:

 

NNT快速数论变换:

 

FWT?:

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值