菲波那切数列的案例(递归,闭包)

1,144 阅读3分钟

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,

// 斐波那契的案例
// 兔子数:
// 月份:1 2 3 4 5 6 7   8  9  10 11
// 数量:1 1 2 3 5 8 13 21 34  55 89

青铜

function fib(n){
    if(n === 1 || n === 2) return 1
    return fib(n - 1) + fib(n - 2)
}

白银(提高性能)

看一下性能怎么样?
let count = 0 // 计数 : 统计fib函数递归了多少次
function fib(n){
    count++
    if(n === 1 || n === 2) return 1
    return fib(n - 1) + fib(n - 2)
}
console.log(fib(40)); // 102334155
console.log(count); // 204668309

我去计算到40个月的时候count居然跑了204668309次,怎么办?

// 缓存: (cache), 是个存储数据的容器
// 在js里面如何去实现缓存: 用数组或对象来做缓存容器,使用键来存值,还可以通过键来取值,
// 可以实现即能存储值还可以取值。

// 使用缓存的思路:
// 1. 不是先去计算结果,先判断缓存中是否有对应的数据
// 2. 如果有,直接取出缓存中的对应数据
// 3. 如果没有,就去先计算,把计算结果存储到缓存中,方便下次使用

// 在这个案例中,如何去使用缓存
let cache = {
    // 把月份作为键,把月份对应的数量作为值
    // 1: 1,
    // 2: 1,
    // 3: 2, 
    // 4: 3
};

// 使用递归去计算兔子第10个月的数量
let count = 0 // 计数 : 统计fib函数递归了多少次
function fib(n){
    count++
    // 求出第n个月的兔子数量
    // 2. 结束
    if(n === 1 || n === 2) return 1
    if (cache[n]) {
        // 说明了: 缓存中有该月份的数量值,直接取出缓存中的数据
        return cache[n]
    } else {
        // 说明缓存中没有该月份的数量
        let ret = fib(n - 1) + fib(n - 2) // 计算的结果
        // 把结果存储到缓存中
        cache[n] = ret
        // 把结果给返回出去
        return ret
    }
}
console.log(fib(40));
console.log(count);

// 月份         优化前的次数            优化后的次数
// n = 10;    count = 109                   17
// n = 11;    count = 177                   19
// n = 12;    count = 287                   21
// n = 20;    count = 13529
// n = 21;    count = 21891
// n = 40;    count = 204668309             77

黄金(安全问题)

思考cache 暴露在全局中,如果有人修改了怎么办?

// 使用闭包来保护cache数据的安全
function outer(){
    // 在这个案例中,如何去使用缓存
    // 缓存里面的数据是计算得出的,我们应该充分信赖缓存里面的数据
    // 缓存里面的数据应该是绝对正确而且是不能被随意修改的
    let cache = {
        // 把月份作为键,把月份对应的数量作为值
        // 1: 1,
        // 2: 1,
        // 3: 2, 
        // 4: 3
    };

    // 使用递归去计算兔子第10个月的数量
    function fib(n){

        // 求出第n个月的兔子数量

        // 2. 结束
        if(n === 1 || n === 2) return 1

        if (cache[n]) {
            // 说明了: 缓存中有该月份的数量值,直接取出缓存中的数据
            return cache[n]
        }

        return cache[n] = fib(n - 1) + fib(n - 2)
    }

    return fib
}

let ret = outer()
console.log(ret(40))

总结

对于斐波那契案例使用到的技术点:

  1. 递归: 计算结果
  2. 缓存: 存储计算的数据
  3. 闭包: 保护缓存数据的安全