FFT倒位序算法的改进

前面的博文中我们给出了一个很简洁的快速傅里叶变换,然而其性能非常不佳运行缓慢,通过改进旋转因子的计算方式,我们将某些三角求解转化为复乘复加,从而减小了相当一部分计算量,性能显著上升。此时我们分析算法发现整个程序性能瓶颈在于前期的倒位序算法,普通算法将会消耗超过鲽形变换的时间,这一篇文章中我们试图在这一方面进行改进

1.对偶性

我们以 N 2 作为分割点,设 a 为小于 N 2 的一个偶数( A a ), A a 的逆序数,可以看到有

A = r e v ( a ) ( 1. a )
其中函数 r e v ( ) 是一个倒位序函数。
一看这个A就是一个偶数(将之展开为加权二进制,小于 N 2 的数字最高为权为零,倒位序以后 2 0 的权为零)
同时
A = r e v ( a ) ( 1. b )
也成立,我们称这个性质为对偶性。可以证明: 对于任意一对原数和逆序数,对偶性始终成立

利用对偶性,我们有可能一定程度上缩小问题的规模

考虑了偶数的情况,我们再来看一下奇数,我们可以证明

r e v ( a + 1 ) = N 2 + A ( 2. a )
成立(同样展开为加权二进制的形式,最低为上的1倒位序后出现在最高位上),同样根据对偶性我们有
r e v ( N 2 + A ) = a + 1 ( 2. b )

我们还需要
r e v ( N 2 + a + 1 ) = N 2 + A + 1 ( 3. a )
r e v ( N 2 + A + 1 ) = N 2 + a + 1 ( 3. b )

罗列了这么多性质我们可以利用其完成需要的工作。如果不理解这四组八个小等式,不妨写一个N=16时的原序序列和逆序序列就懂啦

2.利用对偶性完成优化

这里利用对偶性我们先完成一个小目标:将运算量降低至原来的 1 4
首先利用式子 1. a ,计算小于 N 2 的偶数,这里以N=16为例

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 4 2 6

这时候我们可以看到 1. b 实际上没有起到多大作用(能否发挥作用在最后进行探讨)
然后看到式子 2. a r e v ( a + 1 ) = N 2 + A 我们可以计算小于 N 2 的奇数,这里A我们在上一步中已经完成计算,只需要简单地加上一个 N 2 就可以了

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

现在小于 N 2 的部分已经完成了,根据对偶性,利用式子 2. b r e v ( N 2 + A ) = a + 1

注意在利用这个式子之前我们需要证明:在小于 N 2 的序列中,原序偶数序列的逆序序列均小于 N 2 ,奇数序列的逆序序列均大于 N 2

证明非常容易,只需展开为二进制即可。现在我们可以放心使用 2. b

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

剩下的只有大于 N 2 的奇数部分了,我们有公式 3. a ,利用其我们可以顺利计算出这些剩下的部分

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

我们看到对偶式 3. b 没有起到多大作用
我们得到最后的逆序序列

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[]就是生成的倒位序列。实际上我们完全不需要存储这个倒位序列(既然要存储这个序列查表法岂不更好?),我们注意到上面的每一步仅仅是完成两个数字之间的交换,添加少量代码即可完成,这里不再赘述。


通过改进算法,在我的电脑上进行 N = 2 21 的FFT,结果时间提高到0.19s,距离FFT Benchmark中最快的ooura FFT仅仅0.05s之遥

4.随便的一点探讨

上面我们说对偶式 1. b 3. b 似乎没有起到多大作用。这里以 1. b ,当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 1. b 可以发挥多大作用?由于求解总是由小到大,我们发现只要满足

r e v ( a ) a
对偶就可以发挥作用节省运算次数,这个节省的次数是可以算出来的,我算了一下是
2 l o g N 2 2

这个东西是递增的N越大省去的运算里越多,只不过在N较大的时候它接近线性
同样的式 3. b 依然可以这样改进哦,博主很懒代码还没写过,脑补一下觉得写起来应该不难的

猜你喜欢

转载自blog.csdn.net/little_cats/article/details/81509007