FFT倒位序算法的改进

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

1.对偶性

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

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

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

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

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

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

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

2.利用对偶性完成优化

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

0123456789101112131415
0426

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

0123456789101112131415
00+8=844+8=1222+8=1066+8=14

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

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

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

0123456789101112131415
084122106141537

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

0123456789101112131415
0841221061418+0+1=958+4+1=1338+2+1=1178+6+1=15

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

0123456789101112131415
084122106141951331175

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=221 N = 2 21 的FFT,结果时间提高到0.19s,距离FFT Benchmark中最快的ooura FFT仅仅0.05s之遥

4.随便的一点探讨

上面我们说对偶式 1.b 1. b 3.b 3. b 似乎没有起到多大作用。这里以 1.b 1. b ,当N=16时为例

01234567
0

运用对偶,0还是0额算了

01234567
042(对偶)

这里可以发挥作用了,我们可以运用对偶性省去一次计算,棒棒哒~

01234567
0426

还是他本身,运算结束。
我们发现这里用了对偶可以省去一次运算。那么对于任意的 N N 1.b 可以发挥多大作用?由于求解总是由小到大,我们发现只要满足

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

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

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大困困瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值