递归还是不递归,That‘s a problem!

<script type="text/javascript">digg_url = "http://blog.csdn.net/rangercyh/archive/2010/04/09/5465519.aspx";digg_title = "递归还是不递归,That‘s a problem!";digg_bgcolor = "#FFFFFF";digg_skin = "normal";</script><script src="http://digg.com/tools/diggthis.js" type="text/javascript"></script><script type="text/javascript">digg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;</script>

今天我班的大神心血来潮,居然找了本算法的书看起来了,里面有很多经典的例子,大神在编程方面是一张白纸,所以看到讲解递归的部分时就傻了眼,比如书上讲解求Fibonacci数列时就用的是递归算法,相当的简洁明了,代码如下:

unsigned long fibonacci (int n)
{
    if (n <= 2)
    {
        return 1;
    }
    else
    {
        return fibonacci(n-1) + fibonacci(n-2);
    }
}

我对于递归的思想很是赞同的,有化繁为简,分而治之的想法在里面,就好像很多编程思想一样,如:动态规划等等。但我认为大部分的递归想法仅仅是在人的接受能力上进行了化简,这是后话。其实递归算法是很好理解的,只是大神还不习惯罢了。如上的程序是运用数列的递推公式非常明白的写出来的。但就如我所言,这个递归算法其实浪费了很多CPU资源,我们可以仔细分析一下,比如:

f(5) = f(4) + f(3)
     = f(3) + f(2) + f(2) + f(1)
     = f(2) + f(1) + f(2) + f(2) + f(1)
     = 1 + 1 + 1 + 1 + 1

这里计算f(5)就用到了12个加法,其实我们可以发现我们把f(3)算出来后f(4)其实不用像我这样拆开来算的,直接用f(3)的结果就行了,换句话说即充分利用所求项等于前两项的和,而这两项之间又有关系,不难想到,可以写出一个不使用递归的算法如下:

unsigned long fibonacci (int n)
{
    unsigned long f0, f1, temp;
    int i;
    if (n <= 2)
    {
        return 1;
    }
    else
    {
        for (f0 = f1 = 1, i = 3; i <= n; i++)
        {
            temp = f0 + f1;
            f0 = f1;
            f1 = temp;
        }
        return f1;
    }
}

这个算法用一个变量temp保存两个前项之和,然后及时更新f0和f1,最后那个return返回temp也是可以的,因为我的temp保存的内容和f1一样。可以再看一下我们计算f(5)时做了多少个加法,仔细一数是仅仅只用了三个加法,大大减少了运算开销,而其上面那个递归程序因为在不停的调用函数,所以还会消耗一定的函数调用栈。为什么大牛们都说除非是实在太繁杂,否则别用递归,原因可见一二。我把我的想法向班上的大牛说了下,他马上告诉我一个在一本书上的更快的算法,这着实让我们班一群小草很是震惊,他说是这么想的,由我上面那个非递归的算法可以推出在算f(n)时最多不会超过n-2个加法,要加快程序只能把加法继续减少,这里才是思维的精髓,事实上一般减少加法的方法就是使用乘法,不过后来我想到乘法器比加法器要复杂,所以能够提高的效率我们并不可知,不过这种思想确实不一般。曾经在线性代数课上老师也介绍过一个伪Fibonacci矩阵,即一个2*2的矩阵,第一行元素为1,1,第二行为1,0。我们发现f(n)的值就是这个矩阵n-2次方后在第一行上的两个元素的和,如f(5)就等于把这个矩阵3次方后,此时第一行的两个元素分别为3和2,所以f(5) = 3 + 2。这个要推也很简单,算一两个就发现这个规律了,依据这个思想,他写下了如下的代码:

unsigned long fibonacci (int n)
{
    unsigned long a, b, c, d;
    int i;
    if (n <= 2)
    {
        return 1;
    }
    else
    {
        matrix_power(1,1,1,1,n-2,&a,&b,&c,&d);
        return f1;
    }
}
//计算矩阵的n次方
matrix_power(unsigned long a, unsigned long b, unsigned long c, unsigned long d, int n,
             unsigned long *aa, unsigned long *bb, unsigned long *cc, unsigned long *dd)
{
    unsigned long xa, xb, xc, xd;
    if (1 == n)
    {
        *aa = a, *bb = b, *cc = c, *dd = d;
    }
    else if (n & 0x01 == 1)
    {
        matrix_power(a, b, c, d, n-1, &xa, &xb, *xc, *xd);
        *aa = a * xa + b * xc;
        *bb = a * xb + b * xd;
        *cc = c * xa + d * xc;
        *dd = c * xb + d * xd;
    }
    else
    {
        matrix_power(a, b, c, d, n>>1, &xa, &xb, &xc, &xd);
        *aa = xa * xa + xb * xc;
        *bb = xa * xb + xb * xd;
        *cc = xc * xa + xd * xc;
        *dd = xc * xb + xd * xd;
    }
}

我们还可以发现这段代码在求矩阵的n次方时用了一个分治的思想,判断n的奇偶,如果是偶数就递归求出n/2次方再平方,若是奇数就写成M*M的偶数次方,这里居然又用了递归,倒!原本是不想递归才绕了这么一大圈子,现在又回到原点了,实在不好意思去求大牛了, 只好自己动手丰衣足食,不就是求一个矩阵的n次方吗?看我也不用递归。矩阵弄在一起太复杂,我就假设是求一个数m的n次方吧!如果是按递归算法求m的n次方,然后把每次递归调用时传入的n值列出来我发现一个惊人的事实,那就是每次调用时n均为2的倍数,这时我似乎看到了些什么,于是我仔细研究了下,加上大胆的猜想,终于让我发现了一个规律,在求m的n次方时可以把n写成二进制数,找到二进制位为1的位,然后可以将n写成2的这些位所对应的权值之和,于是m的n次方就能写成m的这些权值次方的和了,比如求m的9次方,将9写成二进制数为1001,所以9 = 2的0次方 + 2的3次方,令a = 2的0次方,b = 2的3次方,则m的9次方 = m的a次方 + m的b次方。这样一来我只需要遍历n的二进制位就行了,于是我迅速修改了上面求矩阵的n次方的代码:

//改变出参的值
getTemp(unsigned long **aa, unsigned long **bb, unsigned long **cc, unsigned long **dd,
        unsigned long a, unsigned long b, unsigned long c, unsigned long d)
{
    unsigned long tempa = **aa, tempb = **bb, tempc = **cc, tempd = **dd;
    tempa = **aa * a + (**bb) * c;
    tempb = **aa * b + (**bb) * d;
    tempc = **cc * a + (**dd) * c;
    tempd = **cc * b + (**dd) * d;
    **aa = tempa;
    **bb = tempb;
    **cc = tempc;
    **dd = tempd;
}

//计算矩阵的n次方修正版
matrix_power(unsigned long a, unsigned long b, unsigned long c, unsigned long d, int n,
             unsigned long *aa, unsigned long *bb, unsigned long *cc, unsigned long *dd)
{
    unsigned long xa = a, xb = b, xc = c, xd = d;
    unsigned long tempa = a, tempb = b, tempc = c, tempd = d;
    int flageven = 0, flag = 1;
    if (!(n & 0x01UL))
    {
        flageven = 1;
    }
    while (n > 0)
    {
        if (1 == (n & 0x01UL))
        {
            if (1 == flag)
            {
                *aa = a;
                *bb = b;
                *cc = c;
                *dd = d;
                flag = 0;
                if (1 == flageven)
                {
                    getTemp(&aa, &bb, &cc, &dd, xa, xb, xc, xd);
                }
            }
            else
            {
                getTemp(&aa, &bb, &cc, &dd, xa, xb, xc, xd);
            }
        }
        tempa = xa * a + xb * c;
        tempb = xa * b + xb * d;
        tempc = xc * a + xd * c;
        tempd = xc * b + xd * d;
        xa = tempa;
        xb = tempb;
        xc = tempc;
        xd = tempd;
        n >>= 1;
    }
}

忙活了半个晚上终于结束了这场和递归的战斗,至此这个Fibonacci数列总算被阶段性拿下,一个我认为可以接受的非递归算法出炉。关于递归还有很多可以讨论一下,不过现在已经早上了,我还是先去休息一下,过段时间再来和大家分享一下自己的递归心得。同时也希望大家对我上面的算法有什么建议或是置疑都可以提出来,我们共同探讨,互联网最方便的应该是交流了,我期待您的关注。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值