面试经常遇到有人问菲波那切数列,并且问题也越来越刁钻,递归,非递归,尾递归等各种实现方式不一而足,已经不是最开始那个正经的斐波那契了。弄来弄去,还是要理解他的实现原理,以不变应万变。
数列形式:1,1,2,3,5,8,13·······求第n个数(n>=3)
这个数列的第n个数,等于前两个数的和,数列的前两个数为固定值,从第三个开始形成规律。现在说一下我对n的理解,很多网上的数列都是从0开始的,0,1,1,2,3······(n>=3),我觉着这个n的含义是第几个数,第0个数总感觉怪怪的,如果只是纯数学不管n的含义,那倒是随便。
正常递归:
public function test($n){
if($n<=0) return false;
if($n==1||$n==2) return 1;
return test($n-1)+test($n-2);
}
执行结果:n=35 结果:9227465 时间:16秒
尾递归:
function lastTest($n,$res1,$res2){
if($n==1) return $res1;
return lastTest($n-1,$res2,$res1+$res2);
}
//调用
echo lastTest($n,1,1)
执行结果:结果:9227465 时间:0.0000472069秒
先解释一下这俩个递归和结果为什么差距这么大:
第一:正常递归那个一次递归调用两次本身,类似于二叉树往下分裂,1生2,2生4,对于内存的消耗是指数级增长。而尾递归每次只是调用一次本身,内存消耗是线性增长。
第二:随便举个例子,求第6个数,正常递归:test(5)+test(4) 继续分裂 test(4)+test(3)+test(3)+test(2) 继续分裂:test(3)+test(2)+test(2)+test(2)+test(2) 继续分裂 test(2)+test(2)+test(2)+test(2)+test(2)+test(2),可以看到,相同的值他要进行多次重复运算。尾递归:每次算完的res1+res2的值都会赋给下一次循环的的res2,然后将res2赋给res1,不会进行重复运算。
假如计算第6个数,res1每次计算等于上次的res2,res2每次计算等于上次的res1+res2:
n | res1 | res2 |
6 | 1 | 1 |
5 | 1 | 2 |
4 | 2 | 3 |
3 | 3 | 5 |
2 | 5 | 8 |
1 | 8 | 13 |
我只计算到第35个数,是因为数再大正常递归已经超过php脚本默认执行时间30秒,再大很快内存就超了。这个算法要是运行在程序中简直就是毒瘤······
下边是非递归for循环(这两个for循环其实一样的就是一个递增一个递减):
正常循环:
function test($n){
if($n<=0) return false;
if($n==1||$n==2) return 1;
$res1 = 1;
$res2 = 1;
for ($i=3; $i <=$n ; $i++) {
$res = $res1+$res2;
$res1 = $res2;
$res2 = $res;
}
return $res;
}
执行结果:结果:9227465 时间:0.0000119209秒
也是正常循环(代码就改了一点点):
function test($n){
if($n<=0) return false;
if($n==1||$n==2) return 1;
$res1 = 1;
$res2 = 1;
for ($i=$n; $i >=3 ; $i--) {
$res = $res1+$res2;
$res1 = $res2;
$res2 = $res;
}
return $res;
}
执行结果:结果:9227465时间:0.0000119209秒
这俩个for循环的时间刚好一样,多点几次相差不大,我就用这两个时间了。尾递归那个很厉害了,竟然跟for循环一个量级,不过还是慢了4倍左右,多刷几次都是4倍左右,第一个就不说了。
所以这里边就牵扯到一个程序优化问题,面试的时候如果问你程序如何优化,我觉得尽量不用递归也算是很重要的一个优化手段。