【学习笔记】超简单的快速傅里叶变换(FFT)(含全套证明)

整理的算法模板合集: ACM模板


点我看多项式全家桶(●^ ◡ _◡ ^●)

学习笔记(用ipad记录的笔记)

超级简单的快速傅里叶变换!只要基础够扎实,顺着推一遍没有什么难以理解的,我学的整个过程没有一点卡壳,真的很爽,整整写了三个多小时,写了满满6页的笔记。主要是内容太多了。又花了几个小时的时间整理整个过程,梳理出这篇博客,一万字有余。

我的数学博客一般都会包含全套证明,没有证明我会很难受的hhh。

一、概念概述

快速傅里叶变换 (fast Fourier transform), 即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,简称FFT。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,FFT算法计算量的节省就越显著。

离散傅里叶变换(Discrete Fourier Transform,缩写为 DFT),是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其 DTFT 的频域采样。

FFT 是一种高效实现 DFT 的算法,称为快速傅立叶变换(Fast Fourier Transform,FFT)。它对傅里叶变换的理论并没有新的发现,但是对于在计算机系统或者说数字系统中应用离散傅立叶变换,可以说是进了一大步。快速数论变换 (NTT) 是快速傅里叶变换(FFT)在数论基础上的实现。

在 1965 年,Cooley 和 Tukey 发表了快速傅里叶变换算法。事实上 FFT 早在这之前就被发现过了,但是在当时现代计算机并未问世,人们没有意识到 FFT 的重要性。一些调查者认为 FFT 是由 Runge 和 König 在 1924 年发现的。但事实上高斯早在 1805 年就发明了这个算法,但一直没有发表。

上述内容摘自百度hhh

就是想让大家了解一下我们将要学的东西到底有多🐂🍺(●’◡’●)。然后进入我们今天的正题:

二、前置知识

首先是一些基础概念:

1. 多项式

学习笔记
多项式乘法 —— 卷积

我们上面说了多项式 × \times × 多项式 = = = 新的多项式。

所以我们最关心的操作就是两个多项式的乘法 —— 卷积。

广义上的卷积为:

h ( x ) = ∫ − ∞ ∞ g ( τ ) ⋅ f ( x − τ ) d τ {h(x) = \int _{- \infty} ^{\infty}g(\tau) \cdot f(x - \tau)} \rm{d\tau} h(x)=g(τ)f(xτ)dτ

我们这里仅讨论多项式域,即两个多项式 / 数组 / 序列的卷积。

即给定两个多项式 f ( x ) f(x) f(x) g ( x ) g(x) g(x)

f ( x ) = a 0 + a 1 x + ⋯ + a n x n f(x)= a_0 + a_1x+ \dots +a_nx^n f(x)=a0+a1x++anxn

g ( x ) = b 0 + b 1 x + ⋯ + b m x m g(x)= b_0 + b_1x+ \dots +b_mx^m g(x)=b0+b1x++bmxm

计算多项式 Q ( x ) = f ( x )   ⋅   g ( x ) : Q(x) = f(x)\ ·\ g(x): Q(x)=f(x)  g(x):

Q ( x ) = ∑ i = 0 n ∑ j = 0 m a i b j x i + j = c 0 + c 1 x + ⋯ + c n + m x n + m \boxed {Q(x) = \sum \limits_ {i = 0} ^ n \sum \limits_ {j = 0 } ^ m a_i b_j x ^ {i + j}} = c_0 + c_1 x + \dots + c_ {n + m} x ^ {n + m} Q(x)=i=0nj=0maibjxi+j=c0+c1x++cn+mxn+m

C ( x ) = A ( x ) ∗ B ( x ) = ∑ i = 0 n ( ∑ j = 0 i a j b i − j ) x j + i − j = ∑ i = 0 n ( ∑ j = 0 i a j b i − j ) x i C(x)=A(x)*B(x)=\sum_{i = 0}^{n}(\sum_{j=0}^{i}a_j b_{i-j})x^{j+i-j}=\sum_{i = 0}^{n}(\sum_{j=0}^{i}a_j b_{i-j})x^{i} C(x)=A(x)B(x)=i=0n(j=0iajbij)xj+ij=i=0n(j=0iajbij)xi

也就是对于 C ( x ) C(x) C(x) 的第 i i i 项的系数:

[ i ] C ( x ) = ∑ j = 0 i a j b i − j [i]C(x)=\sum_{j=0}^{i}a_j b_{i-j} [i]C(x)=j=0iajbij

       这里使用的就是上面介绍的系数表示,我们如果直接暴力相乘很明显是一个 O ( n m ) O(nm) O(nm) 的复杂度,因为我们每一项都需要相乘。但是我们可以通过快速傅里叶变换将常用的系数表示转为点值表示用 O ( n l o g n ) O(nlogn) O(nlogn) 的时间复杂度下计算得到答案,再逆变换转成系数表示输出

2. 复数

在这里插入图片描述


复数分为实部和虚部,可以描述为一个二元组 ( x , y ) (x,y) (x,y),表示这个数等于 x + y − 1 x+y\sqrt {-1} x+y1 。一般用 i i i 表示 − 1 \sqrt{-1} 1

由于是个二元组,所以它在理解的时候可以抽象为一个二维向量,分布在平面直角坐标系上。

事实上,它确实也有不少性质和向量相同。

  • 复数的模

定义复数的模,为复平面原点到 ( a , b ) (a,b) (a,b)的距离,即 ∣ z ∣ = x 2 + y 2 |z|=\sqrt {x^2+y^2} z=x2+y2

定义 z z z范数 N ( z ) = ∣ z ∣ 2 = x 2 + y 2 N(z)=|z|^2=x^2+y^2 N(z)=z2=x2+y2

  • 共轭复数

复数 z = a + b i z=a+bi z=a+bi 的共轭是 z ′ = a − b i z'=a-bi z=abi,记作 z ˉ \bar z zˉ

性质: z × z ˉ = a 2 + b 2 z\times \bar z=a^2+b^2 z×zˉ=a2+b

  • 34
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
### 回答1: 快速傅里叶变换FFT)是一种高效的计算时间域信号的频域表示的算法。下面是于C语言的快速傅里叶变换FFT)算法,注释说明了每个步骤的作用。 ```c #include <stdio.h> #include <complex.h> #include <math.h> // 前置声明函数 void fft(complex double x[], int N); void bit_reverse(complex double x[], int N); int main() { // 定义输入信号和长度 complex double x[] = {1, 1, 1, 1, 0, 0, 0, 0}; int N = sizeof(x) / sizeof(x[0]); // 执行FFT变换 fft(x, N); // 输出结果 for (int i = 0; i < N; i++) { printf("%f + %f i\n", creal(x[i]), cimag(x[i])); } return 0; } // 快速傅里叶变换算法 void fft(complex double x[], int N) { // 将输入信号按位反转 bit_reverse(x, N); for (int N_s = 2; N_s <= N; N_s *= 2) { // 计算旋转因子 complex double Wn = cexp(-2 * M_PI * I / N_s); // 迭代计算每个级别的蝶形运算 for (int k = 0; k < N; k += N_s) { complex double w = 1; for (int j = 0; j < N_s / 2; j++) { complex double t = w * x[k + j + N_s / 2]; complex double u = x[k + j]; // 蝶形运算 x[k + j] = u + t; x[k + j + N_s / 2] = u - t; w *= Wn; } } } } // 按位反转函数 void bit_reverse(complex double x[], int N) { int i, j, k; for (i = 1, j = N / 2; i < N - 1; i++) { if (i < j) { complex double temp = x[j]; x[j] = x[i]; x[i] = temp; } k = N / 2; while (j >= k) { j -= k; k /= 2; } j += k; } } ``` 该代码实现了一个简单的8点FFT算法。首先,需要定义一个复数数组`x[]`来存储输入信号,并指定输入信号的长度`N`。然后,通过调用`fft()`函数来执行FFT变换,并将结果存储在输入信号数组中。最后,使用循环输出变换后的信号结果。 在`fft()`函数中,首先调用`bit_reverse()`函数按位反转输入信号数组。然后,通过循环进行迭代计算,每次迭代都完成当前级别的蝶形运算,直到完成全部级别的计算。在蝶形运算过程中,使用旋转因子`Wn`来乘以输入信号数组的一部分,并进行加法和减法运算,得到新的输出结果。 `bit_reverse()`函数用于按位反转输入信号数组。通过循环将输入信号的位进行反转以实现这一目标。 请注意,这只是一个简单的示例代码,用于说明FFT算法的本原理。在实际应用中,可能需要优化计算过程以提高性能,并处理更大的输入信号。 ### 回答2: 快速傅里叶变换FFT)是一种高效计算离散傅里叶变换(DFT)的算法,它能够将一个长度为N的序列转换为频域上的N个频率成分。下面是一个于C语言的FFT算法的示例: ```c #include <stdio.h> #include <math.h> #define PI 3.14159265359 // 定义复数结构体 typedef struct { double real; // 实部 double imag; // 虚部 } Complex; // 计算FFT void fft(Complex* x, int N) { if (N <= 1) { return; } // 将输入序列拆分成奇偶部分 Complex* even = (Complex*) malloc(N/2 * sizeof(Complex)); Complex* odd = (Complex*) malloc(N/2 * sizeof(Complex)); for (int i = 0; i < N/2; i++) { even[i] = x[2 * i]; odd[i] = x[2 * i + 1]; } // 递归计算奇偶部分的FFT fft(even, N/2); fft(odd, N/2); // 合并奇偶部分的结果 for (int k = 0; k < N/2; k++) { double angle = -2*PI*k/N; Complex w = {cos(angle), sin(angle)}; Complex t = {w.real * odd[k].real - w.imag * odd[k].imag, w.real * odd[k].imag + w.imag * odd[k].real}; x[k] = {even[k].real + t.real, even[k].imag + t.imag}; x[k + N/2] = {even[k].real - t.real, even[k].imag - t.imag}; } free(even); free(odd); } int main() { // 输入序列与长度 Complex x[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}}; int N = sizeof(x) / sizeof(x[0]); // 计算FFT fft(x, N); // 输出结果 for (int i = 0; i < N; i++) { printf("X[%d] = %f + %fi\n", i, x[i].real, x[i].imag); } return 0; } ``` 上述代码实现了一个FFT算法,并打印出计算结果。首先定义了一个复数结构体,然后使用递归方式计算FFT。在计算过程中,将输入序列拆分成奇偶部分,然后递归计算奇偶部分的FFT,最后合并奇偶部分的结果。 在主函数中,定义了一个输入序列x,并调用fft函数计算FFT。最后输出计算结果。 这段代码可以通过将输入序列修改为需要计算FFT的序列,然后运行程序来获得FFT的结果。 ### 回答3: 快速傅里叶变换FFT)算法是一种高效的计算离散傅里叶变换(DFT)的方法,广泛应用于信号处理、图像处理和音频处理等领域。下面是于C语言实现的FFT算法,包详细的注释: ```c #include <stdio.h> #include <math.h> // 计算FFT void fft(double real[], double imag[], int N) { int i, j, k; int m, n, L, Lk; double theta, wpr, wpi, wr, wi, tmpReal, tmpImag; // 确定计算层数L L = log2(N); // 通过蝶形运算计算FFT for (m = 1; m <= L; ++m) { // 计算蝶形运算的间隔 n = pow(2, m); Lk = N / n; // 计算旋转因子e^(-2*pi*i/N) theta = -2 * M_PI / n; wpr = cos(theta); wpi = sin(theta); // 循环遍历每个蝶形运算 for (k = 0; k < N; k += n) { wr = 1; wi = 0; // 进行蝶形运算 for (j = 0; j < Lk; ++j) { // 计算下标 i = k + j; // 计算蝶形运算 tmpReal = real[i + Lk] * wr - imag[i + Lk] * wi; tmpImag = imag[i + Lk] * wr + real[i + Lk] * wi; // 更新结果 real[i + Lk] = real[i] - tmpReal; imag[i + Lk] = imag[i] - tmpImag; real[i] += tmpReal; imag[i] += tmpImag; // 更新旋转因子 tmpReal = wr; wr = wr * wpr - wi * wpi; wi = wi * wpr + tmpReal * wpi; } } } } int main() { int N = 8; // 输入序列长度 double real[] = {1, 2, 3, 4, 0, 0, 0, 0}; // 实部 double imag[] = {0, 0, 0, 0, 0, 0, 0, 0}; // 虚部 int i; // 输出原始数据 printf("原始数据:\n"); for (i = 0; i < N; ++i) { printf("%.2f + %.2fi\n", real[i], imag[i]); } // 计算FFT fft(real, imag, N); // 输出FFT结果 printf("FFT结果:\n"); for (i = 0; i < N; ++i) { printf("%.2f + %.2fi\n", real[i], imag[i]); } return 0; } ``` 以上代码实现了一个简单FFT算法示例,

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值