Update:9/10/2022 鸽了太久…增补了一些新的表述和简单推导,以及FFT在算法竞赛中的应用部分。帖子里的代码已经分别在2021全国大学生电子设计竞赛、洛谷OJ和课程设计中实战过,可靠性有保障。
Origin:10/23/2021 原始文章,刚学完FFT时做的一些笔记…漏洞挺多
找一本数字信号处理的书,把DFT的原理耐心看一遍就能明白所有前置知识的概念,比如什么是W(N,nk),为什么要把实数序列拓展到复数域上,不要看xxx博文的介绍。FFT就是DFT的一种快速实现算法,DFT复杂度O(
n
2
n^2
n2),FFT可以把复杂度降到O(
n
l
o
g
n
nlogn
nlogn)。FFT分为基2 时间抽取法与基2 频率抽取法,本文介绍的是时间抽取法。
FFT的实现步骤主要分为三步:
就是把序列下标的二进制编码倒置,新序列x‘[i]=x[rev(i)],软件法或者硬件寻址法(比如DSP)都可以。

W就是
W
N
2
M
−
i
j
{W_\frac{N}{2^{M-i}}}^{j}
W2M−iNj,
蝶形系数有三个重要性质:
这里主要用到可约性。我们可以把所有 W N 2 M − i j {W_\frac{N}{2^{M-i}}}^{j} W2M−iNj化为 W N j ⋅ 2 M − i {W_N}^{j\cdot 2^{M-i}} WNj⋅2M−i,这样,所有的 W N i ( 0 < = i < = N ) {W_N}^{i} (0<=i<=N) WNi(0<=i<=N)就可以直接预处理完成,蝶形计算中要用到的时候直接调用即可。

虽然FFT的原理是把每一级的序列细分为奇数下标组和偶数下标组,但由于FFT蝶形计算具有原位同址的特点,第
i
i
i 级蝶形输出仅与第
i
−
1
i-1
i−1 级的输入有关,所以我们不用关心每一级的第
i
i
i 个位置上具体是
x
x
x 序列中原本第几个元素,直接按照蝶形方向计算前进即可。我们可以观察到随着每一级的提升,蝶形会"膨胀",第
i
i
i 级一个蝴蝶结两端的间隔是
2
i
−
1
2^{i-1}
2i−1。根据以上特征,将每一级的序列分治为两个处理区间
[
l
,
l
+
n
−
1
]
[l,l+n-1]
[l,l+n−1] 和
[
l
+
n
,
r
]
[l+n,r]
[l+n,r],使用递归法实现FFT核心程序,递归法的模型主要就是遍历区间的完全二叉树,以根节点为入口,遍历到每个叶子结点后进行最小蝶形子运算,然后再回溯更新其父节点区间,回溯到根节点即可得到输出序列,具体的实现过程如下所示:
首先,蝶形运算处理过程可以抽象为以下完全二叉树:

然后,整个蝶形运算的过程可以表示为:

有FFT变换自然就会有其逆变换IFFT,欣喜的是,IFFT也可以靠FFT算法实现!操作步骤如下:
举个例子,有了FFT,我们可以得到任意时刻音乐信号的实时频谱(音乐软件的特效也大多是FFT频谱可视化),将信号的频谱分布得到后,我们就可以进行进一步的处理,比如加一个FIR数字滤波器,实现一个均衡器的功能。
ACM和OI感觉还是用的挺多的…主要是用在快速求解多项式系数上(卷积)。作为EE人,个人不是很认同算法竞赛圈里普遍的复变函数推导FFT,感觉多少有点魔怔了,把简单问题复杂化。可以给一个信号处理视角的FFT卷积运算策略。
首先,两个序列
x
(
n
)
x(n)
x(n) 和
h
(
n
)
h(n)
h(n) 卷积
x
(
n
)
∗
h
(
n
)
x(n) * h(n)
x(n)∗h(n)这个操作看作是实现了一个FIR滤波器。
然后,我们把FFT的变换从数学中的实数域到复数域变换看成是时间域到频域的时频变换。
为什么信号处理中经常要考虑时频变换呢?因为一个时域信号的波形往往是很奇形怪状的,看不出什么花头,反而是频域信号,我们可以通过信号的频谱了解构成这个信号的各个频率分量。
下面给出一个信号与系统中的一个时频变换经典结论
x
(
n
)
∗
h
(
n
)
<
时频变换
>
X
(
k
)
H
(
k
)
x(n) * h(n)<\frac{时频变换}{}> X(k)H(k)
x(n)∗h(n)<时频变换>X(k)H(k)
也就是说,时域里做卷积运算相当于频域里做乘积。
而FFT就是实现时频变换的好工具。
因此,快速求解
x
(
n
)
∗
h
(
n
)
x(n)*h(n)
x(n)∗h(n) 的方法就变成了

洛谷模板Code:洛谷P3803 【模板】多项式乘法(FFT)
算法竞赛中FFT我自己接触的不是太多(太弱了摸不到做FFT的题捏),但了解了一下似乎是能实现大整数乘法、还有动态规划决策求解(货币系统类问题)。
不过听打ACM的朋友说现在FFT已经成每个队都会的签到题了????
递归法实现(不是最好的方法,但是最直观的)
ST官方库有一个用纯汇编实现的FFT,72Mhz的单片机系统下只要4ms就能跑完1024点FFT,以后有机会去逆向一下嘻嘻。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define pi 3.1415926
using namespace std;
/***
Codes by LZH on 10/23/2021
***/
typedef struct Complex{
double a,b; //a:real b:Imagine
};
const int N=1024; //Num of Sample Nodes
const double fs=2048; //Sample frequency
Complex x[N],u[N],W[N]; //x:the fft sequence
const int resolution=fs/N; //Calculation frequency resolution
Complex comp_plus(Complex u,Complex v){
Complex e;
e.a=u.a+v.a; e.b=u.b+v.b;
return e;
}
Complex comp_times(Complex u,Complex v){
Complex e;
e.a=u.a*v.a-u.b*v.b;
e.b=u.a*v.b+u.b*v.a;
return e;
}
Complex comp_minus(Complex u,Complex v){
Complex e;
e.a=u.a-v.a; e.b=u.b-v.b;
return e;
}
void rev(){ //reverse the sequence in bit order.
int len=log2(N);
int idd,ret,bit;
for (int i=0; i<N; i++){
idd=i; ret=0;
for (int j=0; j<len; j++){
ret = ret << 1;
bit = idd & 1;
idd = idd >> 1;
ret = bit | ret;
}
u[i]=x[ret];
}
for (int i=0; i<N; i++)
x[i]=u[i];
}
Complex Wn(double A,double B){
Complex u;
u.a=cos((2*pi/A)*B); u.b=-sin((2*pi/A)*B);
return u;
}
void fft(int l,int r,int len){
Complex tmp;
int n=len/2;
if (len<2) return; //Level 1
fft(l,l+n-1,n); //even seg process
fft(l+n,r,n); //odd seg process
for (int i=l; i<=r; i++){
if (i<l+n){
u[i]=comp_plus(x[i],comp_times(W[(i-l)*(N/len)],x[i+n]));
}
else{
x[i]=comp_minus(x[i-n],comp_times(x[i],W[(i-n-l)*(N/len)]));
x[i-n]=u[i-n];
}
}
return;
}
int main(){
for (int i=0; i<N; i++){
double t=i/fs; //time resolution
x[i].a=114*sin(2*pi*50*t)+514*sin(2*pi*152*t)+1919*sin(2*pi*536*t)+810*sin(2*pi*996*t); //Generate the original signal sequence and tranfer it to Complex number field
x[i].b=0; //Im{x(n)}=0
W[i]=Wn(N,i); //e^-j((2*pi*i)/N)
}
rev(); //reverse the original order
fft(0,N-1,N);
for (int i=0; i<N/2; i++){
double u;
u=sqrt(x[i].a*x[i].a+x[i].b*x[i].b);
printf("%d %.3lf\n",i*resolution,u*2/N); //the real Ampltude of the signal is the Amp of fft sequence times 2 divide N.
}
return 0;
}
上面的代码演示了采样点N=1024,采样频率=2048的FFT,原始信号是四个正弦信号的叠加,频率分别是:50,152,536,996;幅度是:114,514,1919,810。通过实验结果,我们可以发现FFT还是挺成功的,没有发生明显的频谱泄漏。这里要注意的是FFT中信号抽样依然要满足奈奎斯特采样定理 (
f
s
>
=
2
f
c
f_s>=2f_c
fs>=2fc)。

1.Q:为什么我用这套程序跑1000点的FFT会跟Matlab出来不一样?
A:帖子里给出的是基2 FFT,也是最常用的一种FFT。基2的含义是FFT运算的点数必须是
2
n
2^n
2n ,如果要运算1000点,请先通过补零将序列长度补充到1024,数字信号处理的知识告诉我们,对任意序列补零后再进行FFT运算,并不影响其运算结果。Matlab中任意点数的FFT是另外一种FFT的实现方法,实现过程相当复杂,暂时网络上没看到什么详细的开源工程。
2.Q:频谱泄露那么严重该怎么解决?
A:可以通过加窗进行改善。常用的有汉宁窗、海明窗、布莱克曼窗(幅值特性保留最好)。但是加窗之后会造成频谱能量的衰减,这是因为由帕萨瓦尔定理可知同一信号在时域内和频域内的总能量必然是相等的,但加窗这个过程是一种数学上的近似衰减,这必然会损失部分信号的原始能量。
3.Q:嵌入式系统的内存资源太紧张,FFT跑个1024点都很勉强怎么办?
A:这是工程上一个很常见的问题,嗯…内存优化,目前我只知道工程上64点FFT就够用了,好像需要对FFT进行改进一下进行等效操作…这个本科数字信号处理教材上还真没讲过…内存优化的话还是看看startup.s里怎么进行优化吧,实际项目中似乎启动用的汇编文件内存开销是最大的。
4.Q:上面的例子进一步加速的方法?
A:1.用循环版的非递归实现法。2.序列重排复杂度可以再降到
O
(
n
)
O(n)
O(n) , 具体可以参考OI Wiki上的方法。
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项
我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)
虽然1.8.7的构建我似乎有一个向后移植的Shellwords::shellescape版本,但我知道该方法是1.9的一个特性,在1.8的早期版本中绝对不支持.有谁知道我在哪里可以找到(以Gem形式或仅作为片段)针对Ruby转义的Bourne-shell命令的强大独立实现? 最佳答案 您也可以从shellwords.rb中复制您想要的内容。在Ruby的颠覆存储库的主干中(即GPLv2'd):defshellescape(str)#Anemptyargumentwillbeskipped,soreturnemptyquotes.ret