快速傅里叶变换知识整理
前置知识
点值表示法
对于一个
可以由
由此我们衍生出的新的表示方法 点值表示法,使用
卷积
这里我们需要了解卷积函数的一个性质:
多项式相乘
对于两个多项式
如果使用 系数表示法 :我们需要枚举
如果使用 点值表示法 :
观察可知,如果两个多项式去相同的
复数
定义:
运算规律 | |
---|---|
加法 | |
减法 | |
乘法 | |
除法 |
幅角(相位角):从原点出发、指向
在极坐标下,复数可以使用模长
单位圆
定义:复平面上圆心在原点且半径为
圆上点的坐标:
当将圆等分成
单位根
复数相乘的重要法则:模长相乘,幅角相加(可以利用合角公式来证明):
对于
来说要求 ,即 为 的倍数,这样子可以进行二分递归。
对于
- 周期性:
- 对称性:
- 折半性:
对于上述的有关规律我们都可以使用欧拉定理将三角函数转换成指数形式进行计算。
离散傅里叶变化(DFT)
对于任意的多项式转点值表示,我们可以将任意
代入之后我们就可以将多项式之间的乘法变为
为了优化算法,伟大的傅里叶取了一些特殊的点代入进行优化。
代入
如果使用矩阵封装后的表示形式即为:
快速傅里叶变化(Fast Fourier Transform)
对于一个系数型多项式
对于 DFT 进行点值转换的过程来说,我们希望将其加速优化。
设:
则:
将
将
结合上述的分析:
的系数为 的系数为 的系数为
当
当
接下来我们进行二分递归进行计算。
一共
示例:计算
。 初始化:
, ,
,
,
,
, 同理递归计算
和 对应表达式的值。
快速傅里叶逆变换(IFFT)
既然我们已经将两个多项式从系数表示法转换成了点值表示法进行相乘得到了最终要求的多项式的点值表示法,现在还有最后一步,就是将点值表示法转换成系数表示法,也就是进行快速傅里叶逆变换(IFFT)。
前置数学推导:
即
为 的共轭复数
启发点
完全
我们可以发现后一项就是
则对于
只有当
否则当
快速傅里叶逆变换
设多项式
变为点值表示式:
其中
构造多项式
代入
得到点值表达式
可得:
当
当
所以
然后再这个过程中和快速傅里叶变化(FFT)唯一不同的一点就是我们代入的是 单位根的倒数 ,也就是 单位根的共轭复数 。
所以在
递归版本 FFT
void FFT (vector<complex<double>> &A, int n, int inv) {
if (n == 1) return; // 递归终点
int mid = n >> 1;
vector<complex<double>> A1(mid+1), A2(mid+1);
// 奇偶分离
for (int i = 0; i < mid; ++i) {
A1[i] = A[i*2];
A2[i] = A[i*2+1];
}
// 递归计算
FFT(A1, mid, inv);
FFT(A2, mid, inv);
// 初始化
complex<double> wk(1, 0);
complex<double> w1(cos(2*PI/n), inv * sin(2*PI/n));
// 计算对应多项式乘法
for (int i = 0; i < mid; ++i) {
A[i] = A1[i] + A2[i]*wk;
A[i+mid] = A1[i] - A2[i]*wk;
wk *= w1;
}
}
FFT 的优化
三次 变成 两次
我们可以观察这个式子:
对于多项式
证明:
我们所需要求的为:
我们将
对于
可以看出来其 虚部除以
蝴蝶变换
观察上述递归 起始态 和 最终态,我们可以发现他们进行了位逆序变换,我们可以利用这个特性跳过向下递归的这个过程同时节约空间利用效率。
原理简单说明:
观察我们向下二分的这个过程,每个数的二进制位上第一个位如果是
对于蝴蝶变换的过程有种有意思的优化方式使用
首先我们递推到第
利用这个递推我们就可以在
void change (vector<complex<double>> &A, int n) {
vector<int> rev(n+1);
for (int i = 0; i < n; ++i) {
rev[i] = rev[i/2]/2 + ((i&1) ? n/2 : 0);
}
for (int i = 0; i < n; ++i) {
if (i < rev[i]) swap(A[i], A[rev[i]]);
}
}
void FFT (vector<complex<double>> &A, int n, int inv) {
change(A, n); // 蝴蝶变换
for (int k = 2; k <= n; k <<= 1) { // 层枚举
complex<double> w1(cos(2*PI/k), inv * sin(2*PI/k));
for (int i = 0; i < n; i += k) { // 块枚举
complex<double> wk(1, 0);
int mid = k >> 1;
for (int j = 0; j < mid; ++j) { // 块内枚举
complex<double> x = A[i+j];
complex<double> y = A[i+j+mid]*wk;
A[i+j] = x+y;
A[i+j+mid] = x-y;
wk *= w1;
}
}
}
}