最近认真研究了一下算法导论里面的多项式乘法的快速计算问题,主要是用到了FFT,自己也实现了一下,总结如下。

1.多项式乘法

两个多项式相乘即为多项式乘法,例如:3*x^7+4*x^5+1*x^2+5与8*x^6+7*x^4+6*x^3+9两个式子相乘,会得到一个最高次数项为13的多项式。一般来说,普通的计算方法是:把A多项式中的每一项与B中多项式中的每一项相乘,得到n个多项式,再把每个多项式相加到一起,得到最终的结果,不妨假设A,B的最高次项都为n-1,长度都为n,那么计算最终的结果需要o(n^2)时间复杂度。而使用快速傅里叶变换(FFT),则可以将时间复杂度降低到o(nlog n)。这是因为,对一个复数序列做正/反快速傅里叶变换的时间复杂度都是o(nlog n),而变换后的序列逐项相乘即为原序列做多项式乘法的结果(多项式乘法相当于卷积)。所以,FFT可以降低多项式相乘运算的时间复杂度,具体的解释和证明在《算法导论》或者其他任何一本相关的算法书中都有详细描述,在此不再赘述。另外,需要注意的是,一些其他的运算也可以转化成多项式乘法,进而利用FFT来加快运算。例如:1.数字乘法运算,和多项式乘法类似,A*B的操作就是用A每一位上的数字乘以B每一位上的数字。尤其是在大数乘法中,FFT可以大幅度加快运算。2.给定A到B的不同长度路径详细数据,B到C的不同路径长度详细数据,求A到C不同长度路径的数量。可以把A到B和B到C不同长度的路径看成不同次数的项,例如:A到B有3条长度为4,2条长度为5的路径,B到C有1条长度为2,4条长度为3的路径,那么A到C不同长度路径的数量等于(3*x^4+2*x^5)*(4*x^3+1*x^2)得到的各项的系数,转化成多项式相乘问题之后,就可以利用FFT来加快运算速度了。

2.FFT

大多数人应该是只需要会用FFT即可,但是这个算法比较基础,因此我自己编程实现了一下,总的代码只有150行左右,其实不算长,当然,对输入序列长度不是2的整数次幂这种情况我没有相应的预处理,算是偷懒了。其实只要对长度取一下对数即可,例如:输入长度如果是37,首先把37*2,拓展成74(这个是FFT必须的),然后对74取log2上取整即可,得到27=128,因此在74后面再添加54个0。

另外,需要注意的一点是,reverse函数在FFT和IFFT中是必须的,不过鉴于多项式乘法需要成对进行FFT和IFFT,所以在做多项式乘法的时候,reverse应该是可以省略的(当然,这个函数的耗时很小)。w的值应该提前计算出来,这样在FFT和IFFT中蝶形计算每一项的时候,就不用重复计算w了,可以节省很多时间。

具体FFT的原理和解释,可以查维基、信息论、数字信号处理、随机过程等任一领域的教科书。

3.代码

程序主要包括FFT,IFFT函数,以及一些复数运算

3.1复数的定义和相关运算定义

//复数
struct Complex{
double real;
double image;
};
Complex a1[MAX_SIZE],a2[MAX_SIZE],result[MAX_SIZE],w[MAX_SIZE];
//复数相乘计算
Complex operator*(Complex a,Complex b){
Complex r;
r.real=a.real*b.real-a.image*b.image;
r.image=a.real*b.image+a.image*b.real;
return r;
}
//复数相加计算
Complex operator+(Complex a,Complex b){
Complex r;
r.real=a.real+b.real;
r.image=a.image+b.image;
return r;
}
//复数相减计算
Complex operator-(Complex a,Complex b){
Complex r;
r.real=a.real-b.real;
r.image=a.image-b.image;
return r;
}
//复数除法计算
Complex operator/(Complex a,double b){
Complex r;
r.real=a.real/b;
r.image=a.image/b;
return r;
}
//复数虚部反计算
Complex operator~(Complex a){
Complex r;
r.real=a.real;
r.image=0-a.image;
return r;
}

3.2FFT和IFFT函数及相关函数

其实FFT和IFFT的原理一样,只是IFFT多了一个除法步骤,也可以把两个合并成一个函数。Reverse用于重新排列输入数组的元素下标,例如输入数组长度为8,则0,1,2,3,4,5,6,7下标的元素经过重新排列后变为0,4,2,6,1,5,3,7下标的元素。Compute_W用于预先计算FFT中需要的w值。

//重新排列方法2,效率较高
void Reverse(int* id,int size,int m){
for(int i=0;i<size;i++){
for(int j=0;j<(m+1)/2;j++){
int v1=(1<<(j)&i)<<(m-2*j-1);
int v2=(1<<(m-j-1)&i)>>(m-2*j-1);
id[i]|=(v1|v2);
}
}
};
//重新排列方法1,该方法是用pow函数效率比较低
void Reverse(int* id,int size,int m){
for(int i=0;i<size;i++){
for(int j=0;j<m;j++){
int exp=(i>>j)&1;
id[i]+=exp*(int)pow((double)2,(double)(m-j-1));
}
}
};
//计算并存储需要乘的w值
void Compute_W(Complex w[],int size){
for(int i=0;i<size/2;i++){
w[i].real=cos(2*PI*i/size);
w[i].image=sin(2*PI*i/size);
w[i+size/2].real=0-w[i].real;
w[i+size/2].image=0-w[i].image;
}
};
//快速傅里叶
void FFT(Complex in[],int size){
int* id=new int[size];
memset(id,0,sizeof(int)*size);
int m=log((double)size)/log((double)2);
Reverse(id,size,m); //将输入重新排列,符合输出
Complex *resort= new Complex[size];
memset(resort,0,sizeof(Complex)*size);
int i,j,k,s;
for(i=0;i<size;i++)
resort[i]=in[id[i]];
for(i=1;i<=m;i++){
s=(int)pow((double)2,(double)i);
for(j=0;j<size/s;j++){
for(k=j*s;k<j*s+s/2;k++){
Complex k1= resort[k]+w[size/s*(k-j*s)]*resort[k+s/2];
resort[k+s/2]=resort[k]-w[size/s*(k-j*s)]*resort[k+s/2];
resort[k]=k1;
}
}
}
for(i=0;i<size;i++)
in[i]=resort[i];
delete[] id;
delete[] resort;
};
//快速逆傅里叶
void IFFT(Complex in[],int size){
int* id=new int[size];
memset(id,0,sizeof(int)*size);
int m=log((double)size)/log((double)2);
Reverse(id,size,m); //将输入重新排列,符合输出
Complex *resort= new Complex[size];
memset(resort,0,sizeof(Complex)*size);
int i,j,k,s;
for(i=0;i<size;i++)
resort[i]=in[id[i]];
for(i=1;i<=m;i++){
s=(int)pow((double)2,(double)i);
for(j=0;j<size/s;j++){
for(k=j*s;k<j*s+s/2;k++){
Complex k1=(resort[k]+(~w[size/s*(k-j*s)])*resort[k+s/2]);
resort[k+s/2]=(resort[k]-(~w[size/s*(k-j*s)])*resort[k+s/2]);
resort[k]=k1;
}
}
}
for(i=0;i<size;i++)
in[i]=resort[i]/size;
delete[] id;
delete[] resort;
};

3.3主函数

输入两个多项式的系数(长度必须都是2的整数次幂),输出两个多项式相乘的结果

int main(){
//输入两个多项式数列
int size,size1,size2,i;
memset(a1,0,sizeof(a1));
memset(a2,0,sizeof(a2));
memset(w,0,sizeof(w));
memset(result,0,sizeof(result));
scanf("%d%d",&size1,&size2);
for(i=0;i<size1;i++)
scanf("%lf",&a1[i].real);
for(i=0;i<size2;i++)
scanf("%lf",&a2[i].real);
size=size1>size2?size1*2:size2*2;
Compute_W(w,size);
FFT(a1,size);
FFT(a2,size);
for(i=0;i<size;i++)
result[i]=a1[i]*a2[i];
IFFT(result,size);
for(i=0;i<size1+size2-1;i++)
printf("%.2lf ",result[i].real);
printf("\n");
return 0;
}

下面是完整的代码

CPP文件下载

多项式相乘快速算法原理及相应C代码实现---用到fft的更多相关文章

  1. 最全排序算法原理解析、java代码实现以及总结归纳

    算法分类 十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序. 线性时间非比较类排序:不通过 ...

  2. Kd-Tree算法原理和开源实现代码

    本文介绍一种用于高维空间中的高速近期邻和近似近期邻查找技术--Kd-Tree(Kd树). Kd-Tree,即K-dimensional tree,是一种高维索引树形数据结构,经常使用于在大规模的高维数 ...

  3. MinFilter(MaxFilter)快速算法C++实现

    目录 1.算法简述 1.1.MinFilter(MaxFilter) 算法简述 1.2.MinFilter(MaxFilter) 快速算法简述 2.实现代码 2.1.MinFilterOneRow 单 ...

  4. SSE图像算法优化系列三十:GIMP中的Noise Reduction算法原理及快速实现。

    GIMP源代码链接:https://gitlab.gnome.org/GNOME/gimp/-/archive/master/gimp-master.zip GEGL相关代码链接:https://gi ...

  5. (转)RSA算法原理

    RSA算法原理(二)   作者: 阮一峰 日期: 2013年7月 4日 上一次,我介绍了一些数论知识. 有了这些知识,我们就可以看懂RSA算法.这是目前地球上最重要的加密算法. 六.密钥生成的步骤 我 ...

  6. 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】

    原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...

  7. GBDT算法原理深入解析

    GBDT算法原理深入解析 标签: 机器学习 集成学习 GBM GBDT XGBoost 梯度提升(Gradient boosting)是一种用于回归.分类和排序任务的机器学习技术,属于Boosting ...

  8. kmeans算法原理以及实践操作(多种k值确定以及如何选取初始点方法)

    kmeans一般在数据分析前期使用,选取适当的k,将数据聚类后,然后研究不同聚类下数据的特点. 算法原理: (1) 随机选取k个中心点: (2) 在第j次迭代中,对于每个样本点,选取最近的中心点,归为 ...

  9. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

随机推荐

  1. 【THUWC2017】在美妙的数学王国中畅游(bzoj5020)

    我数学是真的菜!! 清华光用数学知识就把我吊起来打,我还是太菜了 题解 如果每座城市的 $f$ 都是 $3$,维护一下树的路径上的 $\sum a,\space \sum b$ 即可. 其实就是维护一 ...

  2. jenkins配置发送邮件

    1.打开系统管理->系统设置,找到邮件设置,如下: 2.SMTP或者其他方式的发送邮件,可自行配置,一下列出了qq邮箱和163邮箱设置的地方,如下图: qq邮箱: 往下拉,找到如下图: 163邮 ...

  3. 在vue项目当中使用sass

    需要分别安装node-sass 和 sass-loader;可以不需要ruby; webpack当中配置 { test: /\.vue$/, loader: 'vue-loader', options ...

  4. ios 的版本记录

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; CFShow(infoDictionary); // ap ...

  5. Notepad++中常用的插件【转】

    转自:http://www.crifan.com/files/doc/docbook/rec_soft_npp/release/htmls/npp_common_plugins.html 1.4. N ...

  6. 算法 & 数据结构——任意多边形填充

    需求 . 在计算机中,选区是一个很常见的功能,例如windows按住鼠标左键拖动划出矩形选区,Photshop通过钢笔工具任意形状选区.选区本身不过是通过线段闭合的一个几何形状,但是如何填充这个选区, ...

  7. LeetCode OJ--Path Sum II **

    https://oj.leetcode.com/problems/path-sum-ii/ 树的深搜,从根到叶子,并记录符合条件的路径. 注意参数的传递,是否需要使用引用. #include < ...

  8. AC日记——软件包管理器 洛谷 P2416

    题目描述 Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个 ...

  9. AC日记——旅行 洛谷 P3313

    题目描述 S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神教.隐形独角兽教.绝地教都是常见的信仰. 为了方便,我 ...

  10. 【APIO2015】Jakarta Skyscrapers

    题目描述 印尼首都雅加达市有 $N$ 座摩天楼,它们排列成一条直线,我们从左到右依次将它们编号为 $0$ 到 $N − 1$.除了这 $N$ 座摩天楼外,雅加达市没有其他摩天楼. 有 $M$ 只叫做 ...