笔记2-Fibonacci/斐波那契数列问题

  • 前言

笔记是笔者个人理解,难免有误(尤其是关于算法复杂度分析的部分),还望各位大佬不吝赐教!欢迎讨论交流!

还有一些胡言乱语,纯笔者喝酒喝出来的胡话,权博看官们一乐,慎用、勿当真!

因为个人原因,没办法及时回复消息,望佬们海涵!

基本用伪代码来体现算法思路,没办法兼顾各种语言,直接copy不能用,靠各位大佬自己动手小修小改一下了!

Fibonacci数列:1,1,2,3,5,8,13,21,...

规律:

F[0] = F[1] = 1;

F[n] = F[n-1] + F[n-2],n>=2;

  • 思路

乍一看到规律“F[n] = F[n-1] + F[n-2],n>=2”,很容易递归上头,不过递归也分情况好坏:老老实实地做递归就很暴力,一点都不优雅,但这也是最容易被想到的方法;还可以借鉴动态规划的备忘录思想,做记忆递归,即每个元素只计算一遍便足够确定值了,之后再需要用到该元素的时候直接用,不必再计算。

那么类似的,既然能记忆递归,那也可以直接做动态规划

再看上面那条规律,一遍一遍地重复滚雪球,这不就是迭代的感觉么?

 综上,暂时有了四种思路来解决Fibonacci数列问题:

  1. 普通递归
  2. 记忆递归
  3. 动态规划
  4. 迭代  

1. 普通递归

来个函数,就叫Fibonacci吧。有一个整型形参k,表示要求斐波那契数列的第k个元素的值。所以我们定义的这个Fibonacci函数得返回一个整型,那按照递归的一般形式,肯定要调用自个儿,所以return内容那里就先写自个儿吧,传什么参数给它待会儿再说。先有了个大致雏形:

int Fibonacci(int k)
{
    //等待添加代码
    return Fibonacci(???);
}

 那根据规律“F[n] = F[n-1] + F[n-2]”,代入到我们的Fibonacci函数来,就是说Fibonacci(k)=Fibonacci(k-1)+Fibonacci(k-2)呗。所以return内容可以确定下来了:

int Fibonacci(int k)
{
    //等待添加代码
    return Fibonacci(k-1)+Fibonacci(k-2);
}

递归函数的一般形式中,还要有一个用于结束递归的判断语句,这里就是k=0或k=1的情况:

int Fibonacci(int k)
{
    if(k==0||k==1)
    {
        return 1;
    }
    return Fibonacci(k-1)+Fibonacci(k-2);
}

2. 记忆递归

涉及到“记忆”,那就要准备一个数组作为“备忘录”记录已经计算过值的元素,比如下面的动态一维数组F。

//数据准备动作:
int n=...;
int *F=new int[n];
//初始化动作:
for(int i=0;i<n;i++)
{
    F[i]=0;    //都赋0初值
}

int Fibonacci(int k)
{
    //等待添加点睛之笔
    if(k==0||k==1)
    {
        F[k]=1;
    }else{
        F[k]=Fibonacci[k-1]+Fibonacci[k-2];
    }
    return F[k];
}

至此,这段代码仍然是光记忆但没用到记忆的内容,让我们来为它添上这点睛之笔:

if(F[k]>0)    //看到这个判断条件,明白之前赋初值的时候为啥要赋0了吧?
{
    return F[k];
}

3. 动态规划(DP)

思想基本和记忆递归类似,每个元素最多只被计算一次,上码:

int Fibonacci(int k)
{
    int *F=new F[k];
    F[0]=F[1]=1;
    for(int i=2;i<=k;i++)
    {F[i]=F[i-1]+F[i-2];}
    return F[n];
}

但是,这个有缺陷,因为它是作为子函数等待被其他函数体调用的,每个元素最多只被计算一次是针对当次调用执行而言的;而每被调用一次,F数组就需要计算一遍,还是重复计算了,浪费。改进后可以避免此种情况,哪怕子函数Fibonacci(int k)被多次调用,F数组从头到尾每个元素最多也是只被计算一次,码如下:

//全局变量:
int n=...;
int *F=new F[n];
//初始化动作:
for(int i=0;i<n;i++)
{F[i]=0;}

int Fibonacci(int k)
{
    F[0]=F[1]=1;
    int j=0;
    if(F[k]>0)
    {return F[k];}
    while(F[j]>0){
        j++;
    }    //j跳出循环后,j的值即为F数组当前尚未被计算过的首个元素的位置
    for(int i=j;i<=k;i++)
    {F[i]=F[i-1]+F[i-2];}
    return F[k];
}

 但是——虽然避免了重复计算,但是又引入了循环判断TVT。那再改进一下吧,把循环判断去掉,用一个全局变量索引index来定位F数组当前尚未被计算过的首个元素的位置,码如下:

//全局变量:
int n=...;
int *F=new F[n];
int index=2;
//初始化动作:
for(int i=0;i<n;i++)
{F[i]=0;}
F[0]=F[1]=1;    //这个也提出来,能少做一次计算也好,节俭点肯定没错,跟过日子一个样嘛嘿嘿。

int Fibonacci(int k)
{
    if(F[k]>0)    //判断条件里也可以写index>k,由算法来看和F[k]>0是等价的
    {return F[k];}
    for(int i=index;i<=k;i++)
    {
        F[i]=F[i-1]+F[i-2];
        index=i;
    }
    return F[k];
}

这样应该是解决上面意外引入的问题了。这个案例是不是有点取舍的味道?多花费一点内存空间来存放全局变量F[0]和F[1],能节省很多时间复杂度,有舍就有得,“舍得”二字,大概是能量守恒定律的诗意化的说法吧,谁能想到,科学和国语有一天也能学科交叉?挺妙的。

4. 迭代

这典型的“用空间换时间”,跟上面一样(作为子函数被调用一次就要重复计算一次),只需要分配三个整型变量的空间给Fn、Fn1、Fn2,然后就撒手不管让他仨滚雪球去,迭来迭去 ,颇有一股不顾人死活的劲头。码如下:

int Fibonacci(int k)
{
    if(k==0||k==1)
    {return 1;}
    int Fn=0,Fn1=1,Fn2=1;
    for(int i=3;i<=n;i++)
    {
        Fn=Fn2+Fn1;
        Fn1=Fn2;
        Fn2=Fn;
    }
    return Fn;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值