什么是FFT

FFT是用来快速计算两个多项式相乘的一种算法。

如果我们暴力计算两个多项式相乘,复杂度必然是\(O(n^2)\)的,而FFT可以将复杂度降至\(O(nlogn)\)


如何FFT

要学习FFT,我们得先了解它的思想。

首先,我们得先了解如何表示一个多项式。显然,我们最传统的方法表示多项式就是表示它的系数就好。但是,如果我们用系数来计算两个多项式相乘,复杂度无论如何都是\(O(n^2)\)的。因此,我们引入点值表示法。

补充资料:什么是点值表示

设A(x)是一个n−1次多项式,那么把n个不同的x代入,会得到n个y。这n对(x,y)唯一确定了该多项式,即只有一个多项式能同时满足“代入这些x,得到的分别是这些y”。

由多项式可以求出其点值表示,而由点值表示也可以求出多项式。

——胡小兔dalao的博客

所以说,我们要表示一个n-1次多项式,可以用n个点值来表示。如果用点值来计算两个多项式相乘,那就很简单了,我们只需要两个多项式的点值两两对应相乘即可(如果两个多项式次数不同,我们也必须让次数较小的那个多项式强行算够一样多的点值(即多取几个\(x\)来计算即可)),这样做的复杂度是\(O(n)的\)。

因此,如果我们能快速地把一个多项式从系数表示变为点值表示,我们就能快速计算两个多项式相乘啦。

这个快速计算的过程。

1.如何取点

我们要把一个多项式从系数形式变为点值形式,肯定躲不开取\(x\)的过程。先辈傅里叶已经为我们解决了这个问题。他取的\(x\)为虚数。

如果您没有学习过复数,请移步胡小兔dalao的博客,他有详细的讲解。



所以说,我们是假设把一个单位圆分成n份(纵坐标为虚部,横坐标为实部),单位圆上我们每取的一个点所代表的虚数(实部与虚部相加)即对应一个\(x\)

根据我们的数学知识,圆上的任意一个我们取出来的点的坐标都可以表示为\((cos((k*2*pi)/n),sin((k*2*pi)/n))\)的形式,逆时针将这\(n\)个点从\(0\)开始编号,第\(k\)个点对应的虚数记作\(ω_n^k\)

补充资料:单位根的性质

性质一:\(ω^{2k}_{2n}=ω^k_n\)

证明:它们对应的点/向量是相同的。

性质二:\(ω^{k+n/2}_n=−ω^k_n\)

证明:它们对应的点是关于原点对称的(对应的向量是等大反向的)。

——胡小兔dalao的博客

这样子,我们就取出了\(n\)个\(x\)

补充资料:为什么要取这些点

如果我们取这些点,我们最后可以快速地把点值式转换为系数式,具体方法及证明见下文

2.如何快速算出每个\(x\)对应的多项式的值

这就涉及到FFT的核心算法了。如果我们暴力去算,复杂度依旧是\(O(n^2)\),并没有什么用。因此,我们FFT的核心思想是分治

我们先把原多项式拉出来:

\(A(x)=a_0*x^0+a_1*x^1+a_2*x^2+a_3*x^3+a_4*x^4+...+a_{n-1}*x^{n-1}\)

设两个新的多项式:

\(A_1(x)=a_0*x^0+a_2*x^1+a_4*x^2+a_6*x^3+...a_{n-2}*x^{n/2-1}\)

\(A_2(x)=a_1*x^0+a_3*x^1+a_5*x^2+a_7*x^3+...a_{n-1}*x^{n/2-1}\)

显然我们有:

\(A(x)=A_1(x^2)+x*A_2(x^2)\)

所以说,我们可以把原来得式子分成两个长度只有一半的式子,每次都能减少一半的计算量,这样子,我们复杂度就变成了\(O(n*logn)\)

假设我们已经递归下去算出了\(A_1\)与\(A_2\)在\((\omega_{\frac{n}{2}}^{0}, \omega_{\frac{n}{2}}^{1}, \omega_{\frac{n}{2}}^{2}, ... , \omega_{\frac{n}{2}}^{\frac{n}{2} - 1})\)的值,怎么合并回\(A\)在\((\omega_n^{0}, \omega_n^{1}, \omega_n^{2}, ... , \omega_n^{n-1})\)的值呢?

我们把\(\omega_n^x\)带回我们刚刚的这个式子:\(A(x)=A_1(x^2)+x*A_2(x^2)\)有:

\(A(\omega_n^x)=A_1(\omega_n^{x^2})+\omega_n^x*A_2(\omega_n^{x^2})\)

\(A(\omega_n^x)=A_1(\omega_{n/2}^{x})+\omega_n^x*A_2(\omega_{n/2}^{x})\)

那另外那一半怎么算呢?

同样把\(\omega_n^{x+n/2}\)带入\(A(x)=A_1(x^2)+x*A_2(x^2)\)有:

\(A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega_n^{2k + n}) + \omega_n^{k + \frac{n}{2}}A_2(\omega_n^{2k + n})\)

$A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) + \omega_n^{k + \frac{n}{2}} $ $A_2(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) \(
\)A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega_{\frac{n}{2}}^{k}) - \omega_n^kA_2(\omega_{\frac{n}{2}}^{k})$ [1]

实现上,差不多长这样:

const double PI=acos(-1);
typedef complex <double> cp;
inline cp omega (int K,int n)
{
return cp(cos(2*PI*K/n),sin(2*PI*K/n));
}
void FFT(cp a[],int n,bool type)
{
if(n==1) return;
static cp buf[M];
int m=n/2;
for(int i=0;i<m;i++)
buf[i]=a[i*2],buf[i+m]=a[i*2+1];
for(int i=0;i<n;i++)
a[i]=buf[i];
FFT(a,m,type);
FFT(a+m,m,type);
for(int i=0;i<m;i++)
{
cp x=omega(i,n);
if(type==true) x=conj(x);//conj在这里做取倒的作用,具体作用请看下文第四点
buf[i]=a[i]+x*a[i+m];
buf[i+m]=a[i]-x*a[i+m];
}
for(int i=0;i<n;i++)
a[i]=buf[i];
}

3.后续优化

理论上来说,我们已经可以实现FFT了,很不幸的是,递归版本的常数巨大(递归消耗以及大量的三角函数计算),我们可以通过一些玄学方法来优化这份FFT代码:

在进行fft时,我们要把各个系数不断分组并放到两侧,那么一个系数原来的位置和最终的位置有什么规律呢?

初始位置:0 1 2 3 4 5 6 7

第一轮后:0 2 4 6|1 3 5 7

第二轮后:0 4|2 6|1 5|3 7

第三轮后:0|4|2|6|1|5|3|7

“|”代表分组界限。

可以发现(这你都能发现?),一个位置a上的数,最后所在的位置是“a二进制翻转得到的数”,例如6(011)最后到了3(110),1(001)最后到了4(100)。

那么我们可以据此写出非递归版本fft:先把每个数放到最后的位置上,然后不断向上还原,同时求出点值表示。 [1:1]

代码大概长这样:

void FFT(cp a[],int n,bool type)
{
static int len=0,t_num=n-1,t[N];
while(t_num!=0) t_num/=2,len++;
for(int i=0,j;i<=n;i++)
{
for(t_num=i,j=0;j<len;j++)
t[j]=t_num%2,t_num/=2;
reverse(t,t+len);
for(t_num=0,j=0;j<len;j++)
t_num+=t[j]*(1<<j);
if(i<t_num) swap(a[i],a[t_num]);
}
for(int l=2;l<=n;l*=2)
{
int m=l/2;
cp x0=omega(1,l);
if(type==true) x0=conj(x0);
for(int i=0;i<n;i+=l)
{
cp x=cp(1,0);
for(int j=0;j<m;j++,x*=x0)
{
cp temp=x*a[i+j+m];
a[i+j+m]=a[i+j]-temp;
a[i+j]=a[i+j]+temp;
}
}
}
}

4.怎么把点值式换回系数

FFT有一个性质:把多项式\(A(x)\)的离散傅里叶变换结果作为另一个多项式\(B(x)\)的系数,取单位根的倒数即\(ω^0_n,ω_n^{-1},ω_n^{-2},...,ω^{-n+1}_n\)作为\(x\)代入\(B(x)\),得到的每个数再除以\(n\),得到的\(是A(x)\)的各项系数啦。

补充资料:如何证明这个性质

我们设带入后\(B\)的某个点值为\(z_k\),多项式\(B\)算出来的某个点值为\(j_i\),我们有:

$z_k = \sum_{i = 0}^{n - 1} y_i(\omega_n^{-k})^i \(
\)z_k= \sum_{i = 0}^{n - 1}(\sum_{j = 0}^{n - 1} a_j(\omega_n^i)^j)(\omega_n^{-k})^i \(
\)z_k= \sum_{j = 0}^{n - 1}a_j(\sum_{i = 0}^{n - 1}(\omega_n^{j - k})^i)\(
[^1]
这里的\)\sum_{i = 0}^{n - 1}(\omega_n^{j - k})^i\(是可以求出来得,当\)j=k\(的时候,这个式子等于n,其他时候均为0(使用等比数列求和即可证明)
因此我们有:\)z_k=n*a_k$。

证毕


最后的最后......

恭喜你,到此为止,你已经学会了FFT

撒花✿✿ヽ(°▽°)ノ✿


  1. 这一段抄自胡小兔dalao的博客

快速傅里叶变换学习笔记(FFT)的更多相关文章

  1. FFT 快速傅里叶变换 学习笔记

    FFT 快速傅里叶变换 前言 lmc,ikka,attack等众多大佬都没教会的我终于要自己填坑了. 又是机房里最后一个学fft的人 早背过圆周率50位填坑了 用处 多项式乘法 卷积 \(g(x)=a ...

  2. FWT快速沃尔什变换学习笔记

    FWT快速沃尔什变换学习笔记 1.FWT用来干啥啊 回忆一下多项式的卷积\(C_k=\sum_{i+j=k}A_i*B_j\) 我们可以用\(FFT\)来做. 甚至在一些特殊情况下,我们\(C_k=\ ...

  3. 【原创】SpringBoot & SpringCloud 快速入门学习笔记(完整示例)

    [原创]SpringBoot & SpringCloud 快速入门学习笔记(完整示例) 1月前在系统的学习SpringBoot和SpringCloud,同时整理了快速入门示例,方便能针对每个知 ...

  4. Sass简单、快速上手_Sass快速入门学习笔记总结

    Sass是世界上最成熟.稳定和强大的专业级css扩展语言 ,除了Sass是css的一种预处理器语言,类似的语言还有Less,Stylus等. 这篇文章关于Sass快速入门学习笔记. 资源网站大全 ht ...

  5. [学习笔记]FFT——快速傅里叶变换

    大力推荐博客: 傅里叶变换(FFT)学习笔记 一.多项式乘法: 我们要明白的是: FFT利用分治,处理多项式乘法,达到O(nlogn)的复杂度.(虽然常数大) FFT=DFT+IDFT DFT: 本质 ...

  6. numpy教程:快速傅里叶变换模块numpy.fft

    http://blog.csdn.net/pipisorry/article/details/51050297 快速傅里叶变换 NumPy中,fft模块提供了快速傅里叶变换的功能.在这个模块中,许多函 ...

  7. ASP.NET Core快速入门--学习笔记系列文章索引目录

    课程链接:http://video.jessetalk.cn/course/explore 良心课程,大家一起来学习哈! 抓住国庆假期的尾巴完成了此系列课程的学习笔记输出! ASP.NET Core快 ...

  8. Python快速入门学习笔记(二)

    注:本学习笔记参考了廖雪峰老师的Python学习教程,教程地址为:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb49318210 ...

  9. [Docker]Docker快速上手学习笔记

    0. 学习的一些疑问 如何热更新镜像(images)?(你可以快速启动或者销毁容器.这种时间几乎是实时的) 如何热更新游戏服? 好处在于各个应用之间环境相互独立,即使某一个容器崩溃也不会影响到其它容器 ...

随机推荐

  1. Win10系统下的MySQL5.7.24版本(解压版)详细安装教程

    进入MySQL官网下载压缩包 MySQL官网:https://www.mysql.com/ 将页面拉到最底,点击MySQL Community Server 跳转到下载页面,默认选择是最新版MySQL ...

  2. 高效学习必备软件:OneNote+ Mindmaster

    做笔记有两个关键点: 一是笔记内容详略得当.二是知识的框架清晰完整. 为什么这样说? 举个例子,如图是我的笔记界面,用的是免费的OneNote, OneNote是微软出的笔记软件, 非常好用,有着书写 ...

  3. ASP。netcore,Angular2 CRUD动画使用模板包,WEB API和EF 1.0.1

    下载Angular2ASPCORE.zip - 1 MB 介绍 在本文中,让我们看看如何创建一个ASP.NET Core CRUD web应用程序与Angular2动画使用模板包,web API和EF ...

  4. 零基础小白Python入门必看:面向对象之典型魔术方法

  5. Java防止文件被篡改之文件校验和

    Java防止文件被篡改之文件校验和转载:请注明出处,谢谢! 1.为什么要防止文件被篡改?  答案是显然的,为了保证版权,系统安全性等.之前公司开发一个系统,技术核心是一个科学院院士的研究成果,作为一款 ...

  6. 闭嘴,给你一个数!1分钟,学完C语言指针,不扎手只扎心的针!

    序言 指针是C语言学习者绕不过的一道坎,也是C语言学习者不得绕过的一道坎.辨别一个人C语言学的好赖就看他对指针的理解怎么样.指针内容也是工作面试经常问到的问题.本文将带你重新认识那个绊倒你的指针,以解 ...

  7. 经验分享:Windows10值得推荐的软件,总有一款是你的菜

    今天在知乎上看到有人分享wids10推荐好用的软件:今天小编做了一点点的修改和根据自己的使用情况总结出来转发分享给大家:   1.安全放病毒--火绒[推荐] 2.办公软件--office2019[推荐 ...

  8. python保存图片

    #coding=utf-8 import requests url ="https://images.pexels.com/photos/1181767/pexels-photo-11817 ...

  9. 【事件中心 Azure Event Hub】关于EventHub中出现Error时候的一些问题(偶发错误,EventHub后台升级,用户端错误,Retry机制的重要性)

    请问对偶发的定义是多少频率? 针对偶发的定义,主要是看发生的时间非常短,次数极少(如 10次以内),并且发生的时候EventHub其他分区或其他连接都是正常接收和发送数据.所以对于频率是没有明确的定义 ...

  10. python中实现格式化输入(史上最简单、最高效的实现方法,不借助任何模块)

    今天我在写python作业时突然想到格式化输入一段文字,譬如只需读取输入的前几个字符就行,而不幸的是,python中的输入并没有c中的read().getchar()函数,于是我网上搜了一下,网上的解 ...