斐波那契数列迭代、迭代、迭代解法

354 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

斐波那契数列

斐波那契数列是另一个可以用递归解决的问题。它是一个由0、1、1、2、3、5、8、13、21、34等数组成的序列。数2由1 + 1得到,数3由1 + 2得到,数5由2 + 3得到,以此类推。斐波那契数列的定义如下。

  • 位置0的斐波那契数是零。
  • 1和2的斐波那契数是1。
  • n(此处n>2)的斐波那契数是(n-1)的斐波那契数加上(n-2)的斐波那契数。

迭代求斐波那契数

我们用迭代的方法实现了fibonacci函数,如下所示。

function fibonacciIterative(n) {
  if (n < 1) return 0;
  if (n <= 2) return 1;

  let fibNMinus2 = 0;
  let fibNMinus1 = 1;
  let fibN = n;
  for (let i = 2; i <= n; i++) { // n >= 2
    fibN = fibNMinus1 + fibNMinus2; // f(n-1) + f(n-2)
    fibNMinus2 = fibNMinus1;
    fibNMinus1 = fibN;
  }
  return fibN;
}

迭代求斐波那契数

fibonacci函数可以写成下面这样。

function fibonacci(n){
  if (n < 1) return 0; // {1}
  if (n <= 2) return 1; // {2}
  return fibonacci(n - 1) + fibonacci(n - 2); // {3}
}

在上面的代码中,有基线条件(行{1}和行{2})以及计算n>2的斐波那契数的逻辑(行{3})。

如果我们试着寻找fibonacci(5),下面是调用情况的结果。

迭代斐波那契数

还有第三种写fibonacci函数的方法,叫作记忆化。记忆化是一种保存前一个结果的值的优化技术,类似于缓存。如果我们分析在计算fibonacci(5)时的调用,会发现fibonacci(3)被计算了两次,因此可以将它的结果存储下来,这样当需要再次计算它的时候,我们就已经有它的结果了。

下面的代码展示了使用记忆化的fibonacci函数。

function fibonacciMemoization(n) {
  const memo = [0, 1]; // {1}
  const fibonacci = (n) => {
    if (memo[n] != null) return memo[n]; // {2}
    return memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo); // {3}
  };
  return fibonacci;
}

在上面的代码中,我们声明了一个memo数组来缓存所有的计算结果(行{1})。如果结果已经被计算了,我们就返回它(行{2}),否则计算该结果并将它加入缓存(行{3})。

为什么要用递归?它更快吗

我们运行一个检测程序来测试三种不同的fibonacci函数。

迭代的版本比递归的版本快很多,所以这表示递归更慢。但是,再看看三个不同版本的代码。递归版本更容易理解,需要的代码通常也更少。另外,对一些算法来说,迭代的解法可能不可用,而且有了尾调用优化,递归的多余消耗甚至可能被消除。

所以,我们经常使用递归,因为用它来解决问题会更简单。