当前位置: 首页 > news >正文

快速傅里叶变换 FFT

前置知识:离散时间傅里叶变换

和一些复变函数的小知识

离散时间傅里叶变换 & 离散傅里叶级数

离散时间傅里叶变换定义式:\(X(j\Omega)=\sum\limits^{\infty}_{n=-\infty}x_n e^{-j\Omega n}\)

性质:时域上的卷积等价于经过傅里叶变换后在频域上做乘法然后反变换回来

考虑到一个离散信号可以写成一个形式幂级数,那么两个信号的卷积自然对应多项式乘法

那么就可以通过傅里叶变换的方式算多项式乘法

离散时间傅里叶级数定义式:\(\tilde{X_k}=\sum\limits^{N}_{n=0}x_n e^{-j\frac{2\pi}{N} k}\)

离散傅里叶变换

离散傅里叶级数要求有周期并且长度有限

一般的离散信号没有周期,但是长度有限是满足的,但是值域是连续的

考虑抽样的过程,一个域抽样在另一个域表示周期延拓

所以我们可以很自然的把有限信号周期延拓一下,这样可以用离散傅里叶级数的计算公式了

这么做就相当于是在连续的频域上抽样,

所以我们接下来就可以利用抽样和恢复的技术把这个信号的频域离散下来存起来了


解释完原理之后我们直接照搬离散傅里叶级数的计算公式就可以

\(\tilde{X_k}=\sum\limits^{N}_{n=0}x_n e^{-j\frac{2\pi}{N} k}\)

这样卷积就可以变成乘法,做乘法的时间显然是 \(O(n)\)

但是这样朴素的过程,时间复杂度显然是 \(O(n^2)\) 考虑优化

快速傅里叶变换(递归)

注意到 \(e^{-j\frac{2\pi}{N} k}=-\omega ^{k}_{n}=\cos {\frac{2\pi}{N} k}-j\frac{2\pi}{N} k\)

而且还有一个 \(\omega^{k}_{2n}=-\omega^{k+n}_{2n}\)

那么我们其实悄无声息的凑出了一个 \((-1)^n\) 这个项

对于高中数列学的比较好的估计想到了凑奇偶项

假设所有偶数项构成 \(G(x)=a_0+a_2x^2+a_4x^4+\cdots\) 奇数项构成 \(H(x)=a_1x+a_3x^3+a_5x^5+\cdots\)

则有 \(F(x)=G(x)+xH(x)\)

对于 \(F(\omega^{k}_{2n})=G(\omega^{k}_{2n})+\omega^{k}_{2n}H(\omega^{k}_{2n})\)

对于 \(F(\omega^{k}_{2n})=G(\omega^{k+n}_{2n})+\omega^{k+n}_{2n}H(\omega^{k+n}_{2n})=G(\omega^{k}_{2n})-\omega^{k}_{2n}H(\omega^{k}_{2n})\)

并且还有 \(G(x),H(x)\) 也可以依照 \(F(x)\) 做处理

而且性质更好的是,\(G(x)\)\(H(x)\) 显然对应 \(F(x^2)\),也就是下角标除了个 \(2\)

也就是至多会递归 \(\log n\)

时间复杂度 \(O(n\log n)\)

代码直接模拟上述过程即可,

由于每一层递归处理的项数不变,所以每一层看似函数的数量增长很多,实际上是 \(O(n)\)

然后要注意的是,这个分治其实一直要求下角标是偶数,所以一定要把长度补成 \(2\) 的幂次

递归版FFT实现多项式乘法
#include <bits/stdc++.h>
using namespace std;
#define fu complex<double>
const fu I=(0,1);
const double pi=acos(-1),e=exp(1);
vector<fu> FFT(vector<fu>a,int val){int N=a.size();vector<fu> F(N); if(a.size()==1)return a;vector<fu> g,h;for(int i=0;i<N;i++){if(i&1)h.push_back(a[i]);else g.push_back(a[i]);}fu now={1,0},omega={cos(2*pi/N),val*sin(2*pi/N)};vector<fu> G=FFT(g,val),H=FFT(h,val);for(int i=0;i<N/2;i++,now*=omega){F[i]=G[i]+now*H[i];F[i+N/2]=G[i]-now*H[i];}return F;
}
int read(){int i=1,j=0;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-')i=-1;ch=getchar();}while(ch>='0'&&ch<='9'){j=j*10+ch-48;ch=getchar();}return i*j;
}
int lg(int x){if(x==1)return 0;return lg(x/2)+1;
}
int main(){int n=read(),m=read();int len=(1<<lg(n+m+2))==(n+m+2)?(1<<lg(n+m+2)):(1<<(lg(n+m+2)+1));vector<fu>a(len),b(len),c;for(int i=0;i<=n;i++)a[i]=1.0*read();for(int i=0;i<=m;i++)b[i]=1.0*read();vector<fu> F=FFT(a,-1),G=FFT(b,-1),H(len);for(int i=0;i<len;i++)H[i]=F[i]*G[i];c=FFT(H,1);for(int i=0;i<n+m+1;i++)cout<<(int)(c[i].real()/len+0.5)<<" ";return 0;
}

优化

第一个优化是位逆序排列优化

在递归的过程,涉及到 \(G\)\(H\) 的重新排列和向下递归

这个优化主要是针对重排到单点时的系数

具体而言:递归到底时,该位的系数在原序列的位置,是这个位的二进制倒写

证明考虑奇偶项分类的过程,实际上是将最后一位分开了

那么末尾取 \(0\) 向左递归 末位取 \(1\) 向右递归的过程,

实际上是根据当前最后一位的结果确定最高位的结果,这两位是完全相同的

把这一位删掉,递归地考虑这个过程即得。

所以可以实现预处理每一位最后的位置,然后向上合并的过程中更新

这个预处理的过程可以直接 \(O(n)\) 做完

然后是额外的 \(O(1)\) 空间更新

位逆序排列优化完了之后,相邻的两项就是一个 \(G\) 和 一个 \(H\)

同时这个 \(G\)\(H\) 我们接下来也用不到

同时这两个位置恰好也是 \(F\) 的位置

所以实际上没有必要额外开空间就能做了

非递归版FFT实现多项式乘法
#include <bits/stdc++.h>
using namespace std;
#define fu complex<double>
const int o=2222222;
const double pi=acos(-1);
int r[o];
fu a[o],b[o];
void pre(int n){int x=n/2,p=0;r[p++]=0,r[p++]=x;for(int i=2;i<=n;i*=2){x/=2;for(int j=0;j<i;j++)r[p++]=r[j]|x;}
}
void FFT(fu *a,int n,int val){for(int i=0;i<n;i++){if(i<r[i])swap(a[i],a[r[i]]);}for(int i=2;i<=n;i*=2){for(int j=0;j<n;j+=i){fu now(1,0),omega(cos(2*pi/i),val*sin(2*pi/i));for(int k=j;k<j+i/2;k++,now*=omega){fu x=a[k],y=a[k+i/2];a[k]=x+now*y;a[k+i/2]=x-now*y; }}}
}
int read(){int i=1,j=0;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-')i=-1;ch=getchar();}while(ch>='0'&&ch<='9'){j=j*10+ch-48;ch=getchar();}return i*j;
}
int lg(int x){if(x==1)return 0;return lg(x/2)+1;
}
int main(){int n=read(),m=read();int len=(1<<lg(n+m+2))==(n+m+2)?(1<<lg(n+m+2)):(1<<(lg(n+m+2)+1));pre(len);for(int i=0;i<=n;i++)a[i]={1.0*read(),0};for(int i=0;i<=m;i++)b[i]={1.0*read(),0};FFT(a,len,-1),FFT(b,len,-1);for(int i=0;i<len;i++)a[i]=a[i]*b[i];FFT(a,len,1);for(int i=0;i<n+m+1;i++)printf("%d ",(int)(a[i].real()/len+0.5));return 0;
}
http://www.jsqmd.com/news/428511/

相关文章:

  • 如何通过 C# 实现 PDF 文本提取?
  • 颗粒计数器怎么选?环保、制造、科研场景下的5大头部厂商横向对比 - 深度智识库
  • 闭眼入!9个降AIGC软件测评:MBA降AI率必备工具推荐
  • 2026年2月四川空调制冷设备/二手空调/冻库/冻库制冷设备/冷库设备/保鲜库/冻库设备供应商权威选购白皮书:市场分化下的价值选择 - 2026年企业推荐榜
  • 2026天然石市场口碑榜:这些厂家值得您关注,石材/砌墙石/天然石/脚踏石/文化石/蘑菇石/贴墙石,天然石直销厂家排行 - 品牌推荐师
  • 2026年考研数学辅导哪家强,颜语堂多方位服务助力学子上岸 - mypinpai
  • RAG 的尽头是没有 RAG?阿里刚开源的这个狠活,把向量库掀了
  • 2026年AI芯片厂家推荐榜:CPU芯片/基带芯片/存储芯片/电源管理芯片全系供应,适配工业/汽车/消费多场景 - 品牌推荐官
  • 买机床用哪个软件靠谱?机床商务网深耕行业16载,打造工业母机生态闭环 - 品牌推荐大师1
  • 2026年北京小程序开发服务商深度解析|多场景定制如何驱动业务增长 - 品牌2026
  • MOS管电源适配器开关稳压控制应用:高效能转换的核心引擎
  • 机房不该靠“手速”撑着:聊聊运维自动化在数据中心里的真正价值
  • AI 写代码,会不会把 DevOps 直接“重做一遍”?
  • 2026/3/2
  • 2026年全国仓库地坪漆厂家哪家实力强?靠谱优质且适配多场景需求 - 深度智识库
  • MOS管在新能源动力电池包放电控制中的关键作用与阿赛姆解决方案
  • 高性价比细胞基因点突变服务商盘点:本土标杆海星生物与国际先进公司对比 - 品牌推荐大师1
  • 基于SpringBoot+Vue的书城阅读器系统设计与实现
  • linux xshell 能登陆上但xftp连不上 提示无法“127.0.0.1”建立连接【转】
  • SQL记录 备份全部结构38
  • 绵羊线粒体数据mafft多序列比对:线程数目对速度的影响
  • 【GitHub项目推荐--Fara-7B:微软高效计算机使用智能体模型】⭐⭐⭐
  • windows wsl 安装多操作系统
  • 烧菜火锅哪家火?排行前几名实力揭秘!社区火锅/美食/烧菜火锅/特色美食/火锅,烧菜火锅品牌排行榜单 - 品牌推荐师
  • 路由
  • 别只盯着离线指标了:用大数据把模型“在线状态”盯死
  • 别从每个房间找门了:一题《墙与门》看懂“多源 BFS”的威力
  • 小程序定制开发如何选择专业服务商?北京麦冬科技多行业解决方案解析 - 品牌2026
  • 市场口碑好的道路工程反光膜制造企业推荐几家 - 五色鹿五色鹿
  • 照着用就行:AI论文写作软件 千笔写作工具 VS WPS AI,研究生专属神器!