通过矩阵从整体角度搞懂快速傅里叶变换原理
离散傅里叶变换公式
公式
f[k]=∑n=0N−1g[n]e−i(2π/N)kn,其中(0<=n<N)f[k]=\sum_{n=0}^{N-1}g[n]e^{-i(2\pi/N)kn}, 其中(0<=n<N) f[k]=n=0∑N−1g[n]e−i(2π/N)kn,其中(0<=n<N)
逆变换公式
g[n]=1N∑k=0N−1f[k]ei(2π/N)kn,其中(0<=k<N)g[n]=\frac{1}{N}\sum_{k=0}^{N-1}f[k]e^{i(2\pi/N)kn}, 其中(0<=k<N) g[n]=N1k=0∑N−1f[k]ei(2π/N)kn,其中(0<=k<N)
快速傅里叶变换
从以上公式看,如果直接按照公式来求离散傅里叶变换,其时间复杂度是O(N^2)
快速傅里叶变换就是一种能在O(n*log(n))时间复杂度内进行傅里叶变换及其逆变换的算法
离散傅里叶变换公式矩阵表示
令
G=[g[0]g[1]⋮g[n−1]]F=[f[0]f[1]⋮f[n−1]]G= \begin{bmatrix} g[0] \\ g[1] \\ \vdots \\ g[n-1] \end{bmatrix} \\\ \\\ F= \begin{bmatrix} f[0] \\ f[1] \\ \vdots \\ f[n-1] \end{bmatrix} \\\ G=g[0]g[1]⋮g[n−1] F=f[0]f[1]⋮f[n−1]
E[i]=[e−i(2π/N)∗0∗0e−i(2π/N)∗0∗1⋯e−i(2π/N)∗0∗(n−1)e−i(2π/N)∗1∗095⋯e−i(2π/N)∗1∗(n−1)⋮⋮⋱⋱⋮⋮⋱e−i(2π/N)∗(n−1)∗(n−1)]E[i]=\begin{bmatrix} e^{-i(2\pi/N)*0*0} & e^{-i(2\pi/N)*0*1} & \cdots & e^{-i(2\pi/N)*0*(n-1)} \\ e^{-i(2\pi/N)*1*0} & 95 & \cdots & e^{-i(2\pi/N)*1*(n-1)} \\ \vdots & \vdots & \ddots & \ddots \\ \vdots & \vdots & \ddots & e^{-i(2\pi/N)*(n-1)*(n-1)} \\ \end{bmatrix} E[i]=e−i(2π/N)∗0∗0e−i(2π/N)∗1∗0⋮⋮e−i(2π/N)∗0∗195⋮⋮⋯⋯⋱⋱e−i(2π/N)∗0∗(n−1)e−i(2π/N)∗1∗(n−1)⋱e−i(2π/N)∗(n−1)∗(n−1)
则离散傅里叶公式可改写为
F=E[i]∗GF=E[i]*G F=E[i]∗G
逆变换可改写为
G=1NE[−i]∗FG=\frac{1}{N}E[-i]*F G=N1E[−i]∗F
注意: E[i]里的 i 是一个变量,i 即正虚数单位,代表正变换,-i 代表逆变换,下同。
递归求解
令
E[i][n]=[e−i(2π/N)∗k∗0e−i(2π/N)∗k∗1…e−i(2π/N)∗k∗(n−1)]E[i][n]0n=[e−i(2π/N)∗k∗0e−i(2π/N)∗k∗2…e−i(2π/N)∗k∗(n−2)]E[i][n]1n=[e−i(2π/N)∗k∗1e−i(2π/N)∗k∗3…e−i(2π/N)∗k∗(n−1)]E[i][n]=\begin{bmatrix} e^{-i(2\pi/N)*k*0} & e^{-i(2\pi/N)*k*1} & \dots & e^{-i(2\pi/N)*k*(n-1)}\\ \end{bmatrix} \\\ \\\ E[i][n]^{0n}=\begin{bmatrix} e^{-i(2\pi/N)*k*0} & e^{-i(2\pi/N)*k*2} & \dots & e^{-i(2\pi/N)*k*(n-2)}\\ \end{bmatrix} \\\ \\\ E[i][n]^{1n}=\begin{bmatrix} e^{-i(2\pi/N)*k*1} & e^{-i(2\pi/N)*k*3} & \dots & e^{-i(2\pi/N)*k*(n-1)}\\ \end{bmatrix} \\ E[i][n]=[e−i(2π/N)∗k∗0e−i(2π/N)∗k∗1…e−i(2π/N)∗k∗(n−1)] E[i][n]0n=[e−i(2π/N)∗k∗0e−i(2π/N)∗k∗2…e−i(2π/N)∗k∗(n−2)] E[i][n]1n=[e−i(2π/N)∗k∗1e−i(2π/N)∗k∗3…e−i(2π/N)∗k∗(n−1)]
则
E[i]=[E[0][n]0nE[0][n]1n⋮⋮E[n−1][n]0nE[n−1][n]1n]\\ E[i]= \begin{bmatrix} E[0][n]^{0n} & E[0][n]^{1n} \\ \vdots & \vdots \\ E[n-1][n]^{0n} & E[n-1][n]^{1n} \\ \end{bmatrix} \\ E[i]=E[0][n]0n⋮E[n−1][n]0nE[0][n]1n⋮E[n−1][n]1n
将E[i]矩阵竖着再切一刀,平分两半,用数学语言描述就是,
令
E00(n/2)=[E[0][n]0n⋮E[n/2−1][n]0n]E01(n/2)=[E[0][n]1n⋮E[n/2−1][n]1n]E10(n/2)=[E[n/2][n]0n⋮E[n−1][n]0n]E11(n/2)=[E[n/2][n]1n⋮E[n−1][n]1n]\\ E_{00(n/2)}= \begin{bmatrix} E[0][n]^{0n} \\ \vdots \\ E[n/2-1][n]^{0n}\\ \end{bmatrix} \\\ \\\ E_{01(n/2)}= \begin{bmatrix} E[0][n]^{1n} \\ \vdots \\ E[n/2-1][n]^{1n}\\ \end{bmatrix} \\\ \\\ E_{10(n/2)}= \begin{bmatrix} E[n/2][n]^{0n} \\ \vdots \\ E[n-1][n]^{0n}\\ \end{bmatrix} \\\ \\\ E_{11(n/2)}= \begin{bmatrix} E[n/2][n]^{1n} \\ \vdots \\ E[n-1][n]^{1n}\\ \end{bmatrix} \\\\ E00(n/2)=E[0][n]0n⋮E[n/2−1][n]0n E01(n/2)=E[0][n]1n⋮E[n/2−1][n]1n E10(n/2)=E[n/2][n]0n⋮E[n−1][n]0n E11(n/2)=E[n/2][n]1n⋮E[n−1][n]1n
于是
E[i]=[E00(n/2)E01(n/2)E10(n/2)E11(n/2)]E[i]=\begin{bmatrix} E_{00(n/2)} & E_{01(n/2)} \\ E_{10(n/2)} & E_{11(n/2)} \\ \end{bmatrix} E[i]=[E00(n/2)E10(n/2)E01(n/2)E11(n/2)]
再令
w[k]=e−i(2π/N)∗kw[k]=e^{-i(2\pi/N)*k} w[k]=e−i(2π/N)∗k
W0(n/2)=[w[0]00…00w[1]0…000w[2]…0⋮⋮⋮⋮0000…w[n/2−1]]W1(n/2)=[w[n/2]00…00w[n/2+1]0…000w[n/2+2]…0⋮⋮⋮⋮0000…w[n−1]]W^{0(n/2)}=\begin{bmatrix} w[0] & 0 & 0 & \dots & 0 \\ 0 & w[1] & 0 & \dots & 0 \\ 0 & 0 & w[2] & \dots & 0 \\ \vdots & \vdots & \vdots & \vdots & 0 \\ 0 & 0 & 0 & \dots & w[n/2-1] \\ \end{bmatrix} \\\ \\\ W^{1(n/2)}=\begin{bmatrix} w[n/2] & 0 & 0 & \dots & 0 \\ 0 & w[n/2+1] & 0 & \dots & 0 \\ 0 & 0 & w[n/2+2] & \dots & 0 \\ \vdots & \vdots & \vdots & \vdots & 0 \\ 0 & 0 & 0 & \dots & w[n-1] \\ \end{bmatrix} W0(n/2)=w[0]00⋮00w[1]0⋮000w[2]⋮0………⋮…0000w[n/2−1] W1(n/2)=w[n/2]00⋮00w[n/2+1]0⋮000w[n/2+2]⋮0………⋮…0000w[n−1]
于是
E[i]=[E00(n/2)E01(n/2)E10(n/2)E11(n/2)]=[E00(n/2)W0(n/2)∗E00(n/2)E10(n/2)W1(n/2)∗E10(n/2)]=[E00(n/2)W0(n/2)∗E00(n/2)−E00(n/2)−W1(n/2)∗E00(n/2)]=[E00(n/2)W0(n/2)∗E00(n/2)−E00(n/2)W0(n/2)∗E00(n/2)]E[i] =\begin{bmatrix} E_{00(n/2)} & E_{01(n/2)} \\ E_{10(n/2)} & E_{11(n/2)} \\ \end{bmatrix} \\\ \\\ =\begin{bmatrix} E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ E_{10(n/2)} & W^{1(n/2)}*E_{10(n/2)} \\ \end{bmatrix} \\\ \\\ =\begin{bmatrix} E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ -E_{00(n/2)} & -W^{1(n/2)}*E_{00(n/2)} \\ \end{bmatrix} \\\ \\\ =\begin{bmatrix} E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ -E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ \end{bmatrix} E[i]=[E00(n/2)E10(n/2)E01(n/2)E11(n/2)] =[E00(n/2)E10(n/2)W0(n/2)∗E00(n/2)W1(n/2)∗E10(n/2)] =[E00(n/2)−E00(n/2)W0(n/2)∗E00(n/2)−W1(n/2)∗E00(n/2)] =[E00(n/2)−E00(n/2)W0(n/2)∗E00(n/2)W0(n/2)∗E00(n/2)]
上面这一步就是整个转化的核心了。
再往后,令
G0(n/2)=[g[0]g[2]⋮g[n−2]]G1(n/2)=[g[1]g[3]⋮g[n−1]]G^{0(n/2)}= \begin{bmatrix} g[0] \\ g[2] \\ \vdots \\ g[n-2] \end{bmatrix} \\\ \\\ G^{1(n/2)}= \begin{bmatrix} g[1] \\ g[3] \\ \vdots \\ g[n-1] \end{bmatrix} \\\ G0(n/2)=g[0]g[2]⋮g[n−2] G1(n/2)=g[1]g[3]⋮g[n−1]
则
F=GE[i]F=[E00(n/2)W0(n/2)∗E00(n/2)−E00(n/2)W0(n/2)∗E00(n/2)]∗[G0(n/2)G1(n/2)]F=GE[i] \\\ F = \begin{bmatrix} E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ -E_{00(n/2)} & W^{0(n/2)}*E_{00(n/2)} \\ \end{bmatrix} * \begin{bmatrix} G^{0(n/2)} \\ G^{1(n/2)} \\ \end{bmatrix} \\ \\\\ F=GE[i] F=[E00(n/2)−E00(n/2)W0(n/2)∗E00(n/2)W0(n/2)∗E00(n/2)]∗[G0(n/2)G1(n/2)]
到这一步,可以看到,我们已经成功将一个 n 阶的离散傅里叶变换降为了 n/2 阶,到这里就实现了O(n*logn)时间复杂度的快速傅里叶算法。
逆变换的和正变换的大同小异,就不在赘述了。
如果想要更简洁的形式,更深刻的理解离散傅里叶变换,可以进一步化简。
令
M=E00(n/2)∗G0(n/2)N=E00(n/2)∗G1(n/2)Z=W0(n/2)M=E_{00(n/2)}*G^{0(n/2)} \\\\ N=E_{00(n/2)}*G^{1(n/2)} \\\\ Z=W^{0(n/2)} \\\\ M=E00(n/2)∗G0(n/2)N=E00(n/2)∗G1(n/2)Z=W0(n/2)
则
F=[M+Z∗N−M+Z∗N]F = \begin{bmatrix} M+Z*N \\ -M+Z*N \\ \end{bmatrix} F=[M+Z∗N−M+Z∗N]
最后,上代码
public class FFT {/*** 傅里叶变换* @param x* @return*/public static Complex[] fft(Complex[] x){return fftTrans(x, true);}/*** 逆傅里叶变换* @param x* @return*/public static Complex[] ifft(Complex[] x) {int N = x.length;Complex[] y = fftTrans(x, false);for (int n = 0; n < N; n++) {y[n] = y[n].divides(N);}return y;}public static Complex[] fftTrans(Complex[] x, boolean dire) {int N = x.length;Complex[] y = new Complex[N];if (N == 1) {y[0] = x[0];return y;}Complex[] x0 = new Complex[N/2];for (int n = 0; n < N; n += 2) {x0[n/2] = x[n];}Complex[] X0 = fftTrans(x0, dire);Complex[] x1 = new Complex[N/2];for (int n = 1; n < N; n += 2) {x1[n/2] = x[n];}Complex[] X1 = fftTrans(x1, dire);for (int k = 0; k < N / 2; k++) {int ci = -1;if (!dire) {ci=-ci;}Complex wnk = getWnk(N, k, ci);Complex wnkX1 = wnk.multiply(X1[k]);y[k] = X0[k].plus(wnkX1);y[k+N/2] = X0[k].minus(wnkX1);}return y;}private static Complex getWnk(int N, int k, int n) {double T = 2 * Math.PI / N;double kth = T * k * n;Complex wk = new Complex(Math.cos(kth), Math.sin(kth));return wk;}}
相关文章:
通过矩阵从整体角度搞懂快速傅里叶变换原理
离散傅里叶变换公式 公式 f[k]∑n0N−1g[n]e−i(2π/N)kn,其中(0<n<N)f[k]\sum_{n0}^{N-1}g[n]e^{-i(2\pi/N)kn}, 其中(0<n<N) f[k]n0∑N−1g[n]e−i(2π/N)kn,其中(0<n<N) 逆变换公式 g[n]1N∑k0N−1f[k]ei(2π/N)kn,其中(0<k<N)g[n]\frac{1}{N}\…...
【C++从0到1】25、C++中嵌套使用循环
C从0到1全系列教程 1、实例代码 #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。int main() {// 超女分4个小组,每个小组有3名超女,在控制台显示每个超女的小组编号和组内编号。// 用一个循环…...

FastDFS与Nginx结合搭建文件服务器,并内网穿透实现公网访问
文章目录前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.1 …...

密集场景下的行人跟踪替代算法,头部跟踪算法 | CVPR 2021
一个不知名大学生,江湖人称菜狗 original author: Jacky LiEmail : 3435673055qq.com Time of completion:2023.4.8 Last edited: 2023.4.8 目录 摘要 主要内容 结果 这篇文章是CVPR 2021 的最新论文,文章的标题: 文章的主要内…...

Matlab与ROS(1/2)---服务端和客户端数据通信(五)
0. 简介 在前几讲我们讲了Matlab中的Message以及Topic的相关知识。而ROS主要支持的通信机制还有服务这一类。服务通过允许请求以及响应的通信方式,来给整个系统完成更紧密的耦合。服务客户端向服务服务器发送请求消息并等待响应。服务器将使用请求中的数据构造响应…...
数字化转型的避坑指南:细说数字化转型十二大坑
随着信息技术的快速发展,数字化转型已经成为许多企业发展的必经之路。然而,数字化转型过程中也存在许多坑,如果不谨慎处理,就可能导致企业陷入困境。本文将细说数字化转型的十二大坑,并提供相应的避坑指南。 1、不了解…...
pt05Encapsulationinherit
Encapsulation &inherit 封装继承 封装 向类外提供必要的功能,隐藏实现的细节, 代码可读性更高优势:简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。私有成员:作用:无需向类外提供…...

面向对象编程(基础)9:封装性(encapsulation)
目录 9.1 为什么需要封装? 而“高内聚,低耦合”的体现之一: 9.2 何为封装性? 9.3 Java如何实现数据封装 9.4 封装性的体现 9.4.1 成员变量/属性私有化 实现步骤: 成员变量封装的好处: 9.4.2 私有化…...
fate-serving-server增加取数逻辑并源码编译
1.什么是fate-serving-server? FATE-Serving 是一个高性能、工业化的联邦学习模型服务系统,专为生产环境而设计,主要用于在线推理。 2.fate-serving-server源码编译 下载fate-serving-serving项目(GitHub - FederatedAI/FATE-Serving: A scalable, h…...

循环队列、双端队列 C和C++
队列 目录 概念 实现方式 顺序队列 循环队列 队列的数组实现 用循环链表实现队列 STL 之 queue 实现队列 STL 之 dequeue 实现双端队列 概念 队列是一种特殊的线性表,它只允许在表的前端(称为队头,front)进行删除操作…...
正则表达式(语法+例子)
文章目录一、介绍二、语法1、匹配字符2、表示数量的字符3、边界字符4、其他字符5、转义字符三、例子1、邮箱2、用逗号分隔的数字集合1,23、允许一位小数4、20yy-mm-dd日期格式5、手机号6、匹配html、xml标签一、介绍 正则表达式(Regular Expression)&am…...

Properties和IO流集合的方法
方法名说明void load(InputStream inStream)从输入字节流读取属性列表(键和元素)void load(Reader reader)从输入字符流读取属性列表(键和元素对)void store(OutputStream out,String comments)将此属性列表(键和元素对…...

python 生成器、迭代器、动态新增属性及方法
目录 一、生成器 1、生成器定义 2、生成器存在的意义 3、创建生成器方式一(生成器表达式) 4. 创建生成器方式二(生成器函数) 1. 生成器函数 2. 生成器函数的工作原理 5. 总结 1. 什么是生成器 2. 生成器特点 二、迭代器…...
Java处理JSON
Java处理json有很多种方法,在这里总结一下。 1 Jackson Spring MVC 默认采用Jackson解析Json,出于最小依赖的考虑,也许Json解析第一选择就应该是Jackson。 1.1 引入的包 Jackson核心模块由三部分组成:jackson-core、jackson-a…...
58-Map和Set练习-LeetCode692前k个高频单词
题目 给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。 示例 1: 输入: words ["i", "love", …...

线程生命周期及五种状态
文章目录一、线程生命周期及五种状态1、New(初始化状态)2、Runnable(就绪状态)3、Running(运行状态)4、Blocked(阻塞状态)5、Terminated(终止状态)二、线程基本方法1、线程等待(wait)2、线程睡眠(sleep)3、…...
OBCP第八章 OB运维、监控与异常处理-灾难恢复
灾难恢复是指当数据库中的数据在被有意或无意破坏后复原数据库所需要执行的活动 回收站:回收站在原理上说就是一个数据字典表,放置用户删除的数据库对象信息。用户删除的东西被放入回收站后,其实仍然占据着物理空间,除非您手动进…...

亚马逊云科技Serverless Data:数字经济下的创新动能
Serverless时代已经到来!企业的技术架构,总是伴随着不断增长的数据与日趋复杂的业务持续演进。如何通过构建更易用的技术架构来聚焦在业务本身,而不必在底层基础设施的管理上投入过多的精力,是数据驱动型企业需要思考的重要议题。…...
【Ruby学习笔记】15.Ruby 异常
Ruby 异常 异常和执行总是被联系在一起。如果您打开一个不存在的文件,且没有恰当地处理这种情况,那么您的程序则被认为是低质量的。 如果异常发生,则程序停止。异常用于处理各种类型的错误,这些错误可能在程序执行期间发生&…...

聊聊MySQL主从延迟
文章目录 MySQL 的高可用是如何实现的呢?二、什么是主备延迟?三、主备延迟常见原因1、备库机器配置差2、备库干私活3、大事务四、主库不可用,主备切换有哪些策略?1、可靠优先2、可用优先实验一实验二3、结论MySQL 的高可用是如何实现的呢? 高可用性(high availability,缩…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...