1

斐波那契数列

先来看一组数字: 1, 1, 2, 3, 5, 8, 13, 21, ...,大家发现什么规律了吗?
拆解:

21 = 13 + 8
13 =  8 + 5
 8 =  5 + 3
 5 =  3 + 2

没错,这就是斐波那契数列,第n项 = n-1项 + n-2项, 第1项和第2项的值为1


计算斐波那契的第30项的值

根据规律,我们第一想法就是递归,代码如下:

function Fib(num){
    if(num == 1 || num == 2) return 1;

    return Fib(num - 1) + Fib(num - 2);
}

console.log( Fib(30) ); // 832040

是不是感觉很简单,那如果计算Fib(100)呢?

你是不是在等待显示结果? 你的浏览器还有反应吗?赶紧把网页关了吧!


计算斐波那契的第100项的值

为什么浏览器卡死了呢? 因为100项的斐波那契递归,递归太深了,计算Fib(45)都要10s,所以我们还能用递归吗?

我们先来思考一下,计算Fib(100)的步骤

Fib(100) = Fib(99) + Fib(98)
 Fib(99) = Fib(98) + Fib(97)
 Fib(98) = Fib(97) + Fib(96)
 ...

我们是不是做了很多重复的事情,能想像Fib(3)被调用了多少次吗? 这就是卡死的原因。

所以,我们把计算过的项存下来,是不是就可以省掉很多重复的递归!

// 缓存
let deepCache = {};

function Fib(num){
    if(num == 1 || num == 2) return 1;
    
    // 如果计算过直接返回
    if(deepCache[num]) return deepCache[num];

    return deepCache[num] = Fib(num - 1) + Fib(num - 2);
}

console.time();
console.log(Fib(100)); // 354224848179262000000
console.timeEnd(); //  0.145751953125ms

我的天,刚才卡死,现在只需要0.1毫秒,这就是缓存的厉害之处

切记:如果递归函数存在重复计算,那么一定要使用缓存!

那这样是不是就无敌了呢? 试一试Fib(10000)

是不是出现了这个:
image.png

我的天,这...,栈溢出,怎么办...

递归的层级是有限的,所以遇到这种递归层级特别深怎么办?

不要方,我们知道,大部分递归都可以用循环实现!


计算斐波那契的第1000项的值

根据规律,我们可以用循环实现,代码如下:

function Fib(num){
    var fibN_1 = 1;
    var fibN_2 = 1;
    var result;

    for(var i=3; i<=num; i++){
        result = fibN_1 + fibN_2;
        fibN_2 = fibN_1;
        fibN_1 = result;
    }

    return result;
}

console.time();
console.log(Fib(10000)); // Infinity
console.timeEnd(); //  0.4091796875ms

所以,如果你看过浏览器源码的话,你会发现,很多方法都是用for循环代替递归实现的(比如: js的sort方法)

欢迎评论留言!


Eric
540 声望10 粉丝

热爱学习的骚年,一起成长吧!