前面的博文中我们给出了一个很简洁的快速傅里叶变换,然而其性能非常不佳运行缓慢,通过改进旋转因子的计算方式,我们将某些三角求解转化为复乘复加,从而减小了相当一部分计算量,性能显著上升。此时我们分析算法发现整个程序性能瓶颈在于前期的倒位序算法,普通算法将会消耗超过鲽形变换的时间,这一篇文章中我们试图在这一方面进行改进
1.对偶性
我们以 作为分割点,设 为小于 的一个偶数( ), 为 的逆序数,可以看到有
一看这个A就是一个偶数(将之展开为加权二进制,小于 的数字最高为权为零,倒位序以后 的权为零)
同时
利用对偶性,我们有可能一定程度上缩小问题的规模
考虑了偶数的情况,我们再来看一下奇数,我们可以证明
我们还需要
罗列了这么多性质我们可以利用其完成需要的工作。如果不理解这四组八个小等式,不妨写一个N=16时的原序序列和逆序序列就懂啦
2.利用对偶性完成优化
这里利用对偶性我们先完成一个小目标:将运算量降低至原来的
首先利用式子
,计算小于
的偶数,这里以N=16为例
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 4 | 2 | 6 |
这时候我们可以看到
实际上没有起到多大作用(能否发挥作用在最后进行探讨)
然后看到式子
:
我们可以计算小于
的奇数,这里A我们在上一步中已经完成计算,只需要简单地加上一个
就可以了
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0+8=8 | 4 | 4+8=12 | 2 | 2+8=10 | 6 | 6+8=14 |
现在小于 的部分已经完成了,根据对偶性,利用式子 :
注意在利用这个式子之前我们需要证明:在小于 的序列中,原序偶数序列的逆序序列均小于 ,奇数序列的逆序序列均大于
证明非常容易,只需展开为二进制即可。现在我们可以放心使用
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 8 | 4 | 12 | 2 | 10 | 6 | 14 | 1 | 5 | 3 | 7 |
剩下的只有大于 的奇数部分了,我们有公式 ,利用其我们可以顺利计算出这些剩下的部分
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 8 | 4 | 12 | 2 | 10 | 6 | 14 | 1 | 8+0+1=9 | 5 | 8+4+1=13 | 3 | 8+2+1=11 | 7 | 8+6+1=15 |
我们看到对偶式
没有起到多大作用
我们得到最后的逆序序列
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 8 | 4 | 12 | 2 | 10 | 6 | 14 | 1 | 9 | 5 | 13 | 3 | 11 | 7 | 5 |
3.优化后的C语言实现
int BitReverse(int j)
{
int i, p;
p = 0;
for (i = 0; i < LogN; i++)
{
if (j&(1 << i))
{
p += 1 << (LogN - i - 1);
}
}
return p;
}
int bitcopy()
{
int i = 0, j;
for (i = 0; i < N / 2;i += 2)
{
rev[i] = BitReverse(i);
rev[i + 1] = N / 2 + rev[i];
rev[N / 2 + i] = rev[i] + 1;
rev[N / 2 + i + 1] = N / 2 + rev[i] + 1;
}
return 0;
}
这里rev[]就是生成的倒位序列。实际上我们完全不需要存储这个倒位序列(既然要存储这个序列查表法岂不更好?),我们注意到上面的每一步仅仅是完成两个数字之间的交换,添加少量代码即可完成,这里不再赘述。
通过改进算法,在我的电脑上进行
的FFT,结果时间提高到0.19s,距离FFT Benchmark中最快的ooura FFT仅仅0.05s之遥
4.随便的一点探讨
上面我们说对偶式 与 似乎没有起到多大作用。这里以 ,当N=16时为例
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … |
---|---|---|---|---|---|---|---|---|
0 |
运用对偶,0还是0额算了
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … |
---|---|---|---|---|---|---|---|---|
0 | 4 | 2(对偶) |
这里可以发挥作用了,我们可以运用对偶性省去一次计算,棒棒哒~
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … |
---|---|---|---|---|---|---|---|---|
0 | 4 | 2 | 6 |
还是他本身,运算结束。
我们发现这里用了对偶可以省去一次运算。那么对于任意的
式
可以发挥多大作用?由于求解总是由小到大,我们发现只要满足
这个东西是递增的N越大省去的运算里越多,只不过在N较大的时候它接近线性
同样的式 依然可以这样改进哦,博主很懒代码还没写过,脑补一下觉得写起来应该不难的