Fast Fourier Transform

Fast Fourier Transform

This is a classic problem in CS – to multiple two degree-n polynomials. The brute force way is obvious, but would cost us O(n2) time. Using the idea of FFT would enable us to do this in O(nlogn) time.
In this blog entry, I will be focusing on the theoretic proof of the FFT algorithm that is so widely used, serving to deepen my own understanding about the topic. Of course, it will be heartening to see if anyone find this article helpful in any way.

Prerequisite knowledge

Polynomials in point value form

For a polynomial of degree-n f(x)= ∑ i = 0 n a i x i \sum_{i=0}^na_ix^i i=0naixi, we choose n arbitrary values of x and use the n pairs of (xi,f(xi)) to represent the polynomial. Recall from the gauss’s system of linear equations, the n pair of values is just sufficient for us to figure out what the original polynomial is exactly.

You may have noticed that using the point value form of polynomial greatly reduced the number of operations required for multiplication. Suppose we need to calculate F(x) which is the product of two polynomials f(x) and g(x),then if we have the n pairs of (xi,f(xi)) and (xi,g(xi)), we can easily derive the corresponding point value form of F(x) which would be (xi,f(xi)g(xi)) in O(n) time. But at what cost? Computing the n values of f(x) alone would require O(n2) time, and we still need F(x) in the coefficient form, and solving the n simultaneous equation would require us O(n3) time by Gauss’s method.

Root of unity

However, notice that we are free to choose any n arbitrary value of x to substitute into the polynomials, and of course we are to choose some special values. In this case, we are to choose n evenly spaced out point on the unit circle of the complex plane, which are the n complex roots of the equation xn=1(recall that in the multiplication of complex numbers can be seen as rotation of the point about the origin on the complex plane, and these n points after rotating n times by angle equal to their argument will end up on the positive side of the real axis with modulus of 1).Let ω n = e 2 π i n \omega_n=e^{\frac{2\pi i}{n}} ωn=en2πi, it is not hard to see this is the first root above the positive real axis, and we can express the whole set of the n roots as
{ ω n k ∣ k = 0 , 1 ⋯   , n − 1 {\omega_n^k\mid k=0,1\cdots,n-1} ωnkk=0,1,n1}, we call them the nth roots of unity, and they satisfy these beautiful symmetric properties:

  1. ω n k = ω n k + n \omega_n^k=\omega_{n}^{k+n} ωnk=ωnk+n(going another full circle will not affect anything)
  2. ω n k = ω 2 n 2 k \omega_n^k=\omega_{2n}^{2k} ωnk=ω2n2k (dividing the unit circle into n equal portions and take the kth crack is equivalent to the 2kth crack if the unit circle is divided into 2n equivalent portions instead)
  3. − ω 2 n k = ω 2 n k + n -\omega_{2n}^k=\omega_{2n}^{k+n} ω2nk=ω2nk+n(taking the negative value is just reflecting the point through the origin, and the two would differ by an angle of π \pi π, thus we need n 2 n \frac{n}{2n} 2nn of the circle to make same equal)

We are to use these n values in our transformation.

Discrete Fourier Transform

The first step is to convert our f(x) and g(x) from coefficient form into point value form. Take f(x) as an example, the idea is to separate the odd and even powers, and use the idea of Divide and Conquer.

For example, for a polynomial with degree n-1(we assume n is a power of 2 here, as for the missing power we can just assume the coefficient is 0)
suppose we have  f ( x ) = ∑ i = 0 n a i x i let  o d d ( x ) = ∑ i = 0 n 2 a 2 ∗ i + 1 x i , e v e n ( x ) = ∑ i = 0 n 2 a 2 ∗ i x i , Then we have  f ( x ) = e v e n ( x 2 ) + x ∗ o d d ( x 2 ) \begin{aligned} \text{suppose we have }f(x)=&\sum_{i=0}^n a_ix^i\\ \text{let }odd(x)=&\sum_{i=0}^{\frac{n}{2}}a_{2*i+1}x^i,\\ even(x)=&\sum_{i=0}^{\frac{n}{2}}a_{2*i}x^i,\\ \text{Then we have }f(x)=&even(x^2)+x*odd(x^2)\\ \end{aligned} suppose we have f(x)=let odd(x)=even(x)=Then we have f(x)=i=0naixii=02na2i+1xi,i=02na2ixi,even(x2)+xodd(x2)
Now we can substitute in our nth roots of unity:
f ( ω n k ) = e v e n ( ( ω n k ) 2 ) + ω n k ∗ o d d ( ( ω n k ) 2 ) = e v e n ( ω n 2 k ) + ω n k ∗ o d d ( ω n 2 k ) = e v e n ( ω n 2 k ) + ω n k ∗ o d d ( ω n 2 k ) ...from(2) f ( ω n k + n 2 ) = e v e n ( ( ω n k + n 2 ) 2 ) + ω n k + n 2 ∗ o d d ( ( ω n k + n 2 ) 2 ) = e v e n ( ω n 2 k + n ) + ω n k + n 2 ∗ o d d ( ω n 2 k + n ) = e v e n ( ω n 2 k ) − ω n k ∗ o d d ( ω n 2 k ) ...from(1),(3) = e v e n ( ω n 2 k ) − ω n k ∗ o d d ( ω n 2 k ) ...from(2) \begin{aligned} f(\omega_n^k)&=even((\omega_n^k)^2)+\omega_n^k*odd((\omega_n^k)^2)\\ &=even(\omega_n^{2k})+\omega_n^k*odd(\omega_n^{2k})\\ &=even(\omega_{\frac{n}{2}}^k)+\omega_n^k*odd(\omega_{\frac{n}{2}}^k)\text{...from(2)}\\ f(\omega_n^{k+\frac{n}{2}})&=even((\omega_n^{k+\frac{n}{2}})^2)+\omega_n^{k+\frac{n}{2}}*odd((\omega_n^{k+\frac{n}{2}})^2)\\ &=even(\omega_n^{2k+n})+\omega_n^{k+\frac{n}{2}}*odd(\omega_n^{2k+n})\\ &=even(\omega_n^{2k})-\omega_n^k*odd(\omega_n^{2k})\text{...from(1),(3)}\\ &=even(\omega_{\frac{n}{2}}^k)-\omega_n^k*odd(\omega_{\frac{n}{2}}^k)\text{...from(2)} \end{aligned} f(ωnk)f(ωnk+2n)=even((ωnk)2)+ωnkodd((ωnk)2)=even(ωn2k)+ωnkodd(ωn2k)=even(ω2nk)+ωnkodd(ω2nk)...from(2)=even((ωnk+2n)2)+ωnk+2nodd((ωnk+2n)2)=even(ωn2k+n)+ωnk+2nodd(ωn2k+n)=even(ωn2k)ωnkodd(ωn2k)...from(1),(3)=even(ω2nk)ωnkodd(ω2nk)...from(2)
Thus, we only need to compute e v e n ( ω n 2 k ) even(\omega^k_\frac{n}{2}) even(ω2nk) and o d d ( ω n 2 k ) odd(\omega^k_\frac{n}{2}) odd(ω2nk) to derive the values of f ( ω n k ) f(\omega_n^k) f(ωnk) and f ( ω n k + n 2 ) f(\omega_n^{k+\frac{n}{2}}) f(ωnk+2n), and we can carry out this process recursively for the odd and even functions as well. For each recursion, we convert the computation of a degree-n polynomial into 2 degree- n 2 \frac{n}{2} 2n polynomials, and we only compute half the number of point values ( ω n k \omega_n^k ωnk) for both the 2 degree- n 2 \frac{n}{2} 2n polynomials since the other half ( ω n k + n 2 \omega_n^{k+\frac{n}{2}} ωnk+2n) just differ by a plus/minus sign. Thus, we need 2 ∗ 1 2 ∗ n = n 2*\frac{1}2*n=n 221n=n operations for each round of recursion, and after l o g 2 n log_2n log2n rounds, we will end up with n degree-1 polynomials and we only need to substitute 1 into each one of them. This would give us a time complexity of O ( n l o g n ) (nlogn) (nlogn).

This process is known as the Discrete Fourier Transform(DFT).

Inverse Discrete Fourier Transform

The second part of the FFT algorithm would be reverting back from point value form to coefficient form. As you might have guessed, the process of Inverse Discrete Fourier Transform(IDFT) is largely similar to that of DFT.

In fact, if we see DFT as a linear transformation from the vector of coefficients { a 0 , a 1 , a 2 , . . . , a n − 1 a_0,a_1,a_2,...,a_{n-1} a0,a1,a2,...,an1} to the vector of the n point values { f ( x 0 ) , f ( x 1 ) , f ( x 2 ) , . . . , f ( x n − 1 ) f(x_0),f(x_1),f(x_2),...,f(x_{n-1}) f(x0),f(x1),f(x2),...,f(xn1)}, we are essentially multiplying the coefficient vector by an n × \times ×n matrix. In fact, the brute force way of substituting each of the n values one by one is essentially matrix multiplication, and our DFT just simplifies the process by using some special properties of root of unity(and by Divide and Conquer).
[ f ( x 0 ) f ( x 1 ) f ( x 2 ) f ( x 3 ) ⋮ f ( x 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}f(x_0 )\\ f(x_1) \\ f(x_2) \\ f(x_3 )\\ \vdots \\ f(x_{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} f(x0)f(x1)f(x2)f(x3)f(xn1)=111111ωn1ωn2ωn3ωnn11ωn2ωn4ωn6ωn2(n1)1ωn3ωn6ωn9ωn3(n1)1ωnn1ωn2(n1)ωn3(n1)ωn(n1)2a0a1a2a3an1
Conversely, if we have the point value vector, and we want to compute the coefficient vector, we just need to multiply the inverse matrix of the bulky n × n n\times n n×n matrix on the left for both sides.
Again, inverse matrices are generally slow to compute, but in this case we can check that the following matrix is the inverse matrix.
1 n [ 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 ] \frac{1}{n} \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} n1111111ωn1ωn2ωn3ωn(n1)1ωn2ωn4ωn6ωn2(n1)1ωn3ωn6ωn9ωn3(n1)1ωn(n1)ωn2(n1)ωn3(n1)ωn(n1)2
We can easily check that the product of the two matrices is the identity matrix. Thus, the IDFT process would be very similar to DFT except for we are using the “negative” roots of unity now. In fact, we can reuse the code of DFT for IDFT.

Sample Code

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define il inline
const int N=3e5+5;
const double pi=acos(-1);
struct comp//complex number
{
    double x,y;
    comp operator + (const comp &c)const{
        return {x+c.x,y+c.y};
        }
    comp operator - (const comp &c)const{
        return {x-c.x,y-c.y};
    }
    comp operator * (const comp &c)const{
        return {x*c.x-y*c.y,x*c.y+y*c.x};
    }
}a[N],b[N];
int n,m,tot=1,bit=0,rev[N];
void fft(comp c[],int inv)//IFFT if inv=1
{
    for(int i=0;i<tot;i++){
        if(i<rev[i])swap(c[i],c[rev[i]]);
    }
    for(int mid=1;mid<tot;mid<<=1){
        comp w1={cos(pi/mid),inv*sin(pi/mid)};
        for(int i=0;i<tot;i+=mid*2){
            comp wk={1,0};
            for(int j=0;j<mid;j++,wk=wk*w1){
                comp x=c[i+j],y=wk*c[i+j+mid];
                c[i+j]=x+y,c[i+j+mid]=x-y;
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++)scanf("%lf",&a[i].x);
    for(int i=0;i<=m;i++)scanf("%lf",&b[i].x);
    while(tot<n+m+1){bit++,tot<<=1;}
    for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    fft(a,1),fft(b,1);
    for(int i=0;i<tot;i++)a[i]=a[i]*b[i];
    fft(a,-1);
    for(int i=0;i<=n+m;i++)printf("%d ",(int)(a[i].x/tot+0.2))//round off;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值