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 年就发明了这个算法,但一直没有发表。
多项式表示
系数表示法
系数表示法就是用一个多项式的各个项系数来表达这个多项式,即使用一个系数序列来表示多项式:
f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n ⟺ f ( x ) = { a 0 , a 1 , ⋯ , a n } f(x) = a_0 + a_1 x + a_2 x ^ 2 + \cdots +a_n x ^ n \iff f(x) = \{a_0, a_1, \cdots, a_n\}
f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n ⟺ f ( x ) = { a 0 , a 1 , ⋯ , a n }
点值表示法
点值表示法是把这个多项式看成一个函数,从上面选取 n + 1 n + 1 n + 1 个点,从而利用这 n + 1 n + 1 n + 1 个点来唯一地表示这个函数。
设:
f ( x 0 ) = y 0 = a 0 + a 1 x 0 + a 2 x 0 2 + a 3 x 0 3 + ⋯ + a n x 0 n f ( x 0 ) = y 0 = a 0 + a 1 x 0 + a 2 x 0 2 + a 3 x 0 3 + ⋯ + a n x 0 n f ( x 1 ) = y 1 = a 1 + a 1 x 1 + a 2 x 1 2 + a 3 x 1 3 + ⋯ + a n x 1 n f ( x 2 ) = y 2 = a 2 + a 1 x 2 + a 2 x 2 2 + a 3 x 2 3 + ⋯ + a n x 2 n ⋮ f ( x n ) = y n = a n + a 1 x n + a 2 x n 2 + a 3 x n 3 + ⋯ + a n x n n \begin{array}{c}f(x_0) = y_0 = a_0 + a_1 x_0 + a_2 x_0 ^ 2 + a_3 x_0 ^ 3 + \cdots + a_n x_0 ^ n\\
f(x_0) = y_0 = a_0 + a_1 x_0 + a_2 x_0 ^ 2 + a_3 x_0 ^ 3 + \cdots + a_n x_0 ^ n\\
f(x_1) = y_1 = a_1 + a_1 x_1 + a_2 x_1 ^ 2 + a_3 x_1 ^ 3 + \cdots + a_n x_1 ^ n\\
f(x_2) = y_2 = a_2 + a_1 x_2 + a_2 x_2 ^ 2 + a_3 x_2 ^ 3 + \cdots + a_n x_2 ^ n\\
\vdots \\
f(x_n) = y_n = a_n + a_1 x_n + a_2 x_n ^ 2 + a_3 x_n ^ 3 + \cdots + a_n x_n ^ n\\
\end{array}
f ( x 0 ) = y 0 = a 0 + a 1 x 0 + a 2 x 0 2 + a 3 x 0 3 + ⋯ + a n x 0 n f ( x 0 ) = y 0 = a 0 + a 1 x 0 + a 2 x 0 2 + a 3 x 0 3 + ⋯ + a n x 0 n f ( x 1 ) = y 1 = a 1 + a 1 x 1 + a 2 x 1 2 + a 3 x 1 3 + ⋯ + a n x 1 n f ( x 2 ) = y 2 = a 2 + a 1 x 2 + a 2 x 2 2 + a 3 x 2 3 + ⋯ + a n x 2 n ⋮ f ( x n ) = y n = a n + a 1 x n + a 2 x n 2 + a 3 x n 3 + ⋯ + a n x n n
那么用点值表示法表示 f ( x ) f(x) f ( x ) 如下
f ( x ) = y n = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n ⟺ f ( x ) = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯ , ( x n , y n ) } f(x) = y_n = a_0 + a_1x + a_2 x^2 + \cdots + a_n x ^ n \iff f(x) = \{(x_0, y_0), (x_1, y_1), \cdots , (x_n, y_n)\}
f ( x ) = y n = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n ⟺ f ( x ) = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯ , ( x n , y n ) }
通俗地说,多项式由系数表示法转为点值表示法的过程,就是 DFT 的过程。相对地,把一个多项式的点值表示法转化为系数表示法的过程,就是 IDFT。而 FFT 就是通过取某些特殊的 x x x 的点值来加速 DFT 和 IDFT 的过程。
单位复根
考虑这样一个问题:
DFT 是把多项式从系数表示转到了点值表示,那么我们把点值相乘之后,再还原成系数表示,就解决了我们的问题。上述过程如下:
假设我们 DFT 过程对于两个多项式选取的 x x x 序列相同,那么可以得到
f ( x ) = ( x 0 , f ( x 0 ) ) , ( x 1 , f ( x 1 ) ) , ( x 2 , f ( x 2 ) ) , ⋯ , ( x n , f ( x n ) ) g ( x ) = ( x 0 , g ( x 0 ) ) , ( x 1 , g ( x 1 ) ) , ( x 2 , g ( x 2 ) ) , ⋯ , ( x n , g ( x n ) ) \begin{array}{c}f(x) = (x_0, f(x_0)), (x_1, f(x_1)), (x_2, f(x_2)), \cdots , (x_n, f(x_n))\\
g(x) = (x_0, g(x_0)), (x_1, g(x_1)), (x_2, g(x_2)), \cdots , (x_n, g(x_n))\\
\end{array}
f ( x ) = ( x 0 , f ( x 0 ) ) , ( x 1 , f ( x 1 ) ) , ( x 2 , f ( x 2 ) ) , ⋯ , ( x n , f ( x n ) ) g ( x ) = ( x 0 , g ( x 0 ) ) , ( x 1 , g ( x 1 ) ) , ( x 2 , g ( x 2 ) ) , ⋯ , ( x n , g ( x n ) )
如果我们设 F ( x ) = f ( x ) ⋅ g ( x ) F(x) = f(x) \cdot g(x) F ( x ) = f ( x ) ⋅ g ( x ) ,那么容易得到 F ( x ) F(x) F ( x ) 的点值表达式:
F ( x ) = { ( x 0 , f ( x 0 ) g ( x 0 ) ) , ( x 1 , f ( x 1 ) g ( x 1 ) ) , ( x 2 , f ( x 2 ) g ( x 2 ) ) , ⋯ , ( x n , f ( x n ) g ( x n ) ) } F(x) = \{(x_0, f(x_0)g(x_0)), (x_1, f(x_1)g(x_1)), (x_2, f(x_2)g(x_2)), \cdots, (x_n, f(x_n)g(x_n))\}
F ( x ) = { ( x 0 , f ( x 0 ) g ( x 0 ) ) , ( x 1 , f ( x 1 ) g ( x 1 ) ) , ( x 2 , f ( x 2 ) g ( x 2 ) ) , ⋯ , ( x n , f ( x n ) g ( x n ) ) }
但是我们要的是系数表达式,接下来问题变成了从点值回到系数。如果我们带入到高斯消元法的方程组中去,会把复杂度变得非常高。光是计算 x i ( 0 ≤ i ≤ n ) x^i(0 \le i \le n) x i ( 0 ≤ i ≤ n ) 就是 n n n 项, 这就已经 O ( n 2 ) O(n^2) O ( n 2 ) 了, 跟别说还要把 n + 1 n + 1 n + 1 个方程进行消元……
因此我们不去计算 x i x^i x i .1 1 1 和 − 1 -1 − 1 的幂都很好算,但是仅仅有两个也不够,我们至少需要 n + 1 n + 1 n + 1 个。利用我们刚学的长度为 1 1 1 的虚数,这些数不管怎么乘长度都是 1 1 1 。我们需要的是 ω k = 1 \omega^k = 1 ω k = 1 中的 ω \omega ω ,容易想到 − i -i − i 和 i i i 是符合的。除此以外:
观察上图,容易发现这是一个单位圆(圆心为原点,半径为 1 1 1 ),单位圆上的向量模长均为 1 1 1 ,根据复数的运算法则,两个复数相乘,在复平面上表示为两个向量模长相乘,辐角相加。因此两个模长为 1 1 1 的向量相乘,得到的仍是模长为 1 1 1 的向量,辐角为两个向量辐角的和。因此我们可以将 ω k = 1 \omega ^ k = 1 ω k = 1 中的 ω \omega ω 理解为复平面上的一个单位向量,满足它的辐角的 k k k 倍恰好是 36 0 ∘ 360^ \circ 3 6 0 ∘ ——即把圆周 k k k 等分的角。我们把符合以上条件的复数(复平面上的向量)称为复根,用 ω \omega ω 表示。
定义
严谨地,我们称 x n = 1 x^n = 1 x n = 1 在复数意义下的解是 n n n 次复根。显然,这样的解有 n n n 个,设 ω n = e 2 π i n \omega_n = e^\frac{2\pi i}{n} ω n = e n 2 π i ,则 x n = 1 x^ n = 1 x n = 1 的解集表示为 { ω n k ∣ k = 0 , 0 , 1 , ⋯ , n − 1 } \{\omega_n ^ k \mid k = 0, 0, 1, \cdots, n - 1\} { ω n k ∣ k = 0 , 0 , 1 , ⋯ , n − 1 } 。我们称 ω \omega ω 是 n n n 次单位复根(the n n n -th root of unity)。根据复平面的知识,n n n 次单位复根是复平面把单位圆 n n n 等分的第一个角所对应的向量。其它复根均可以用单位复根的幂表示。
另一方面,根据欧拉公式,还可以得到 ω n = e 2 π i n = cos ( 2 π n ) + i ⋅ sin ( 2 π n ) \omega_n = e^\frac{2 \pi i}{n} = \cos(\dfrac{2\pi}{n}) +i \cdot \sin (\dfrac{2\pi}{n}) ω n = e n 2 π i = cos ( n 2 π ) + i ⋅ sin ( n 2 π ) 。
举个例子,当 n = 4 n = 4 n = 4 时, ω n = i \omega_n = i ω n = i ,即 i i i 就是 4 4 4 次单位复根:
当 n = 4 n = 4 n = 4 的时候,相当于把单位圆等分 n = 4 n = 4 n = 4 份。将每一份按照极角编号,那么我们只要知道 ω 4 1 \omega_4^1 ω 4 1 因为它的角度是相当于单位角度),就能知道 ω 4 0 , ω 4 1 , ω 4 2 , ω 4 3 \omega_4^0, \omega_4^1, \omega_4^2, \omega_4^3 ω 4 0 , ω 4 1 , ω 4 2 , ω 4 3 。
ω 4 0 \omega _4^0 ω 4 0 恒等于 1 1 1 , ω 4 2 \omega_4^2 ω 4 2 的角度是 ω 4 1 \omega_4^1 ω 4 1 的两倍,所以 ω 4 2 = ( ω 4 1 ) 2 = i 2 = − 1 \omega_4^2 = (\omega_4^1)^2 = i^2=-1 ω 4 2 = ( ω 4 1 ) 2 = i 2 = − 1 ,依次以此类推。
性质
单位复根有三个重要的性质。对于任意正整数 n n n 和整数 k k k :
ω n n = 1 ω n k = ω 2 n 2 k ω 2 n k + n = − ω 2 n k \begin{array}{c} \omega_n^n = 1\\
\omega_n^k =\omega_{2n}^{2k}\\
\omega_{2n}^{k + n} = -\omega_{2n}^k\\
\end{array}
ω n n = 1 ω n k = ω 2 n 2 k ω 2 n k + n = − ω 2 n k
快速傅里叶变换
FFT 算法的基本思想是分治。就 DFT 来说,它分治地来求当 x = ω n k x = \omega_n^k x = ω n k 的时候 f ( x ) f(x) f ( x ) 的值。它的分治思想体现在将多项式分为奇次项和偶次项处理。
举个例子,对于一共 8 8 8 项的多项式
f ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 + a 5 x 5 + a 6 x 6 a 7 x 7 f(x) = a_0 + a _ 1 x + a _ 2 x ^ 2 + a _ 3 x ^ 3 + a _ 4 x ^ 4 + a _ 5 x ^ 5 + a _ 6 x ^ 6 a _ 7 x ^ 7
f ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 + a 5 x 5 + a 6 x 6 a 7 x 7
按照次数的奇偶来分成两组,然后右边提出来一个 x x x
c f ( x ) = ( a 0 + a 2 x 2 + a 4 x 4 + a 6 x 6 ) + ( a 1 x + a 3 x 3 + a 5 x 5 + a 7 x 7 ) = ( a 0 + a 2 x 2 + a 4 x 4 + a 6 x 6 ) + x ( a 1 + a 3 x 2 + a 5 x 4 + a 7 x 6 ) \begin{aligned}{c}f(x) &= (a_0 + a_2 x ^ 2 + a_4 x ^ 4 + a_6 x ^ 6) + (a_1x + a_3 x ^ 3 + a_5 x ^ 5 + a_7 x ^ 7) \\
&= (a_0 + a_2 x ^ 2 + a_4 x ^ 4 + a_6 x ^ 6) + x(a_1 + a_3 x ^ 2 + a_5 x ^ 4 + a_7 x ^ 6)\\
\end{aligned}
c f ( x ) = ( a 0 + a 2 x 2 + a 4 x 4 + a 6 x 6 ) + ( a 1 x + a 3 x 3 + a 5 x 5 + a 7 x 7 ) = ( a 0 + a 2 x 2 + a 4 x 4 + a 6 x 6 ) + x ( a 1 + a 3 x 2 + a 5 x 4 + a 7 x 6 )
分别用奇偶次次项数建立新的函数
G ( x ) = a 0 + a 2 x + a 4 x 2 + a 6 x 3 H ( x ) = a 1 + a 3 x + a 5 x 2 + a 7 x 3 \begin{array}{c}G(x) = a_0 + a_2x + a_4x^2 + a_6x^3\\
H(x) = a_1 + a_3x + a_5x ^ 2 + a_7x_3\\
\end{array}
G ( x ) = a 0 + a 2 x + a 4 x 2 + a 6 x 3 H ( x ) = a 1 + a 3 x + a 5 x 2 + a 7 x 3
那么原来的 f ( x ) f(x) f ( x ) 用新函数表示为
DET ( f ( ω n k ) ) = DET ( G ( ( ω n k ) 2 ) ) + ω n k × DET ( H ( ( ω n k ) 2 ) ) = DET ( G ( ω n 2 k ) ) + ω n k × DET ( H ( ω n 2 k ) ) = DET ( G ( ω n / 2 k ) ) + ω n k × DET ( H ( ω n / 2 k ) ) \begin{aligned}\operatorname{DET}(f(\omega_n^k)) &=\operatorname{DET}(G((\omega_{n}^{k})^2)) + \omega_{n}^{k} \times \operatorname{DET}(H((\omega_n^k)^2))\\
&= \operatorname{DET}(G(\omega_{n}^{2k})) + \omega_{n}^{k} \times \operatorname{DET}(H(\omega_n^{2k}))\\
&=\operatorname{DET}(G(\omega_{n / 2}^{k})) + \omega_{n}^{k} \times \operatorname{DET}(H(\omega_{n / 2}^{k}))\\
\end{aligned}
D E T ( f ( ω n k ) ) = D E T ( G ( ( ω n k ) 2 ) ) + ω n k × D E T ( H ( ( ω n k ) 2 ) ) = D E T ( G ( ω n 2 k ) ) + ω n k × D E T ( H ( ω n 2 k ) ) = D E T ( G ( ω n / 2 k ) ) + ω n k × D E T ( H ( ω n / 2 k ) )
同理可得
DET ( f ( ω n k + n / 2 ) ) = DET ( G ( ω n 2 k + n ) ) + ω n k + n / 2 × DET ( H ( ω n 2 k + n ) ) = DET ( G ( ω n 2 k ) ) − ω n k × DET ( H ( ω n 2 k ) ) = DET ( G ( ω n / 2 k ) ) − ω n k × DET ( H ( ω n / 2 k ) ) \begin{aligned}\operatorname{DET}(f(\omega_n^{k + n / 2})) &=\operatorname{DET}(G(\omega_{n}^{2k + n})) + \omega_{n}^{k + n / 2} \times \operatorname{DET}(H(\omega_n^{2k + n}))\\
&= \operatorname{DET}(G(\omega_{n}^{2k})) - \omega_{n}^{k} \times \operatorname{DET}(H(\omega_n^{2k}))\\
&=\operatorname{DET}(G(\omega_{n / 2}^{k})) - \omega_{n}^{k} \times \operatorname{DET}(H(\omega_{n / 2}^{k}))\\
\end{aligned}
D E T ( f ( ω n k + n / 2 ) ) = D E T ( G ( ω n 2 k + n ) ) + ω n k + n / 2 × D E T ( H ( ω n 2 k + n ) ) = D E T ( G ( ω n 2 k ) ) − ω n k × D E T ( H ( ω n 2 k ) ) = D E T ( G ( ω n / 2 k ) ) − ω n k × D E T ( H ( ω n / 2 k ) )
因此我们求出了 DFT ( G ( ω n / 2 k ) ) \operatorname{DFT}(G(\omega_{n / 2}^{k})) D F T ( G ( ω n / 2 k ) ) 和 DFT ( H ( ω n / 2 k ) ) \operatorname{DFT}(H(\omega_{n / 2}^{k})) D F T ( H ( ω n / 2 k ) ) 后,就可以同时求出 DFT ( f ( ω n k ) ) \operatorname{DFT}(f(\omega_{n}^{k})) D F T ( f ( ω n k ) ) 和 DFT ( H ( ω n k + n / 2 ) ) \operatorname{DFT}(H(\omega_{n}^{k + n / 2})) D F T ( H ( ω n k + n / 2 ) ) 。于是对 G G G 和 H H H 分别递归 DFT即可。
考虑到分治 DFT 能处理的多项式长度只能是 2 m ( m ∈ N ∗ ) 2^m(m \in N ^ \ast) 2 m ( m ∈ N ∗ ) ,否则在分治的时候左右不一样长,右边就取不到系数了。所以要在第一次 DFT 之前就把序列向上补成长度为 2 m ( m ∈ N ∗ ) 2^m(m \in N ^ \ast) 2 m ( m ∈ N ∗ ) (高次系数补 0 0 0 )、最高项次数为 2 m − 1 2^m - 1 2 m − 1 的多项式。
在代入值的时候,因为要代入 n n n 个不同值,所以我们代入 ω n 0 , ω n 1 , ω n 2 , ⋯ , ω n n − 1 ( n = 2 m ( m ∈ N ∗ ) ) \omega_n^0, \omega_n^1, \omega_n^2, \cdots, \omega_n^{n - 1}(n = 2^m(m \in N^\ast)) ω n 0 , ω n 1 , ω n 2 , ⋯ , ω n n − 1 ( n = 2 m ( m ∈ N ∗ ) ) 一共 2 m 2^m 2 m 个不同值。
代码实现方面,STL 提供了复数的模板,当然也可以手动实现。两者区别在于,使用 STL 的 complex
可以调用 exp
函数求出 ω n \omega_n ω n 。但事实上使用欧拉公式得到的虚数来求 ω n \omega_n ω n 也是等价的。
#include <cmath>
#include <complex>
typedef std::complex<double> Comp; // STL complex
const Comp I(0, 1); // i
const int MAX_N = 1 << 20;
Comp tmp[MAX_N];
void DFT(Comp *f, int n, int rev) { // rev=1,DFT; rev=-1,IDFT
if (n == 1) return;
for (int i = 0; i < n; ++i) tmp[i] = f[i];
for (int i = 0; i < n; ++i) { // 偶数放左边,奇数放右边
if (i & 1)
f[n / 2 + i / 2] = tmp[i];
else
f[i / 2] = tmp[i];
}
Comp *g = f, *h = f + n / 2;
DFT(g, n / 2, rev), DFT(h, n / 2, rev); // 递归 DFT
Comp cur(1, 0), step(cos(2 * M_PI / n), sin(2 * M_PI * rev / n));
// Comp step=exp(I*(2*M_PI/n*rev)); // 两个 step 定义是等价的
for (int k = 0; k < n / 2; ++k) {
tmp[k] = g[k] + cur * h[k];
tmp[k + n / 2] = g[k] - cur * h[k];
cur *= step;
}
for (int i = 0; i < n; ++i) f[i] = tmp[i];
}
时间复杂度 O ( n log n ) O(n \log n) O ( n log n ) 。
位逆序置换
这个算法还可以从“分治”的角度继续优化。我们每一次都会把整个多项式的奇数次项和偶数次项系数分开,一直分到只剩下一个系数。但是,这个递归的过程需要更多的内存。因此,我们可以先“模仿递归”把这些系数在原数组中“拆分”,然后再“倍增”地去合并这些算出来的值。
以 8 8 8 项多项式为例,模拟拆分的过程:
初始序列为 { x 0 , x 1 , x 2 , x 3 , x 4 , x 5 , x 6 , x 7 } \{x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7\} { x 0 , x 1 , x 2 , x 3 , x 4 , x 5 , x 6 , x 7 }
一次二分之后 { x 0 , x 2 , x 4 , x 6 } , { x 1 , x 3 , x 5 , x 7 } \{x_0, x_2, x_4, x_6\}, \{x_1, x_3, x_5, x_7\} { x 0 , x 2 , x 4 , x 6 } , { x 1 , x 3 , x 5 , x 7 }
两次二分之后 { x 0 , x 4 } , { x 2 , x 6 } , { x 1 , x 5 } , { x 3 , x 7 } \{x_0, x_4\}, \{x_2, x_6\}, \{x_1, x_5\}, \{x_3, x_7\} { x 0 , x 4 } , { x 2 , x 6 } , { x 1 , x 5 } , { x 3 , x 7 }
三次二分之后 { x 0 } { x 4 } { x 2 } { x 6 } { x 1 } { x 5 } { x 3 } { x 7 } \{x_0\}\{x_4\}\{x_2\}\{x_6\}\{x_1\}\{x_5\}\{x_3\}\{x_7\} { x 0 } { x 4 } { x 2 } { x 6 } { x 1 } { x 5 } { x 3 } { x 7 }
规律:其实就是原来的那个序列,每个数用二进制表示,然后把二进制翻转对称一下,就是最终那个位置的下标。比如 x 1 x_1 x 1 是 001,翻转是 100,也就是 4,而且最后那个位置确实是 4。我们称这个变换为位逆序置换(bit-reversal permutation,国内也称蝴蝶变换)。
根据它的定义,我们可以在 O ( m log n ) O(m \log n) O ( m log n ) 的时间内求出每个数变换后的结果:
/*
* 进行 FFT 和 IFFT 前的反置变换
* 位置 i 和 i 的二进制反转后的位置互换
*len 必须为 2 的幂
*/
void change(Complex y[], int len) {
int i, j, k;
for (int i = 1, j = len / 2; i < len - 1; i++) {
if (i < j) swap(y[i], y[j]);
// 交换互为小标反转的元素,i<j 保证交换一次
// i 做正常的 + 1,j 做反转类型的 + 1,始终保持 i 和 j 是反转的
k = len / 2;
while (j >= k) {
j = j - k;
k = k / 2;
}
if (j < k) j += k;
}
}
快速傅里叶逆变换
傅里叶逆变换可以用傅里叶变换表示。对此我们有两种理解方式。
线性代数角度
IDFT(傅里叶反变换)的作用,是把目标多项式的点值形式转换成系数形式。而 DFT 本身是个线性变换,可以理解为将目标多项式当作向量,左乘一个矩阵得到变换后的向量,以模拟把单位复根代入多项式的过程:
[ y 0 y 1 y 2 y 3 ⋮ y n − 1 ] = [ 1 1 1 1 ⋯ 1 1 ω n 1 ω n 2 ω n 3 ⋯ ω n n − 1 1 ω n 2 ω n 4 ω n 6 ⋯ ω n 2 ( n − 1 ) 1 ω n 3 ω n 6 ω n 9 ⋯ ω n 3 ( n − 1 ) ⋮ ⋮ ⋮ ⋮ ⋱ ⋮ 1 ω n n − 1 ω n 2 ( n − 1 ) ω n 3 ( n − 1 ) ⋯ ω n ( n − 1 ) 2 ] [ a 0 a 1 a 2 a 3 ⋮ a n − 1 ] \begin{bmatrix}y_0 \\ y_1 \\ y_2 \\ y_3 \\ \vdots \\ y_{n-1} \end{bmatrix} = \begin{bmatrix}1 & 1 & 1 & 1 & \cdots & 1 \\ 1 & \omega_n^1 & \omega_n^2 & \omega_n^3 & \cdots & \omega_n^{n-1} \\ 1 & \omega_n^2 & \omega_n^4 & \omega_n^6 & \cdots & \omega_n^{2(n-1)} \\ 1 & \omega_n^3 & \omega_n^6 & \omega_n^9 & \cdots & \omega_n^{3(n-1)} \\ \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \omega_n^{3(n-1)} & \cdots & \omega_n^{(n-1)^2} \end{bmatrix} \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \\ \vdots \\ a_{n-1} \end{bmatrix}
⎣ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎡ y 0 y 1 y 2 y 3 ⋮ y n − 1 ⎦ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎤ = ⎣ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎡ 1 1 1 1 ⋮ 1 1 ω n 1 ω n 2 ω n 3 ⋮ ω n n − 1 1 ω n 2 ω n 4 ω n 6 ⋮ ω n 2 ( n − 1 ) 1 ω n 3 ω n 6 ω n 9 ⋮ ω n 3 ( n − 1 ) ⋯ ⋯ ⋯ ⋯ ⋱ ⋯ 1 ω n n − 1 ω n 2 ( n − 1 ) ω n 3 ( n − 1 ) ⋮ ω n ( n − 1 ) 2 ⎦ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎤ ⎣ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎡ a 0 a 1 a 2 a 3 ⋮ a n − 1 ⎦ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎤
现在我们已经得到最左边的结果了,中间的 x x x 值在目标多项式的点值表示中也是一一对应的,所以,根据矩阵的基础知识,我们只要在式子两边左乘中间那个大矩阵的逆矩阵就行了。由于这个矩阵的元素非常特殊,它的逆矩阵也有特殊的性质,就是每一项取倒数,再除以 n n n ,就能得到它的逆矩阵。
为了使计算的结果为原来的倒数,根据单位复根的性质并结合欧拉公式,可以得到
1 ω k = ω k − 1 = e − 2 π i k = cos ( 2 π k ) + i ⋅ sin ( − 2 π k ) \frac{1}{\omega_k}=\omega_k^{-1}=e^{-\frac{2\pi i}{k}}=\cos\left(\frac{2\pi}{k}\right)+i\cdot \sin\left(-\frac{2\pi}{k}\right)
ω k 1 = ω k − 1 = e − k 2 π i = cos ( k 2 π ) + i ⋅ sin ( − k 2 π )
因此我们可以尝试着把单位根 ω k \omega_k ω k 取成 e − 2 π i k e^{-\frac{2 \pi i}{k}} e − k 2 π i 这样我们的计算结果就会变成原来的倒数,而其它的操作过程与 DFT 是完全相同的。我们可以定义一个函数,在里面加一个参数 1 1 1 或者是 − 1 -1 − 1 ,然后把它乘到 π \pi π 上。传入 1 1 1 就是 DFT,传入 − 1 -1 − 1 就是 IDFT。
单位复根周期性
利用单位复根的周期性同样可以理解 IDFT 与 DFT 之间的关系。
考虑原本的多项式是 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 = ∑ i = 0 n − 1 a i x i f(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1}=\sum_{i=0}^{n-1}a_ix^i f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 = ∑ i = 0 n − 1 a i x i 。而 IDFT 就是把你的点值表示还原为系数表示。
考虑 构造法 。我们已知 y i = f ( ω n i ) , i ∈ { 0 , 1 , ⋯ , n − 1 } y_i=f\left( \omega_n^i \right),i\in\{0,1,\cdots,n-1\} y i = f ( ω n i ) , i ∈ { 0 , 1 , ⋯ , n − 1 } ,求 { a 0 , a 1 , ⋯ , a n − 1 } \{a_0,a_1,\cdots,a_{n-1}\} { a 0 , a 1 , ⋯ , a n − 1 } 。构造多项式如下\
A ( x ) = ∑ i = 0 n − 1 y i x i A(x)=\sum_{i=0}^{n-1}y_ix^i
A ( x ) = i = 0 ∑ n − 1 y i x i
相当于把 { y 0 , y 1 , y 2 , ⋯ , y n − 1 } \{y_0,y_1,y_2,\cdots,y_{n-1}\} { y 0 , y 1 , y 2 , ⋯ , y n − 1 } 当做多项式 A A A 的系数表示法。
这时我们有两种推导方式,这对应了两种实现方法。
方法一
设 b i = ω n − 1 b_i = \omega_n^{-1} b i = ω n − 1 ,则多项式 A A A 在 x = b 0 , b 1 , ⋯ , b n − 1 x=b_0,b_1,\cdots,b_{n-1} x = b 0 , b 1 , ⋯ , b n − 1 处的点值表示法为 { A ( b 0 ) , A ( b 1 ) , ⋯ , A ( b n − 1 ) } \left\{ A(b_0),A(b_1),\cdots,A(b_{n-1}) \right\} { A ( b 0 ) , A ( b 1 ) , ⋯ , A ( b n − 1 ) } 对 A ( x ) A(x) A ( x ) 的定义式做一下变换,可以将 A ( b k ) A(b_k) A ( b k ) 表示为
A ( b k ) = ∑ i = 0 n − 1 f ( ω n i ) ω n − i k = ∑ i = 0 n − 1 ω n − i k ∑ j = 0 n − 1 a j ( ω n i ) j = ∑ i = 0 n − 1 ∑ j = 0 n − 1 a j ω n i ( j − k ) = ∑ j = 0 n − 1 a j ∑ i = 0 n − 1 ( ω n j − k ) i \begin{aligned} A(b_k)&=\sum_{i=0}^{n-1}f(\omega_n^i)\omega_n^{-ik}=\sum_{i=0}^{n-1}\omega_n^{-ik}\sum_{j=0}^{n-1}a_j(\omega_n^i)^{j}\\ &=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j\omega_n^{i(j-k)}=\sum_{j=0}^{n-1}a_j\sum_{i=0}^{n-1}\left(\omega_n^{j-k}\right)^i\\ \end{aligned}
A ( b k ) = i = 0 ∑ n − 1 f ( ω n i ) ω n − i k = i = 0 ∑ n − 1 ω n − i k j = 0 ∑ n − 1 a j ( ω n i ) j = i = 0 ∑ n − 1 j = 0 ∑ n − 1 a j ω n i ( j − k ) = j = 0 ∑ n − 1 a j i = 0 ∑ n − 1 ( ω n j − k ) i
记 S ( ω n a ) = ∑ i = 0 n − 1 ( ω n a ) i S\left(\omega_n^a\right)=\sum_{i=0}^{n-1}\left(\omega_n^a\right)^i S ( ω n a ) = ∑ i = 0 n − 1 ( ω n a ) i
当 a = 0 ( m o d n ) a = 0 \left(\mod n\right) a = 0 ( m o d n ) 时, S ( ω n a ) = n S(\omega_n^a) = n S ( ω n a ) = n 。
当 a ≠ 0 ( m o d n ) a \neq 0 (\mod n) a = 0 ( m o d n ) 时, 我们错位相减
S ( ω n a ) = ∑ i = 0 n − 1 ( ω n a ) i ω n a S ( ω n a ) = ∑ i = 1 n ( ω n a ) i S ( ω n a ) = ( ω n a ) n − ( ω n a ) 0 ω n a − 1 = 0 \begin{aligned} S\left(\omega_n^a\right)&=\sum_{i=0}^{n-1}\left(\omega_n^a\right)^i\\ \omega_n^a S\left(\omega_n^a\right)&=\sum_{i=1}^{n}\left(\omega_n^a\right)^i\\ S\left(\omega_n^a\right)&=\frac{\left(\omega_n^a\right)^n-\left(\omega_n^a\right)^0}{\omega_n^a-1}=0\\ \end{aligned}
S ( ω n a ) ω n a S ( ω n a ) S ( ω n a ) = i = 0 ∑ n − 1 ( ω n a ) i = i = 1 ∑ n ( ω n a ) i = ω n a − 1 ( ω n a ) n − ( ω n a ) 0 = 0
也就是说
S ( ω n a ) = { n , a = 0 0 , a ≠ 0 S\left(\omega_n^a\right)= \left\{\begin{aligned} n,a=0\\ 0,a\neq 0 \end{aligned}\right.
S ( ω n a ) = { n , a = 0 0 , a = 0
也就是说给定点 b i = ω n − 1 b_i = \omega_n^{-1} b i = ω n − 1 , 则 A A A 的点值表示法为
{ ( b 0 , A ( b 0 ) ) , ( b 1 , A ( b 1 ) ) , ⋯ , ( b n − 1 , A ( b n − 1 ) ) } = { ( b 0 , a 0 ⋅ n ) , ( b 1 , a 1 ⋅ n ) , ⋯ , ( b n − 1 , a n − 1 ⋅ n ) } \begin{aligned} &\left\{ (b_0,A(b_0)),(b_1,A(b_1)),\cdots,(b_{n-1},A(b_{n-1})) \right\}\\ =&\left\{ (b_0,a_0\cdot n),(b_1,a_1\cdot n),\cdots,(b_{n-1},a_{n-1}\cdot n) \right\} \end{aligned}
= { ( b 0 , A ( b 0 ) ) , ( b 1 , A ( b 1 ) ) , ⋯ , ( b n − 1 , A ( b n − 1 ) ) } { ( b 0 , a 0 ⋅ n ) , ( b 1 , a 1 ⋅ n ) , ⋯ , ( b n − 1 , a n − 1 ⋅ n ) }
综上所述,我们取单位根为其倒数,对 { y 0 , y 1 , y 2 , ⋯ , y n − 1 } \{y_0,y_1,y_2,\cdots,y_{n-1}\} { y 0 , y 1 , y 2 , ⋯ , y n − 1 } 跑一遍 FFT,然后除以 n n n 即可得到 f ( x ) f(x) f ( x ) 的系数表示。
方法二
我们直接将 ω n i \omega _n^i ω n i 代入 A ( x ) A(x) A ( x ) 。
推导的过程与方法一大同小异,最终我们得到 A ( ω n k ) = ∑ j = 0 n − 1 a j S ( ω n j + k ) A(\omega_n^k) = \sum_{j=0}^{n-1}a_jS\left(\omega_n^{j+k}\right) A ( ω n k ) = ∑ j = 0 n − 1 a j S ( ω n j + k ) 。
当且仅当 j + k = 0 ( m o d n ) j+k=0 \pmod{n} j + k = 0 ( m o d n ) 时有 S ( ω n j + k ) = n S\left(\omega_n^{j+k}\right) = n S ( ω n j + k ) = n ,否则为 0 0 0 。因此 A ( ω n k ) = a n − k ⋅ n A(\omega_n^k) = a_{n-k}\cdot n A ( ω n k ) = a n − k ⋅ n 。
这意味着我们将 { y 0 , y 1 , y 2 , ⋯ , y n − 1 } \{y_0,y_1,y_2,\cdots,y_{n-1}\} { y 0 , y 1 , y 2 , ⋯ , y n − 1 } 做 DFT 变换后,反转再除以 n n n ,同样可以还原 f ( x ) f(x) f ( x ) 的系数表示。
代码实现
所以我们 FFT 函数可以集 DFT 和 IDFT 于一身。代码实现如下:
/*
* 做 FFT
* len 必须是 2^k 形式
* on == 1 时是 DFT,on == -1 时是 IDFT
*/
void fft(Complex y[], int len, int on) {
change(y, len);
for (int h = 2; h <= len; h <<= 1) { // 模拟合并过程
Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h)); // 计算当前单位复根
for (int j = 0; j < len; j += h) {
Complex w(1, 0); // 计算当前单位复根
for (int k = j; k < j + h / 2; k++) {
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t; // 这就是把两部分分治的结果加起来
y[k + h / 2] = u - t;
// 后半个 “step” 中的ω一定和 “前半个” 中的成相反数
// “红圈”上的点转一整圈“转回来”,转半圈正好转成相反数
// 一个数相反数的平方与这个数自身的平方相等
w = w * wn;
}
}
}
if (on == -1) {
for (int i = 0; i < len; i++) {
y[i].x /= len;
}
}
}
/*
* 做 FFT
* len 必须是 2^k 形式
* on == 1 时是 DFT,on == -1 时是 IDFT
*/
void fft(Complex y[], int len, int on) {
change(y, len);
for (int h = 2; h <= len; h <<= 1) { // 模拟合并过程
Complex wn(cos(2 * PI / h), sin(2 * PI / h)); // 计算当前单位复根
for (int j = 0; j < len; j += h) {
Complex w(1, 0); // 计算当前单位复根
for (int k = j; k < j + h / 2; k++) {
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t; // 这就是把两部分分治的结果加起来
y[k + h / 2] = u - t;
// 后半个 “step” 中的ω一定和 “前半个” 中的成相反数
// “红圈”上的点转一整圈“转回来”,转半圈正好转成相反数
// 一个数相反数的平方与这个数自身的平方相等
w = w * wn;
}
}
}
if (on == -1) {
reverse(y, y + len);
for (int i = 0; i < len; i++) {
y[i].x /= len;
}
}
}
快速数论变换
若要计算的多项式系数是别的具有特殊意义的整数,那么 FFT 全部用浮点数运算,从时间上比整数运算慢,且只能用 long double 类型。
要应用数论变化从而避开浮点运算精度问题,参见 快速数论变换 。