斐波那契数列之性能评测

726 阅读3分钟

斐波那契数列指的是这样一个数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...... 这个数列从第3项开始,每一项都等于前两项之和。

n=(n1)+(n2)n=(n−1)+(n−2)

递归

const fib1 = function(n) {
  if (n > 1) return fib1(n - 1) + fib1(n - 2);
  return n;
};
fib1(1000);  // 我的 MacPro 电脑跑不出结果 

ES6 递归

const fib1 = n => (n > 1 ? fib1(n - 1) + fib1(n - 2) : n);
fib1(1000);  // 和上面一样

为什么我的电脑跑不出来结果呢?

我把 n 换成了 10, 打印了一下 fib1 函数的递归调用次数, 打印次数是177次;

我又把 n 换成了 20, 打印了一下 fib1 函数的递归调用次数, 打印次数是21891次;

那如果 n = 1000, 需要调用多少次呢? 有知道的小伙伴在评论区告诉我哈。

刚才打印的时候,我发现有很多重复的调用, 比如 fib(1) 可能会调用很多次, 那么我们是否能缓存一下fib(1) 的值呢?

如果已经求过 fib(1) 的值了,我们会用一个 cache 变量缓存一下结果,下次调用的时候直接从缓存中取出来是不是就可以了!那么我们开始吧!

优化递归

const memozi = (_fib) => {
  const cache = {};
  return (n) => {
    if (cache[n] === undefined) {
      cache[n] = _fib(n);
    }
    return cache[n];
  };
};
const fib2 = memozi(n => n > 1 ? fib2(n - 1) + fib2(n - 2) : n);
fib2(1000);  //4.346655768693743e+208

我尝试使用缓存的方式优化了一下递归调用, 计算的结果是 4.346655768693743e+208

n = 10, 调用 11次

n = 20, 调用 21次

那么 n = 1000, 一定是调用了 1001 次。

那么,经过我们的优化之后,能快速的计算出 n = 1000的结果了, 计算耗时大概在 0.4ms 上下。

优化后的递归,计算速度已经很快了, 那么还有其他更快的方式吗?

for 循环

const fib3 = (n) => {
  let n1 = 0;
  let n2 = 1;
  let c = n1;
  for (let i = 1; i <= n; i++) {
    c = n1 + n2;
    n2 = n1;
    n1 = c;
  }
  return c;
}
fib3(1000);  //4.346655768693743e+208

这段代码使用了js 最基础的 for 循环实现, for 循环了 n 次, 计算耗时大概在 0.2ms 上下。

果然还是 for 循环最实用哈。

1000 次耗时2000 次耗时时间
递归
优化递归0.4ms0.65msO(n)
for 循环0.2ms0.22msO(n)

总结

今天我们学习了 3 种方式实现斐波那契数列求解的方式

  • 递归

    调用次数太多,性能问题严重, 不适合实际使用, 但是一种理解递归易懂的方式。

  • 优化递归

    缓存已调用的递归函数, 下次使用时, 直接返回递归值的结果, 性能不错。

  • for 循环

    使用变量赋值累加的方式, 把 n 赋值给 n - 1, 把 n - 1 赋值给 n - 2,遍历 n 次。

如果还有小伙伴知道比 for 循环更快的计算方式, 可以在下方的评论区跟我讨论哦。