这一篇是第二卷的高潮:快速傅里叶变换(Fast Fourier Transform,FFT)。它把多项式乘法从 O(n2) 降到 O(nlogn),是分治最辉煌的成就之一。FFT 的影响远超算法本身——它是数字信号处理、图像压缩、音频处理、大整数乘法的数学引擎,被《不列颠算法》列为 20 世纪十大算法之一。它的数学比前几篇重,但值得,因为理解了 FFT 你就理解了"换一个表示让计算变快"这个深刻思想。
给两个 n 次多项式 A(x)=∑i=0naixi 和 B(x)=∑i=0nbixi,求乘积 C(x)=A(x)B(x),次数 2n。
朴素做法:Ck=∑i+j=kaibj。每个系数 Ck 是一个卷积,算所有 2n+1 个系数,每个最多 n+1 次乘法,总共 O(n2)。
要加速,得换一个看多项式的视角。
多项式有两种等价的表示:
- 系数表示:(a0,a1,…,an)——这是我们熟悉的。
- 点值表示:取 d+1 个不同的点 x0,…,xd(d 是次数),用这组点上的值 (A(x0),…,A(xd)) 表示。理由:一个 d 次多项式被 d+1 个点唯一确定。
关键洞察:在点值表示下,多项式乘法是 O(n) 的! 因为 C(xi)=A(xi)⋅B(xi)——在每个点上,乘积的值就是两个因子值的乘积,只需 n 次点乘。这比系数表示下的 O(n2) 快得多。
于是策略出来了(这叫"插值乘法"):
- 把 A,B 从系数表示转成点值表示(求值,DFT)。
- 在点值表示下逐点相乘,O(n) 得到 C 的点值表示。
- 把 C 从点值表示转回系数表示(插值,IDFT)。
如果这三步都高效,多项式乘法就快了。问题在于:第 1、3 步怎么做才快? 朴素地在 n 个点上求值,每个点 O(n),总共还是 O(n2)——白搭。FFT 的贡献,就是巧妙地选一组特殊点,让求值和插值都变成 O(nlogn)。
FFT 选的点是单位根(roots of unity)。n 次单位根是满足 ωn=1 的复数 ω,共 n 个:ω0,ω1,…,ωn−1,其中 ω=e2πi/n。
n=8 的单位根,单位圆上均匀分布的 8 个点:
ω⁰
ω⁷ ● ● ω¹
ω⁶ ● | | ● ω²
ω⁵ ● ● ω³
ω⁴
单位根有两个关键性质,是 FFT 加速的基石:
性质一(折半引理): ω2n2k=ωnk。偶数幂的单位根等于规模减半的单位根。这让递归能把问题对半切。
性质二(消去引理): ωnk+n/2=−ωnk。对称性——单位根前后半差一个负号。
现在算 A(ω0),A(ω1),…,A(ωn−1)。把 A(x) 按奇偶拆成两半:
A(x)=Aeven(x2)+x⋅Aodd(x2)
其中 Aeven(y)=a0+a2y+…(偶次项),Aodd(y)=a1+a3y+…(奇次项)。它们都是 2n 次的多项式。
奇迹来了:在单位根上求值时,x2 把 n 个不同的 ωk 映射成只有 2n 个不同的值(因为折半引理 ω2k=ωn/2k)。也就是说,Aeven 和 Aodd 只需在 2n 个点上求值——子问题规模减半!
而且利用消去引理 ωk+n/2=−ωk:
A(ωk+n/2)=Aeven(ω2k)−ωk⋅Aodd(ω2k)
和 A(ωk)=Aeven(ω2k)+ωk⋅Aodd(ω2k) 只差一个符号——算一对 (ωk,ωk+n/2) 的值,复用同一对子结果,只需一次加一次减。
这就是 FFT 的蝶形结构:
FFT(A):
if n == 1: return A // 单点
A_even = 偶次项, A_odd = 奇次项
Y_even = FFT(A_even) // 在 n/2 个点求值
Y_odd = FFT(A_odd)
for k = 0 to n/2 - 1:
Y[k] = Y_even[k] + ωⁿᵏ · Y_odd[k]
Y[k + n/2] = Y_even[k] - ωⁿᵏ · Y_odd[k] // 蝶形
return Y
递归关系 T(n)=2T(n/2)+O(n)=O(nlogn)。又一次和归并排序相同的递归、相同的解——分治的标志性结构。
求值(DFT)把系数转点值,插值(IDFT)反过来。妙处是:IDFT 几乎和 DFT 一样,只是把单位根 ω 换成 ω−1,最后除以 n。所以插值也能用 FFT 同一套分治结构,O(nlogn)。
把三步加起来:
- 求值(DFT)A,B:各 O(nlogn)。
- 点值相乘:O(n)。
- 插值(IDFT)C:O(nlogn)。
总共 O(nlogn)。从 O(n2) 到 O(nlogn)——和排序的 O(nlogn) 同阶!n=106 时,n2=1012 而 nlogn≈2×107,快了五万倍。这就是 FFT 的威力。
FFT 的应用铺天盖地:
- 信号处理:时域信号 ↔ 频域频谱的转换(这就是"傅里叶变换"的本质——在频率空间看信号),音频/图像压缩(JPEG 用 DCT,MP3 用 MDCT,都是 FFT 的变种)。
- 大整数乘法:大数相乘本质是多项式乘法 + 进位,FFT 让它 O(nlogn),Python 大整数乘法就这么实现。
- 卷积:任何能表达成卷积的运算(滤波、相关、多项式)都受益。
- 卷积神经网络:深度学习里某些高效卷积实现也用 FFT(深度学习课 3.3 节的卷积)。
它的核心思想——换一个表示(系数→点值/时域→频域),让计算从难变易——是一个普适的范式。第十一卷的"分解法"会把它提炼成通用设计工具。
Q1. 为什么多项式乘法在"点值表示"下是 O(n),而在"系数表示"下是 O(n2)?
点值表示下,乘积 C(xi)=A(xi)B(xi) 在每个点是一次乘法,n 个点共 O(n)。系数表示下,Ck=∑aibj 是卷积,每个系数要扫一遍 i+j=k,n 个系数共 O(n2)。表示方式不同,乘法的代价天差地别——这正是"换表示让计算变快"的体现。
Q2. FFT 选单位根做求值点,是为了利用哪两个性质?它们怎么让递归规模减半?
折半引理 ω2n2k=ωnk:在单位根上求值时 x2 把 n 个点压成 n/2 个不同值,子问题(Aeven,Aodd)规模减半。消去引理 ωk+n/2=−ωk:算一对 (ωk,ωk+n/2) 的值复用同一子结果,只差正负号。两者合起来让递归 T(n)=2T(n/2)+O(n)=O(nlogn)。
Q3.(思考题) FFT 的思想除了加速多项式乘法,还在哪些地方用上了"换一个表示让计算变易"?
信号处理:时域 → 频域(傅里叶变换),频域里滤波/压缩/特征提取更容易。大整数乘法:把数字当多项式系数,用 FFT 卷积。深度学习卷积:把空间域卷积转到频域(FFT)做逐点相乘。本质上都是"原表示下 O(n2) 的运算,换个表示(点值/频域)后变 O(n),加上两次转换 O(nlogn) 仍净赚"。这种"换表示"是普适设计范式,第十一卷会系统提炼。
FFT 用单位根 + 分治,把多项式求值/插值压到 O(nlogn),从而让多项式乘法从 O(n2) 降到 O(nlogn)。它的核心是"换表示(系数↔点值/时域↔频域)让计算变易",这个思想远远超越多项式乘法本身,撑起了信号处理、压缩、大数乘法等一整片领域。下一篇是第二卷的收官,我们把第二卷所有分治算法提炼成一套设计法则。