一起养成写作习惯!这是我参与「掘金日新计划 · 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。
(此处
)的斐波那契数是(
)的斐波那契数加上(
)的斐波那契数。
迭代求斐波那契数
我们用迭代的方法实现了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}
)以及计算的斐波那契数的逻辑(行
{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
函数。
迭代的版本比递归的版本快很多,所以这表示递归更慢。但是,再看看三个不同版本的代码。递归版本更容易理解,需要的代码通常也更少。另外,对一些算法来说,迭代的解法可能不可用,而且有了尾调用优化,递归的多余消耗甚至可能被消除。
所以,我们经常使用递归,因为用它来解决问题会更简单。