还在用递归实现斐波那契数列,面试官一定会鄙视你到死

斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368......

我记得在初学C语言的时候,大学老师经常会讲一些常见的数学问题及递归的使用,其中斐波那契数列就是一定会被拿出来举例的。在后来工作中,面试做面试题的时候,也很大概率会出现编写算法实现斐波那契额数列求值。可以说,在我们编程道路上,编写算法实现斐波那契数列是每个程序员必定会做的一件事。昨天去参加腾讯课堂举办的一个线下活动,活动中有一位嘉宾,是某课堂的创始人,也是算法课程的讲师,就讲到了这个问题,算是颠覆了我对该问题的认知。本文就根据这名讲师的讲解,来分析和整理一下该问题算法的实现。

下面,我们来看看其中几种常见的算法,并分析其效率。

  1、递归法

通过观察,我们会发现其中的规律,第一项和第二项的值均为1,后面每项的值都是前两项值之和,所以我们很多人基本上都会使用递归来实现,常见的算法如下:

1 public int fib(int n) {
2     if (n == 1 || n == 2) {
3         return 1;
4     }
5     return fib(n - 2) + fib(n - 1);
6 }

这段代码看起来非常的简洁和优雅,我想我们绝大多数的人平时也都是这么写的吧,在此之前笔者就一直都是这么写的,而且在我的知识储备中也就只知道有这样一种算法。

实际上,当n还比较小的时候,用递归法来实现是没有什么问题的,但是当n稍微大一点的时候,比如n=45的时候,我们通过如下测试代码来看看它的执行结果:

1 MyClass myClass = new MyClass();
2 long t1 = System.currentTimeMillis();
3 int n = 45;
4 int result = myClass.fib(n);
5 long t2 = System.currentTimeMillis();
6 System.out.println("n=" + n + ";result=" + result + ";time=" + (t2 - t1));

得到结果为:

n=45;result=1134903170;time=2881

我们发现执行这段代码,花费的时间是2881ms。如果值再大一点,如n=48:

n=48;result=512559680;time=11746

时间达到了11s以上了!如果n再稍微大一点,所消耗的时间是成指数级增长的,比如n=64的时候,所消耗的时间可能是两三百年!不信的话,读者可以试试!

这样看来,就非常可怕了,我们一直认为毫无问题的看起来既简洁又优雅的算法,居然是这么耗时的,这简直就是一段垃圾代码。

我们用一张图来简单分析一下该算法的执行过程,以n=6为例:

我们会发现f(n)这个方法被调用了很多次,而且其中重复率非常之高,也就是说被重复计算了很多次,如果n稍微大一点这棵树会非常庞大。这里我们可以看出,每个节点就需要计算一次,总计算的次数就是该二叉树节点的数量,可见其时间复杂度为O(2n),是指数级的,其空间复杂度也就是该二叉树的高度,为O(n)。这样来看,我们应该就清楚了,为什么这段代码效率如此低下了吧。

2、数组保存法(该名称是自己命令的)

为了避免无数次重复,可以从n=1开始往上计算,并把每一个计算出来的数据,用一个数组保存,需要最终值时直接从数组中取即可,算法如下:

1 public int fib(int n) {
2     int[] fib = new int[n];
3     fib[0] = 1;
4     fib[1] = 1;
5     for (int i = 2; i < n; i++) {
6         fib[i] = fib[i - 2] + fib[i - 1];
7     }
8     return fib[n - 1];
9 }

我们也分别取n=45和n=48来看看执行结果

n=45;result=1134903170;time=0
n=48;result=512559680;time=0

消耗的时间都是0(我这里获取的时间是精确到ms级别的,前后的时间差在1ms以下,所以这里计算出来的结果为0,实际耗时不可能为0,后续不赘述),可见执行效率提高了很多。这种算法主要有一个for循环,其时间复杂度为O(n),期间需要开辟一个长度为n的数组,所以空间复杂度也为O(n),这就在上述算法的基础上极大地提升了效率。

  3、变量保存法(也是自己命名的)

尽管上述算法已经很高效了,但我们还是会发现一个问题,其实整个数组中,每次计算时都只需要最新的3个值,前面的值计算完后就不再需要了。比如,计算到第10次时,需要的数组空间只有第8和第9两个空间,前面第1到第7个空间其实就不再需要了。所以我们还可以改进,通过3个变量来存储数据,算法如下:

 1 public int fib(int n) {
 2     int first = 1;
 3     int second = 1;
 4     int third = 2;
 5     for (int i = 3; i <= n; i++) {
 6         third = first + second;
 7         first = second;
 8         second = third;
 9     }
10     return third;
11 }

时间复杂度仍然为O(n),而空间复杂度为常量级别3,即空间复杂度为0,所以这种方法是非常高效的。

4、公式法

实际上,求斐波那契数列值有一个公式:

可以通过该公式来实现算法:

1 public int fib(int n) {
2     double c = Math.sqrt(5);
3     return (int) ((Math.pow((1 + c) / 2, n) - Math.pow((1 - c) / 2, n)) / c);
4 }

其时间复杂度和空间复杂度就取决于JDK中这些数学公式的实现了,执行效率也是非常高的:

n=48;result=512559680;time=0

通过上面的分析,我们在写算法实现斐波那契数列时,可以根据实际情况选择第3种和第4种。

如果读者发现本文有描述不正确或者不妥的地方,请不吝赐教,非常感谢!

原文地址:https://www.cnblogs.com/andy-songwei/p/11707142.html

时间: 2024-10-11 23:30:56

还在用递归实现斐波那契数列,面试官一定会鄙视你到死的相关文章

13、蛤蟆的数据结构笔记之十三栈的应用之栈与递归之斐波那契数列

13.蛤蟆的数据结构笔记之十三栈的应用之栈与递归之斐波那契数列 本篇名言:"人生不是一支短短的蜡烛,而是一支由我们暂时拿着的火炬,我们一定要把它燃得." 继续递归的斐波那契数列问题. 欢迎转载,转载请标明出处: 1.  斐波那契数列 斐波那契数列,又称黄金分割数列,指的是这样一个数列:0.1.1.2.3.5.8.13.21.--在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)在现代物理.准晶体结构.化学

249 递归:概念,利用递归求1~n的阶乘,利用递归求斐波那契数列,利用递归遍历数据

6.1什么是递归 递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数. 简单理解: 函数内部自己调用自己, 这个函数就是递归函数 注意:递归函数的作用和循环效果一样,由于递归很容易发生"栈溢出"错误(stack overflow),所以必须要加退出条件return. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"&g

【算法】还在用递归实现斐波那契数组,面试官一定会鄙视你到死

我记得在初学C语言的时候,大学老师经常会讲一些常见的数学问题及递归的使用,其中斐波那契数组的实现就是一定会被拿出来举例的.在后来工作中,面试做面试题的时候,也很大概率会出现编程实现斐波那契额数组算法.可以说,在我们编程道路上,编写程序实现斐波那契数组算法是每个程序员必定会做的一件事. 斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,

递归之斐波那契数列

在数学上,費波那契數列是以递归的方法來定义: (n≧2) 用文字來说,就是斐波那契数列由0和1开始,之後的斐波那契数列就由之前的兩数相加. 这也是从维基百科上摘来的表述,比较的专业点.那个简单的写一下前面的几个是: 0,1,1,2,3,5,8,13,21,34,55,89,144,233...... 这个也是成一个指数增长的现象,所以兔子要是都按这个节奏生长,那就天天有肉吃了,还便宜!!! 这个问题相对与汉诺塔问题,较我而且,斐波那契数列一目了然,比较的好理解. 下面就用Ptyhon来实现一下:

递归与斐波那契数列

一.递归 在函数内部,可以调用其他函数;如果一个函数在内部调用自己,那这个函数就是递归函数. 案例:遍历当前目录下的所有文件 1.递归遍历 1 import os 2 def gci(filepath): 3 #遍历filepath下所有文件,包括子目录 4 files = os.listdir(filepath) 5 for fi in files: 6 fi_d = os.path.join(filepath,fi) 7 if os.path.isdir(fi_d): 8 gci(fi_d)

Python递归及斐波那契数列

递归函数 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数.举个例子,我们来计算阶乘 n! = 1 * 2 * 3 * ... * n,用函数 fact(n)表示,可以看出:fact(n) = n! = 1 * 2 * 3 * ... * (n-1) * n = (n-1)! * n = fact(n-1) * n所以,fact(n)可以表示为 n * fact(n-1),只有n=1时需要特殊处理.于是,fact(n)用递归的方式写出来就是: def fact(

使用递推和递归解决斐波那契数列问题~~~

/** * 使用递推的方式处理斐波那契数列 * @param sum * @param i * @return */ public static int findValue(int n){ if(n==1) { return 1; } if(n==2) { return 2; } int sum=1; int pre=1; for(int i=3;i<=n;i++) { int temp=sum; sum+=pre; pre=temp; } return sum; } /** * 采用递归的方式

关于递归和斐波那契数列

这次的题目是要求用递归算法求斐波那契数列的第n项. 众所周知:斐波那契数列中的项等于前两项相加的和,第一项为0,第二项为1.那么我们可以轻易得到递归公式: f(n)=f(n-1)+f(n-2); 其中,第一项为0,第二项为1: if(n==1) return 0; if(n==2) return 1; 然后得到如下代码: #include<stdio.h> int f(int n); int main() { int n; scanf("%d",&n); print

【递归】斐波那契数列第n个数

递归.递推计算斐波那契数列第n项的值: 1 #include <stdio.h> 2 long long fact(int n); //[递推]计算波那契数列第n个数 3 long long fact2(int n);//[递归] 4 int main(int argc, char *argv[]) 5 { 6 int i=1; 7 while(i<=10) 8 { 9 printf("%d %I64d %I64d\n",i,fact(i),fact2(i)); 10