斐波拉切数列

236 阅读2分钟

斐波拉切数列

指的是这样一个数列:0、1、1、2、3、5、8、13、21、34...,F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2).

在javascript中的计算方法有

  • 递归
    // 执行次数
    let count = 0;
    function fib(n) {
        // 每次调用均count++
        count++;
        if (n === 0) {
            return 0n
        }
        if (n === 1) {
            return 1n
        }
        return fib(n - 1) + fib(n - 2)
    }
    // 开始时间
    console.time()
    console.log(fib(45))
    // 结束时间,可以计算程序运行时间
    console.timeEnd()
    console.log(count)
    console.log(memory)

执行结果:
image.png
以上是递归的方式,是呈指数级的增长,主要是因为其中包含很多重复的计算

  • 记忆化搜索:自顶向下的思路
    // 执行次数
    let count = 0;
    // 记忆化的数组
    let memory = {};
    function fib(n) {
        // 每次调用均count++
        count++;
        // 初始化初值,n表示的是BigInt数据类型
        if (n == 0) {
            return 0n;
        }
        if (n == 1) {
            return 1n;
        }
        if (!memory[n]) {
            memory[n] = fib(n - 1) + fib(n - 2)
        }
        // 返回计算结果
        return memory[n]
    }
    // 开始时间
    console.time()
    console.log(fib(45))
    // 结束时间,可以计算程序运行时间
    console.timeEnd()
    console.log(count)
    console.log(memory)

执行结果
image.png
以上是自顶向下的方式,时间复杂度为O(n),计算了45 * 2 - 1 = 89次。

  • 动态规划:自底向上的思路
    // 执行次数
    let count = 0;
    // 记忆化的数组
    let memory = {};

    function fib(n) {
        // 初始化初值,n表示的是BigInt数据类型
        memory[0] = 0n;
        memory[1] = 1n;
        // 从第三个数据开始,均是前两个的加和
        for (let i = 2; i <= n; i++) {
            // 每次调用均count++
            count++;
            memory[i] = memory[i - 1] + memory[i - 2]
        }
        // 返回计算结果
        return memory[n];
    }
    // 开始时间
    console.time()
    console.log(fib(45))
    // 结束时间,可以计算程序运行时间
    console.timeEnd()
    console.log(count)
    console.log(memory)

执行结果
image.png
以上是底向上的方式,时间复杂度也为O(n),仅仅计算了45 - 1 = 44次。

动态规划

将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得源问题答案。

总结:

从递归,到记忆化搜索,最后使用动态规划,是一个逐渐优化的过程。