前言
本文主要是谈一谈自己对蝴蝶变换以及非递归FFT的理解,如果想全面学习FFT算法,请移步其他博客。
蝴蝶变换
为什么要引进蝴蝶变换?
FFT的递归过程实际上是拆分奇数次和偶数次项的过程,每次将偶数次项移到左边,奇数次项移到右边,拆分成两个子多项式,以便递归计算。
为了消除递归中,奇偶次项移动交换产生的冗余时间消耗,引入蝴蝶变换,在FFT之前将每一项排好顺序,递归(迭代)的时候就直接二分变换后的数组就行了。
具体操作?
蝴蝶变换,即将一个数在二进制表示下翻转整个二进制串形成的数。
考虑从小到大计算
i
i
i翻转后的数
R
i
R_i
Ri
对于
i
i
i,我们先让
i
i
i右移一位,然后翻转(即
R
i
/
2
R_{i/2}
Ri/2),然后再右移一位,可以写几个数模拟一下,发现此时只有最低位还未翻转,判断一下即可。
简化版公式:
R
i
=
R
i
/
2
/
2
+
(
i
m
o
d
2
)
∗
n
/
2
R_i=R_{i/2}/2+(i mod 2)*n/2
Ri=Ri/2/2+(imod2)∗n/2其中
n
n
n为数组长度(必为2的幂次)。
非递归FFT
递归FFT中需要用到三个数组,分别表示原多项式的
n
n
n个取值,子多项式的
n
/
2
n/2
n/2个取值。
但是非递归FFT中只需用到一个数组即可。
在蝴蝶变换的帮助下,非递归FFT从最底层开始依次向上迭代,即先计算长度为1的子多项式的1个点的取值(也就是多项书的系数),再计算长度为2的子多项式的2个点的取值,依次类推,直至长度为
n
n
n的原多项式的
n
n
n个点的取值。
譬如:
假设FFT中用到的数组为
y
y
y,第一层(最底层)时,
y
i
y_i
yi代表子多项式第
i
i
i项取
w
1
1
w_1^1
w11的情况,第二层时,
y
i
y_i
yi,
y
i
+
1
y_{i+1}
yi+1代表子多项式第
i
,
i
+
1
i,i+1
i,i+1项取
w
2
1
w_2^1
w21或
w
2
2
w_2^2
w22的情况,第三层时,
y
i
y_{i}
yi,
y
i
+
1
y_{i+1}
yi+1,
y
i
+
2
y_{i+2}
yi+2,
y
i
+
3
y_{i+3}
yi+3代表子多项式第
i
,
i
+
1
,
i
+
2
,
i
+
3
i,i+1,i+2,i+3
i,i+1,i+2,i+3项取
w
4
1
w_4^1
w41,
w
4
2
w_4^2
w42,
w
4
3
w_4^3
w43,
w
4
4
w_4^4
w44的情况,以此类推。
每一层都会从左到右更新一遍y数组的值
合并时:(以第三层为例),子多项式第
i
,
i
+
1
,
i
+
2
,
i
+
3
i,i+1,i+2,i+3
i,i+1,i+2,i+3项取
w
4
1
w_4^1
w41,
w
4
2
w_4^2
w42的值
f
(
w
4
1
)
f(w_4^1)
f(w41),
f
(
w
4
2
)
f(w_4^2)
f(w42)
由底下的子多项式第
i
,
i
+
1
i,i+1
i,i+1项的
f
(
w
2
1
)
f(w_2^1)
f(w21)或
f
(
w
2
2
)
f(w_2^2)
f(w22)的值
加上
w
4
1
w_4^1
w41乘上子多项式第
i
+
2
,
i
+
3
i+2,i+3
i+2,i+3项的
f
(
w
2
1
)
f(w_2^1)
f(w21)或
f
(
w
2
2
)
f(w_2^2)
f(w22)的值计算到;
子多项式第
i
,
i
+
1
,
i
+
2
,
i
+
3
i,i+1,i+2,i+3
i,i+1,i+2,i+3项取
w
4
3
w_4^3
w43,
w
4
4
w_4^4
w44的值
f
(
w
4
3
)
f(w_4^3)
f(w43),
f
(
w
4
4
)
f(w_4^4)
f(w44)
由底下的子多项式第
i
,
i
+
1
i,i+1
i,i+1项的
f
(
w
2
1
)
f(w_2^1)
f(w21)或
f
(
w
2
2
)
f(w_2^2)
f(w22)的值
减去
w
4
1
w_4^1
w41乘上子多项式第
i
+
2
,
i
+
3
i+2,i+3
i+2,i+3项的
f
(
w
2
1
)
f(w_2^1)
f(w21)或
f
(
w
2
2
)
f(w_2^2)
f(w22)的值计算到.
代码
#define ld double
ld PI=acos(-1.0);
for(int i=0;i<len;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1)); //蝴蝶变换(main中)
void bft(complex<ld> y[]){ // 每次fft之前
for(int i=0;i<len;i++){
if(i<r[i]){
swap(y[i],y[r[i]]);
}
}
return;
}
void fft(complex<ld> y[],int zt){ //zt判断正还是逆变换
bft(y);
for(int i=2;i<=len;i<<=1){
complex<ld> step(cos(2.0*PI/i),sin(2.0*PI/i*zt));
for(int j=0;j<len;j+=i){
complex<ld> w(1,0);
for(int k=j;k<j+i/2;k++){
complex<ld> temp1=y[k];
complex<ld> temp2=w*y[k+i/2];
y[k]=temp1+temp2;
y[k+i/2]=temp1-temp2;
w=w*step;
}
}
}
if(zt==-1){
for(int i=0;i<len;i++){
ld temp=y[i].real();
temp/=len;
y[i].real(temp);
}
}
return;
}
后记
FFT可以对应实数的四则运算中的乘法,后面所有的运算操作(除法,取模,取对数)都是要在FFT的基础上进行,所以就像学小学数学必须学好加减乘除一样,学多项式也就必须学好FFT,必须弄懂所有细节。