Fast Fourier transform (FFT)
Evaluate a polynomial at the n complex roots of unity by divide and conquer in O(n log n) (and back, with the inverse FFT); the basis of fast polynomial multiplication.
We want the polynomial A(x) = a_0 + a_1·x + … + a_{n−1}·x^{n−1} at n points. If the points are the n-th roots of
unity ω^0, ω^1, …, ω^{n−1} (with ω = e^{2πi/n}), a trick works:
- Time:
O(n log n) - Space:
O(n log n) (recursive) - CLRS, section 30.2
A(x) = 1 + 2x + 3x² + 4x³ at the four 4th roots of unity (1, i, −1, −i): 10, −2−2i, −2, −2+2i.
import cmathdef transform(coeffs, inverse):a = coeffs[0]n = len(a)if n & (n - 1) != 0:return Noney = fft(a, inverse)if inverse:y = [v / n for v in y]rows = [(k, show(v)) for k, v in enumerate(y)]return [show(v) for v in y]def fft(a, inverse):n = len(a)if n == 1:return [complex(a[0])]sign = -1 if inverse else 1w_n = cmath.exp(sign * 2j * cmath.pi / n) # the principal n-th root of unityeven = fft(a[0::2], inverse)odd = fft(a[1::2], inverse)y = [0] * nw = 1for k in range(n // 2):t = w * odd[k]y[k] = even[k] + ty[k + n // 2] = even[k] - tw = w * w_nreturn ydef show(z):# Rounded for reading, e.g. "2-1.414i".re, im = round(z.real, 3) + 0.0, round(z.imag, 3) + 0.0return f"{re:g}" if im == 0 else f"{re:g}{im:+g}i"
Start
The table is still empty.
Recursion tree
Filled orange = running call · Orange outline = still on the stack · Grey = finished
Local variables
Orange = changed in this step
coeffs list
Call stack
- transform()line 4
Explanation
The idea
We want the polynomial A(x) = a_0 + a_1·x + … + a_{n−1}·x^{n−1} at n points. If the points are the n-th roots of
unity ω^0, ω^1, …, ω^{n−1} (with ω = e^{2πi/n}), a trick works:
A(x) = A_even(x²) + x · A_odd(x²)
and the squares of the n-th roots are the (n/2)-th roots. So we solve two half-size problems and combine them with
butterflies:
y[k] = y⁰[k] + ω^k·y¹[k] y[k + n/2] = y⁰[k] − ω^k·y¹[k]
T(n) = 2T(n/2) + O(n) = O(n log n), against O(n²) to compute the n values one by one.
The inverse FFT
Going back from values to coefficients is the same FFT with ω⁻¹ instead of ω, then dividing by n (the parameter
inverse = 1).
What to watch in the trace
- The call tree is binary (even and odd); the last table holds the rounded values.