q

FFT(Fast Fourier Transform,快速傅里叶变换) 是计算机科学中最伟大的算法之一。简单来说,它的核心作用是:\(O(N \log N)\) 的时间内,完成多项式乘法。

如果没有 FFT,我们做多项式乘积需要 \(O(N^2)\)(就像小学生列竖式乘法一样),当项数达到 10 万级别时,普通计算机就跑不动了。


1. 核心思想:从“系数”到“点值”

理解 FFT 的关键在于多项式的两种表示方法:

  • 系数表示法\(A(x) = a_0 + a_1x + a_2x^2 + \dots + a_nx^n\)。这是我们最熟悉的。
  • 点值表示法:一个 \(n\) 次多项式可以用 \(n+1\) 个不同的点 \((x_i, y_i)\) 唯一确定。

FFT 的奇妙旅程:

  1. DFT (FFT): 把两个多项式的系数转成“点值”。
  2. 点值乘法: 两个多项式在相同 \(x\) 处的 \(y\) 值直接相乘(只需 \(O(N)\))。
  3. IDFT (IFFT): 把乘完后的点值再转回“系数”。

2. 为什么 FFT 这么快?(单位复根的妙用)

如果随机选 \(x\) 点计算点值,复杂度还是 \(O(N^2)\)。FFT 的天才之处在于选了一组特殊的 \(x\)——单位复根 (\(\omega_n^k\))

这些点在复平面上均匀分布在一个单位圆上。它们具有对称性和周期性:

  • 折半定理\(\omega_{2n}^{2k} = \omega_n^k\)
  • 消去定理\(\omega_n^{k + n/2} = -\omega_n^k\)

利用这些性质,我们可以把一个规模为 \(N\) 的问题,拆分成两个规模为 \(N/2\) 的子问题(奇数项一组,偶数项一组)。这就是分治法


3. 递归实现 vs 迭代实现

在你之前的代码里使用的是递归实现。它的逻辑非常清晰:

  1. 分类:把多项式按下标分为偶数项 \(A_0(x)\) 和奇数项 \(A_1(x)\)
  2. 分治:递归计算这两个小多项式在 \(\omega_{n/2}^k\) 处的点值。
  3. 合并(蝴蝶操作):利用公式合并结果:
    • \(A(\omega_n^k) = A_0(\omega_{n/2}^k) + \omega_n^k A_1(\omega_{n/2}^k)\)
    • \(A(\omega_n^{k+n/2}) = A_0(\omega_{n/2}^k) - \omega_n^k A_1(\omega_{n/2}^k)\)

注意:递归版虽然好理解,但在 C 语言中由于频繁开辟内存和函数入栈,效率较低。工业界和竞赛通常使用迭代版(非递归)


4. 形象理解:蝴蝶变换

在迭代版 FFT 中,我们需要先对原始数组进行“位反转置换”(Bit-reversal permutation)。比如下标为 \((1)_{10} = (001)_2\),反转后变成 \((100)_2 = (4)_{10}\)

这样做之后,我们就可以自底向上地通过“蝴蝶操作”直接在原数组上修改,而不需要额外的临时数组。


5. FFT 的应用场景

FFT 不仅仅是为了解数学题,它在现代技术中无处不在:

  • 音频处理:提取音乐的频谱(比如播放器上的频谱跳动)。
  • 图像压缩:JPEG 压缩背后有类似的离散余弦变换(DCT)。
  • 信号通信:4G/5G 信号调制。
  • 大整数乘法:当两个数字有几百万位时,就把它们看作多项式用 FFT 算。

你想深入了解一下如何把你的 C 语言递归代码改写成速度更快的“迭代版(蝴蝶变换)”吗?
既然要“细说”,我们就把 FFT 的外壳剥开。FFT 的本质是利用分治法(Divide and Conquer),而支撑这个分治法能够成立的数学地基,是单位复根的特殊性质。


1. 为什么一定要用复数?

如果我们选普通的实数(如 \(1, 2, 3 \dots\))代入多项式 \(A(x) = \sum a_i x^i\),计算 \(N\) 个点依然需要 \(O(N^2)\)
但如果我们选 单位圆上的 \(N\) 等分点,奇迹就发生了。

单位复根 \(\omega_n^k\)

在复平面上,以原点为圆心,1 为半径作圆。将圆周 \(N\) 等分,从 \((1,0)\) 开始逆时针数,第 \(k\) 个点就是 \(\omega_n^k\)

根据欧拉公式,它的坐标是:

\[\omega_n^k = \cos\left(\frac{2\pi k}{n}\right) + i\sin\left(\frac{2\pi k}{n}\right) \]

这些点有两个救命的性质:

  1. 折半性\(\omega_{2n}^{2k} = \omega_n^k\)(在圆上转 2 圈等分点重合)。
  2. 对称性\(\omega_n^{k + n/2} = -\omega_n^k\)(相差半圈的点,坐标互为相反数)。

2. 核心推导:奇偶分治

假设我们要计算 \(N\) 项多项式 \(A(x)\)\(N\) 为 2 的幂)。我们将系数按下标的奇偶性分成两半:

  • 偶数项\(A_0(x) = a_0 + a_2x + a_4x^2 + \dots\)
  • 奇数项\(A_1(x) = a_1 + a_3x + a_5x^2 + \dots\)

那么原多项式可以写成:

\[A(x) = A_0(x^2) + x A_1(x^2) \]

当我们把 \(\omega_n^k\)(其中 \(k < n/2\))代入时:

  1. 前一半 (\(k\))\(A(\omega_n^k) = A_0(\omega_{n/2}^k) + \omega_n^k A_1(\omega_{n/2}^k)\)
  2. 后一半 (\(k + n/2\))\(A(\omega_n^{k+n/2}) = A_0(\omega_{n/2}^k) - \omega_n^k A_1(\omega_{n/2}^k)\)

注意到了吗? 计算后一半的值时,居然完全用的是前一半算过的数据(只是符号变了)!这就是为什么复杂度能从 \(N^2\) 降到 \(N \log N\)


3. 蝴蝶操作 (Butterfly Operation)

在代码实现中,这种“前一半加、后一半减”的操作被称为蝴蝶操作

  • 左边:是上一层递归传回来的两个点值 \(A_0\)\(A_1\)
  • 右边:是合成出来的当前层更大的多项式点值。
  • 这个形状像蝴蝶的翅膀,因此得名。

4. 迭代优化:位反转置换 (Bit-Reversal)

递归版 FFT 有个致命伤:递归太深、频繁申请内存。为了写出更快的迭代版,我们需要观察递归时系数是怎么拆分的。

\(N=8\) 为例,拆分过程如下:

  • 初始: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

神奇的规律:
把最终底层的下标转为二进制:
0(000), 4(100), 2(010), 6(110), 1(001), 5(101), 3(011), 7(111)
你会发现,它们恰好是原始下标 000, 001, 010, 011...二进制翻转

所以,迭代版 FFT 的步骤是:

  1. 打乱:先把数组按二进制翻转重新排列。
  2. 合并:从底层开始,两两合并,再四四合并……直到合成长度为 \(N\)

总结:FFT 的三步走

  1. 补位:把多项式长度补到 \(2^k\)
  2. 变换 (DFT):把 \(A\)\(B\) 从“系数”变成“点值”。
  3. 乘法:对应点值直接相乘 \(C_i = A_i \times B_i\)
  4. 逆变换 (IDFT):把 \(C\) 变回“系数”(只需在 FFT 算法中把 \(\omega\) 取共轭复数,最后结果除以 \(N\) 即可)。

你想看一眼这个更高效、不带 malloc 的“迭代版蝴蝶变换”代码吗?
gemini生成的

posted @ 2026-03-17 23:32  mhunice  阅读(61)  评论(0)    收藏  举报