算法——Fibonacci数列的多种解法(递归算法)

咳咳,金宸欧巴今天来更新博客了,今天想写的一点内容是关于斐波那契数列的解法,fibonacci数列的定义如下:
F(n)=
{
        a,                           n=1
        b,                           n=2

        F(n-1)+F(n-2),               n>2并且n是奇数

        F(n-1)+F(n-2)+F(n-3),        n>2并且n是偶数
}
        这里a和b是定值,现给出a,b和n,你的任务是计算F(n)。

wtf!!这个不是很简单么?递归!递归啊!

如题目中的意思所示,第一个数是我们给的一个a,第二个数是我们给的值b,凡是遇见大于2的奇数就将数列中的前两个值相加,遇见大于2的偶数就将数列中的前三个数字相加,递归走一波!于是有了以下代码……

//
// main.cpp
// 斐波那契
//
// Created by 王金宸 on 16/4/13.
// Copyright © 2016年 王金宸. All rights reserved.
// 斐波那契是不能用递归算法的

#include <iostream>
using namespace std;
int feiBo(int x,int y,int t);
int main(int argc, const char * argv[])
{
    int a,b,c,n;
    long long int k;
    cin>>c;
    for (int t = 0; t != c; t++)
    {
        cin>>a>>b>>n;
        k = feiBo(a,b,n);
        cout<<k<<endl;
    }
    return 0;
}

int feiBo(int x,int y,int t)
{
    int sum = 0;

    if (t==1)
    {
        sum = x;
    }
    else if (t==2)
    {
        sum = y;
    }
    else if ((t%2!=0)&&(t>2))
    {
        sum = feiBo(x,y,t-1) + feiBo(x,y,t-2);
    }
    else if ((t%2==0)&&(t>2))
    {
        sum = feiBo(x,y,t-1) + feiBo(x,y,t-2) + feiBo(x,y,t-3);//返回的sum只是一个值改变的变量只是中间值
    }
    else
        printf("error");

    return sum;
}

程序是没有错误的啦,输入输出也没有错误,但是!but这样有一个问题就是我们的程序运行时间太长了!,为什么这么说呢?如果我们输入一个值20,我们想要得到f(20)的值,我们必须获取到f(19),f(18),f(17)的值,然而函数体是在嵌套运行的,想要得到f(20)具体的值,我们要递归到f(2),f(1),我们知道,时间复杂度取决于语句执行的次数,如果这样使用递归,程序的时间复杂度会随着n的增大趋近于指数级,那怎样能够避免这个问题呢?是不是递归就不能够使用了?

下面有三种解决办法:

  • 第一,使用正向的循环推导,废话不多说!上代码!:
#include <iostream>
using namespace std;
int main(int argc, const char * argv[])
{
    long long int a,b,c,d,sum = 0;
    cin>>d;
    for (int i = 0;i != d;i++)
    {
        cin>>a>>b>>c;

        if(c == 1)
            cout<<a<<endl;
        if(c == 2)
            cout<<b<<endl;

        for (int k = 1; k < c-1; k++)
        {
            if (k%2 != 0)
            {
                sum = a + b;
                a = b;
                b = sum;
            }
            else
            {
                sum = b + b;
                a = b;
                b = sum;
            }
        }
        if (c>2)
        {
             cout<<sum<<endl;
        }

        sum = 0;
    }
    return 0;
}

我们不能倒着推难道还不能正着推么?这种方法的原理是使用for循环和拷贝赋值的方法,依次算出第三个值到你所需要的值,时间复杂度也降低到了O(n)

  • 第二种方法是使用数组,数组较上一个方法相比时间复杂度一样,但是便于取值,不用进行拷贝赋值
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
    int m,i,n,t,f,z,a[40],b[40];
    cin>>m;
    t=m;
    while(m)
    {
        cin>>f; a[0]=f;
        cin>>z; a[1]=z;
        cin>>n;
        for(i=2; i<n ;i++){
            if(!( i%2 ))
            {
                a[i]=a[i-1]+a[i-2];
            }
            else
            {
                a[i]=a[i-1]+a[i-2]+a[i-3];
            }

        }
        b[t-m]=a[n-1];
        m--;
    }
    for(i=0; i<t ;i++)
        cout<<b[i]<<endl;
    return 0;
}
  • 最后,我们用递归的方法来实现一下这个题目,普通的递归是不够好的,所以这里要使用一种叫做尾递归的方法

        先来看下尾递归的定义(来自百度百科)   
        尾递归:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
    
       当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。虽然编译器能够优化尾递归造成的栈溢出问题,但是在编程中,我们还是应该尽量避免尾递归的出现,因为所有的尾递归都是可以用简单的goto循环替代的。
    

– 由此我们可以看出,使用尾递归首先具备的条件是递归形式的调用都出现在函数的末尾,而且返回值不属于表达式的一部分,它所具备的优势就是当函数进入递归函数的时候不像其他的递归再次进入递归,而是返回了这个递归,然后这个递归再开启出来,这样就不会一直压栈,而是出栈了之后再入栈。

//使用尾递归
// Created by 王金宸 on 16/4/13.
// Copyright © 2016年 王金宸. All rights reserved.
#include <iostream>
using namespace std;
int feiBo(int x,int y,int t);
int d = 0;
int main(int argc, const char * argv[])
{
    int a,b,c,n;
    long long int k;
    cin>>c;
    for (int t = 0; t != c; t++)
    {
        cin>>a>>b>>n;
        k = feiBo(a,b,n);
        d = 0;
        cout<<k<<endl;
    }
    return 0;
}

int feiBo(int x,int y,int t)
{
    if (t==1)
    {
        return x;
    }
    else if (t==2)
    {
        return y;
    }

    else
        {
            if (d%2==0)
            {
                d++;
                return feiBo(y, x+y, t-1);
            }
            else
            {
                d++;
                return feiBo(y, 2*y, t-1);
            }
        }


    return 0;
}

int feiBo(int x,int y,int t)就是一个尾递归函数,而下面是尾递归的四种return的返回值,可以看出返回值的形式都是值或feiBo函数,全局变量d的作用是一个计数器,在调用的递归时候可以改变全局变量的值而当主函数return的时候全局变量才会释放,所以有必要在每次循环之后给全局变量清零,而且全局变量的声明要初始化,这样比较安全哈哈哈。

jacksonWant原创,jacksonWant一个为了情怀而奋斗的小码农,文中如有勘误可以给我发邮件。

    原文作者:递归算法
    原文地址: https://blog.csdn.net/JacksonWant/article/details/51148243
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞