求职干货:再也不怕面试官问斐波那契数列了!
原标题:求职干货:再也不怕面试官问斐波那契数列了!
??
作者 | 守望
责编 | 胡巍巍
前言
假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了。如果真这么想,那就危险了。
递归解法
递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
斐波那契数列的计算表达式很简单:
F(n) = n; n = 0,1
F(n) = F(n-1) + F(n-2),n >= 2;
因此,我们能很快根据表达式写出递归版的代码:
/*fibo.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契数列递归版*/
unsignedlongfibo(unsignedlongintn)
{
if(n <= 1)
returnn;
else
returnfibo(n-1) + fibo(n-2);
}
intmain(intargc,char*argv[])
{
if(1>= argc)
{
printf("usage:./fibo numn");
return-1;
}
unsignedlongn = atoi(argv[1]);
unsignedlongfiboNum = fibo(n);
printf("the %lu result is %lun",n,fiboNum);
return0;
}
关键代码只有4行。简洁明了,一气呵成。
编译:
gcc-ofibofibo.c
运行计算第5个斐波那契数:
$ time ./fibo 5
the 5result is 5
real0m0.001s
user 0m0.001s
sys 0m0.000s
看起来并没有什么不妥,运行时间也很短。
继续计算第50个斐波那契数列:
$ time ./fibo 50
the 50result is 12586269025
real1m41.655s
user 1m41.524s
sys 0m0.076s
计算第50个斐波那契数的时候,竟然将近两多钟!
递归分析
为什么计算第50个的时候竟然需要1分多钟。
我们仔细分析我们的递归算法,就会发现问题,当我们计算fibo(5)的时候,是下面这样的:
|--F(1)
|--F(2)|
|--F(3)| |--F(0)
| |
|--F(4)||--F(1)
||
|| |--F(1)
| |--F(2)|
||--F(0)
F(5)|
| |--F(1)
| |--F(2)|
|| |--F(0)
|--F(3)|
|
|--F(1)
为了计算fibo(5),需要计算fibo(3),fibo(4);而为了计算fibo(4),需要计算fibo(2),fibo(3)……
最终为了得到fibo(5)的结果,fibo(0)被计算了3次,fibo(1)被计算了5次,fibo(2)被计算了2次。可以看到,它的计算次数几乎是指数级的!
因此,虽然递归算法简洁,但是在这个问题中,它的时间复杂度却是难以接受的。
除此之外,递归函数调用的越来越深,它们在不断入栈却迟迟不出栈,空间需求越来越大,虽然访问速度高,但大小是有限的,最终可能导致栈溢出。
在linux中,我们可以通过下面的命令查看栈空间的软限制:
$ulimit-s
8192
可以看到,默认栈空间大小只有8M。
一般来说,8M的栈空间对于一般程序完全足够。如果8M的栈空间不够使用,那么就需要重新审视你的代码设计了。
递归改进版
既然我们知道最初版本的递归存在大量的重复计算,那么我们完全可以考虑将已经计算的值保存起来,从而避免重复计算,该版本代码实现如下:
/*fibo0.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契数列,避免重复计算版本*/
unsignedlongfiboProcess(unsignedlong*array,unsignedlongn)
{
if(n < 2)
returnn;
else
{
/*递归保存值*/
array[n] = fiboProcess(array,n-1) + array[n-2];
returnarray[n];
}
}
unsignedlongfibo(unsignedlongn)
{
if(n <= 1)
returnn;
unsignedlongret = 0;
/*申请数组用于保存已经计算过的内容*/
unsignedlong*array= (unsignedlong*)calloc(n+1,sizeof(unsignedlong));
if(NULL== array)
{
return-1;
}
array[1] = 1;
ret = fiboProcess(array,n);
free(array);
array= NULL;
returnret;
}
/**main函数部分与fibo.c相同,这里省略*/
效率如何呢?
$ gcc -o fibo0 fibo0.c
$ time ./fibo0 50
the 50result is 12586269025
real0m0.002s
user 0m0.000s
sys 0m0.002s
可见其效率还是不错的,时间复杂度为O(n)。
但是特别注意的是,这种改进版的递归,虽然避免了重复计算,但是调用链仍然比较长。
迭代解法
既然递归法不够优雅,我们换一种方法。如果不用计算机计算,让你去算第n个斐波那契数,你会怎么做呢?
我想最简单直接的方法应该是:知道第一个和第二个后,计算第三个;知道第二个和第三个后,计算第四个,以此类推。
最终可以得到我们需要的结果。这种思路,没有冗余的计算。基于这个思路,我们的C语言实现如下:
/*fibo1.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契数列迭代版*/
unsignedlongfibo(unsignedlongn)
{
unsignedlongpreVal = 1;
unsignedlongprePreVal = 0;
if(n <= 2)
returnn;
unsignedlongloop = 1;
unsignedlongreturnVal = 0;
while(loop < n)
{
returnVal = preVal +prePreVal;
/*更新记录结果*/
prePreVal = preVal;
preVal = returnVal;
loop++;
}
returnreturnVal;
}
/**main函数部分与fibo.c相同,这里省略*/
编译并计算第50个斐波那契数:
$ gcc -o fibo1 fibo1.c
$ time ./fibo1 50
the 50result is 12586269025
real0m0.002s
user 0m0.000s
sys 0m0.002s
可以看到,计算第50个斐波那契数只需要0.002s!时间复杂度为O(n)。
尾递归解法
同样的思路,但是采用尾递归的方法来计算。
要计算第n个斐波那契数,我们可以先计算第一个,第二个,如果未达到n,则继续递归计算,尾递归C语言实现如下:
/*fibo2.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契数列尾递归版*/
unsignedlongfiboProcess(unsignedlongn,unsignedlongprePreVal,unsignedlongpreVal,unsignedlongbegin)
{
/*如果已经计算到我们需要计算的,则返回*/
if(n == begin)
returnpreVal+prePreVal;
else
{
begin++;
returnfiboProcess(n,preVal,prePreVal+preVal,begin);
}
}
unsignedlongfibo(unsignedlongn)
{
if(n <= 1)
returnn;
else
returnfiboProcess(n,0,1,2);
}
/**main函数部分与fibo.c相同,这里省略*/
效率如何呢?
$ gcc -o fibo2 fibo2.c
$ time ./fibo2 50
the 50result is 12586269025
real0m0.002s
user 0m0.001s
sys 0m0.002s
可见,其效率并不逊于迭代法。尾递归在函数返回之前的最后一个操作仍然是递归调用。
尾递归的好处是,进入下一个函数之前,已经获得了当前函数的结果,因此不需要保留当前函数的环境,内存占用自然也是比最开始提到的递归要小。时间复杂度为O(n)。?
?
矩阵快速幂解法
这是一种高效的解法,需要推导,对此不感兴趣的可直接看最终推导结果。
下面的式子成立是显而易见的,不多做解释。
如果a为矩阵,等式同样成立,后面我们会用到它。假设有矩阵2*2矩阵A,满足下面的等式:
可以得到矩阵A:
因此也就可以得到下面的矩阵等式:
再进行变换如下:
以此类推,得到:
实际上f(n)就是矩A^(n-1)中的A[0][0],或者是矩A^n中的A[0][1]。
那么现在的问题就归结为,如何求A^n,其中A为2*2的矩阵。
根据我们最开始的公式,很容易就有思路,代码实现如下:
/*fibo3.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COL 2
#define MAX_ROW 2
typedef unsigned long MatrixType;
/*计算2*2矩阵乘法,这里没有写成通用形式,有兴趣的可以自己实现通用矩阵乘法*/
int matrixDot(MatrixType A[MAX_ROW][MAX_COL],MatrixType B[MAX_ROW][MAX_COL],MatrixType C[MAX_ROW][MAX_COL])
{
/*C为返回结果,由于A可能和C相同,因此使用临时矩阵存储*/
MatrixType tempMa[MAX_ROW][MAX_COL] ;
memset(tempMa,0,sizeof(tempMa));
/*这里简便处理*/
tempMa[0][0] = A[0][0] * B[0][0] + A[0][1] *B [1][0];
tempMa[0][1] = A[0][0] * B[0][1] + A[0][1] *B [1][1];
tempMa[1][0] = A[1][0] * B[0][0] + A[1][1] *B [1][0];
tempMa[1][1] = A[1][0] * B[0][1] + A[1][1] *B [1][1];
memcpy(C,tempMa,sizeof(tempMa));
return 0;
}
MatrixType fibo(int n)
{
if(n <=1)
returnn;
MatrixTyperesult[][MAX_COL] = {1,0,0,1};
MatrixTypeA[][2] = {1,1,1,0};
while(n>0)
{
/*判断最后一位是否为1,即可知奇偶*/
if (n&1)
{
matrixDot(result,A,result);
}
n /= 2;
matrixDot(A,A,A);
}
return result[0][1];
}
/**main函数部分与fibo.c相同,这里省略*/
该算法的关键部分在于对A^n的计算,它利用了我们开始提到的等式,对奇数和偶数分别处理。
相关文章
- 好暖!圆明园里一群游客悄悄捡树枝,原来是为了这一家子
- 戴岳云鹏面具抢劫怎么回事?女子为什么戴岳云鹏面具抢劫?
- 广东端州城区保洁面积涉嫌虚报 巡查组挽回2800多万元财政损失
- 百元连号钞票换“茅台”是大坑 消费者不明真相频被蒙骗
- 早报:雷军博鳌语录刷爆朋友圈 苹果CEO将上法庭
- “结婚4年备孕无果穿2月就怀上” 中脉内衣为何还在忽悠
- 联合国安理会讨论戈兰高地问题 14国反对美国决定
- 美一高中生因在校受欺凌拔刀刺伤同学被指控
- 英国议会堵死脱欧协议所有选项,媒体发问:脱欧要何去何从?
- 荷兰国花惨遭亚洲游客摧残,网友:应该拉起“警戒线”
- 酒驾最重死刑,台当局虚晃一招?
- 美媒:补贴新政促电动汽车良性发展
- 1/4欧洲人想让AI替“人类做主”,被批“糟糕的想法”
- 口碑饿了么加大投入 推动本地生活服务数字化升级
- 中国式八大宽容都是哪些 中国式八大宽容分别是什么意思
- 澳前总理:我不信华为窃取信息,还很佩服华为
- 从医院端切入,斯录欣要做最懂医生的临床试验数据采集系统
- 首提盒区房覆盖率,盒马如何实现“全覆盖”小目标?
- 油轮救起移民却遭劫持 马耳他海军登船夺回
- 拼多多联合创始人达达:拼多多难
网友评论
推广链接
最新文章快读
文章随机推荐
- 冠生园前董事被猴子踢掉石头砸死事件详情介绍
- 普瑞丁左旋肉碱真假如何识别
- 东西林俊呈歌词 东西歌词是什么意思
- 《红海行动》登顶单日票房冠军 林超贤张译路演
- 我的宝贝四千金20集、21集、22集分集剧情介绍及预告
- “极挑团”寻找“先知”入“秘境” 屡次遭遇两难抉择
- 《夏至未至》第11-12集预告:傅小司欲向立夏表白 遇见李嫣然对掐
- 乔四玩过的女明星都有谁?乔四爷的全部女人照片 乔四死亡真相揭秘
- 时世艰难:泰国儿童拳赛
- 双十一来了 被种草了《一本好书》的“精神面包”?
- 环保部:进一步规范建设项目危险废物环境影响评价
- 武媚娘传奇中四贵妃最大的boss韦贵妃:武媚娘智斗四贵妃四贵妃如何死的
- 波音研发下代航天飞机:希望10天内10次往返太空
- 他来了请闭眼阮明淮被谁杀死的 苏北被怀疑是凶手
- 《黄飞鸿之怒海雄风》今日上线 赵文卓再续黄飞鸿经典形象
一周热门文章推荐
- 求职干货:再也不怕面试官问斐波那契数列了!
- 好暖!圆明园里一群游客悄悄捡树枝,原来是为了这一家子
- 戴岳云鹏面具抢劫怎么回事?女子为什么戴岳云鹏面具抢劫?
- 广东端州城区保洁面积涉嫌虚报 巡查组挽回2800多万元财政损失
- 百元连号钞票换“茅台”是大坑 消费者不明真相频被蒙骗
- 早报:雷军博鳌语录刷爆朋友圈 苹果CEO将上法庭
- “结婚4年备孕无果穿2月就怀上” 中脉内衣为何还在忽悠
- 联合国安理会讨论戈兰高地问题 14国反对美国决定
- 美一高中生因在校受欺凌拔刀刺伤同学被指控
- 英国议会堵死脱欧协议所有选项,媒体发问:脱欧要何去何从?
- 荷兰国花惨遭亚洲游客摧残,网友:应该拉起“警戒线”
- 酒驾最重死刑,台当局虚晃一招?
- 美媒:补贴新政促电动汽车良性发展
- 1/4欧洲人想让AI替“人类做主”,被批“糟糕的想法”
- 口碑饿了么加大投入 推动本地生活服务数字化升级