【C语言】求第n个斐波那契数

问题与前言

输入n,输出第n个斐波那契数

例如:

输入:5  输出:5

输入:10, 输出:55

输入:2, 输出:1

斐波那契数列(Fibonacci sequence),又称黄金分割数列 ,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,其数值为:1、1、2、3、5、8、13、21、34……

在学习数学的过程中我们一定遇到过斐波那契数列,但是现在如何用C语言来解决这个问题呢?这里我将给出递归与迭代两种方法尝试求解,然后你会发现后者更适合该问题,而前者则不。

不了解递归的朋友可以先去看看我写的递归文章。递归是一种特殊的算法设计模式,它涉及到一个函数或过程直接或间接地调用自身。这种技术通常用于解决可以分解为更小、更简单子问题的问题。

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。重复执行一系列运算步骤,从前面的量依次求出后面的量的过程。此过程的每一次结果,都是由对前一次所得结果施行相同的运算步骤得到的。计算机中需要反复执行的子程序(一组指令),即执行程序中的循环,直到满足某条件为止,亦称为迭代。

说得通俗点,循环是迭代的一种,但不是所有迭代都是循环。比如goto语句也可以是一种迭代。有时递归的层次太深,会浪费太多的栈帧空间,也可能引起栈溢出的问题,所以这种情况我们就需要迭代。而求第n个斐波那契数,就属于该问题。

话不多说,我们来看看到底怎么个事吧!

递归法

我们已经知道该怎么去写一个递归了,我们分析问题时最重要的是要抓住这个问题如何拆分成更小的一个问题,或者说怎么样可以每次脱下一层(最外层),然后每脱一层都更接近最小的那层,就像剥开一个洋葱。

那么求第n个斐波那契数,要怎么找“层”呢?其实斐波那契数是那种乍一看就非常适合递归的。我们回想斐波那契数的规律。

如果不管特殊情况(第1和2个斐波那契数),一般情况下,每一位数都是前两个数的和。也就是说知道前两项我们就知道这一项,而前两项如何求得呢?它们各自也是去找自己前两项,所以我们会发现,做的是同样或者说极其类似的事情。

那么当我们写一个函数Fib()专门用来计算第n个斐波那契数时,计算第n-1个和n-2个斐波那契数时正如上面说的做同样的事,我们相当于要去计算第n-1和n-2个斐波那契数,也就是说还是去找这两项各自的前两项,所以还是调用函数Fib(),所以最后的结果就是我们在Fib()函数里调用了自己。

但是我们知道递归必须要有结束条件,而且每一次递归层次加深,都应该更接近这个结束条件。那么,在这个问题里我们的结束条件应该是什么呢?

其实说结束条件,还可以说就是我们的特殊情况:对于前两个数1、1,由于它俩没有前两项,我们可以单独写一个if语句,if(n<=2)…,而else就是第三个数往后有前两项的正常情况了。假设我们现在要求第5个斐波那契数,那么我们要去找第3和4个斐波那契数,然后它们各自要去找自己的前两项……在这个过程中找的每一项都无法确定值是多少,直到我们找到n<=2的情况,我们才确定一个具体的值,然后因为确定了这里的具体值,外一层的值也得以确定,逐层返回:

int Fib(int n)//要传参数n,告诉这个函数我们要求的是第几个斐波那契数
              //返回值是int类型,因为得到的结果也是一个整数
{
    if(n<=2)
       return 1;
    else
       return Fib(n-1)+Fib(n-2);//斐波那契数为前两个斐波那契数的和
                                //而前两个斐波那契数各自也是自己的前两项之和,所以也调用Fib()
}

图解:

递归法求斐波那契数的缺陷 

此时,不知道你是否看出来了,黄色的箭头,是不是有点多了?明明只是第5个斐波那契数却有8个箭头,而每一个箭头代表一次函数调用。

我们还可以设计一种方法来看看这个缺陷在输入的n较大的时候,有多么夸张:

int Fib(int n)
{
    int count=0;//用来统计第3个斐波那契数被计算到了多少次
    if(n==3)
        count++;//具体计算 
    if(n<=2)
        return 1;
    else
        return Fib(n-1)+Fib(n-2);
}

上面的n为5时,第3个斐波那契数被结算到了2次,但是当我们让n==40时,第3个斐波那契数会被计算多少次呢?

#include<stdio.h>
int count = 0;//count为全局变量
int Fib(int n)
{
    if (n == 3)
        count++;//当计算到第3个斐波那契数时,统计一下
    if (n <= 2)
        return 1;
    else
        return Fib(n - 1) + Fib(n - 2);
}

int main()
{
    int n = 40;
    int ret = Fib(n);
    printf("%d\n", ret);
    printf("第3个斐波那契数倍计算到了%d次\n",count);//因为count为全局变量,这里可以打印想要的结果
    return 0;
}

运行结果:

首先,这个运行需要的时间较长,要等待一会才能打印出结果,可见运算量之大。

 然后我们看到下面,仅仅是求第40个斐波那契数,就已经把第3个斐波那契数计算了千万次了,这效率能高吗?

当我们再把n加到50看看结果:

等了相当久(在笔者的电脑里需要一分钟)后才出来结果,而且这个count的结果比40又大得多:

这是已经是5亿次了,从40到50貌似只是10的差距,其实计算量是在指数级地增长。这里面怎么可能每一次都是必要的计算,其实有很多没有必要的计算。 

  

小结

递归法求第n个斐波那契数虽不难写,也简洁,却隐藏巨大的缺陷,当n越大,这个缺陷就会越大得多。

迭代法

我们从第n个斐波那契数的规律出发,普通情况下,三个数里第三个数就是前两个数的和,我们可以把要求的数就当做一组数的第三个数,我们需要让它变成循环“走”起来,才能我们想要哪个第三个数就能“走”到那里去,所以我们可以做这样一件事,每次计算完第三个数(即前两个数之和)之后,把后两个数往前赋值:

c = a + b;//c是第三个数,a、b是前两个数
a = b;
b = c;

那么在经过a=b;和b=c;的赋值后,下一轮的c就能得到新的第三个数,其实就是上一轮的c代表的第n个斐波那契数的下一个斐波那契数,这样求得一个个斐波那契数正是我们想要的

现在重点的问题是,我们要怎么写这个循环条件呢?而且我们现在用的变量是a、b、c,该怎么准确对应第n个斐波那契的n呢?

那么,我们可以简单画个对应图看看情况:

那么当我们循环停下时,此时得到的c就是我们要的第n个斐波那契数,接下来只要再处理一下n<=2的情况和变量的声明与初始化:

我们可能会一下写成这样:

陷阱1

问题出在哪呢?int c = 0;的c不应该初始化为0,当我们求前两位斐波那契数时,我们想要得到的返回值是1而不是0,所以这里改为1,(有悖于我们一般初始化的惯性思维)就刚好能完成我们的需求。

 陷阱2

其实除了这一种陷阱,对C语言掌握不够熟练的我在写这段代码的时候还写出了另一种错误,给遇到相同情况的朋友解答,没有遇到的朋友可以直接跳过:

按理说,n>2等同于n-2>0,然后好像可以写成while(n-2)。但其实当n=1时,n-2为-1,居然进入了循环:

调试效果:

可以从左侧箭头看到,此时确实进入了循环,而这绝不是我们想要的。所以在C语言中,当while的括号内为负数时,是可以进入循环的,要注意这一点。

小结

讲到这里,求第n个斐波那契数的递归法与迭代法都已讲解完了,相信大家对于哪种方法更好也已有了清晰的判断,希望遇到错误可以向我反馈,感谢! 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值