先来回忆一下高精度乘法的原理
对于正整数相乘A
视为B
同理为A * B
视作多项式相乘, 得到C
的多项式形式为C
的值。
其中,
这种方法的时间复杂度为
多项式的点值表示
设n
次多项式
由线性代数知识可知, 若知道了n+1组不同的[x, f(x)]
的关系, 即可得到一个n+1 x n+1
的多元线性方程组, 且这个方程组有唯一解。
也就是说, 对于一个n
次多项式
设n
次多项式m
次多项式n + m
次, 所以我们将两个多项式进行零填充到n + m
次, 此时如果
则
可以看到, 在点值形式下, 计算
离散傅里叶变换(DFT)
我们要将n
次多项式
可以任意取n + 1
个x值, 带入进行计算, 但这样时间复杂度是
数学家傅里叶取了一组特殊的点带入, 使得其可以进行优化。
单位根
若复数
将单位圆n
等分, 即可得到圆上的n个坐标点,而这n个坐标点表示的复数都是n
次单位根。其中第k个n次单位根称为
单位根的性质
在本文中需要用到单位根的如下性质(均可通过展开式推导得出):
而离散傅里叶变换就是一组单位根作为x值, 但如果还是直接代入计算的话, 复杂度仍然没有变化。
快速傅里叶变换(FFT)
快速傅里叶变换是一种能在计算机中快速地计算离散傅里叶变换的算法。
FFT的基本思想是分治。
设n-1
阶多项式
将
设
则
代入
且
故求出
假设我们已经求出了g
和h
在f
的点值表示。
g
和h
的问题规模都是f
的一半, 所以时间复杂度为
离散傅里叶逆变换(IDFT)
用矩阵乘法表示DFT的过程为
FFT结束后我们得到了最右边的结果, 所以要得到系数矩阵只需要让结果乘以中间大矩阵的逆矩阵即可。
怎么求这个矩阵的逆矩阵呢?这里的结论是每一项取倒数,再除以多项式的长度n, 至于证明可以参见快速傅里叶变换(FFT)超详解 - 知乎 (zhihu.com)
快速傅里叶逆变换(IFFT)
结果矩阵乘以逆矩阵这个过程显然和FFT的过程很相似,考虑怎么转化。
观察
所以IFFT和FFT可以公用一套代码, 只需要用一个标志位来控制关键位置的取反。
代码实现
//
// Created by trudbot on 2023/7/14.
//
#include "complex"
//sign为1时是FFT, -1是IFFT
//n必须为2的整数次幂
const double pi = acos(-1);
void FFT(std::complex<double> *a, int n, int sign) {
if (n == 1) return;
std::complex<double> g[n / 2], h[n / 2];
for (int i = 0, j = 0; i < n; i += 2, j ++) {
g[j] = a[i], h[j] = a[i + 1];
}
FFT(g, n / 2, sign), FFT(h, n / 2, sign);
std::complex<double> w(1, 0), u(cos(2 * pi) / n, sign * sin(2 * pi / n));
for (int i = 0; i < n / 2; i ++, w *= u) {
a[i] = g[i] + w * h[i];
a[i + n / 2] = g[i] - w * h[i];
}
}
void DFT(std::complex<double> *a, int n) {
FFT(a, n, 1);
}
void IDFT(std::complex<double> *a, int n) {
FFT(a, n, -1);
for (int i = 0; i < n; i ++) {
a[i] /= n;
}
}
参考
超详细易懂FFT(快速傅里叶变换)及代码实现_Trilarflagz的博客-CSDN博客
快速傅里叶变换(FFT)超详解 - 知乎 (zhihu.com)
傅里叶变换与大数乘法 - Free_Open - 博客园 (cnblogs.com)
如何通俗地解释什么是离散傅里叶变换? - 知乎 (zhihu.com)