【算法】JS实现斐波那契数列
斐波那契数列,又称黄金分割数列,是一种递归的数列,以它的递推公式和美丽的数学性质得到了广泛的应用。本文将介绍使用 JS 实现斐波那契数列的几种方法,包括递归和循环,以及优化方案等。
1. 斐波那契数列定义
斐波那契数列(Fibonacci sequence)是一种递归的数列,以它的递推公式和美丽的数学性质得到了广泛的应用。它的定义如下:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)。也就是说,第 n 个斐波那契数就是第 n-1 个和第 n-2 个斐波那契数之和。通常用 F(n)表示斐波那契数列中的第 n 个数。斐波那契数列是一个由 0 和 1 开始,后续的每一项都是前两项之和的数列。
2. 以终为始——递归实现
2.1 普通递归
对于普通递归我的总结就是:以终为始,套娃计算,适时终止。
- 以始为终:用倒推的方式来思考。
- 套娃计算:找到套娃的公式。
- 适时终止:递归到能直接得到结果的地方就停并返回结果。
下面我们来开始倒推。从后往前倒推就是 :
1 | f(n) = f(n-1) + f(n-2) |
我们可以得到:
- 公式:f(n) = f(n-1) + f(n-2)
- 终止条件:n=2 和 n=1
最终代码实现如下:
1 | function fibonacci(n) { |
JavaScript 代码运行时,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。
F(50)调用 F(49) 和 F(48),F(49)调用 F(48) 和 F(47),…
以此类推,所有的调用记录,就形成一个“调用栈”(call stack)。
如果栈深度过大,就会导致堆栈溢出。
- 测试一下你的调用栈
1 | let i = 0; |
2.2 缓存重复计算
上面提到fibonacci(50)
在浏览器中执行因为栈溢出会导致浏览器卡死的情况,让我们来优化一下。
观察下图我们不难发现,两个黄色实线圈和两个紫色的虚线的地方,都出现了重复计算,fibonacci(n)
中的 n 越大,重复计算的地方越多。如果我们能缓存每次计算的结果,就能减少重复计算,提升运行效率。
代码实现:
1 | function fibonacci(n) { |
2.3 避免重复计算(尾调优化)
尾调用优化是一种有效的优化技术,可以减少函数调用次数,从而提升递归函数的效率。它的原理是将当前函数的返回值作为另一个函数的参数,并将函数的调用放到最后(即尾调),从而减少函数调用和堆栈的深度。这样,就可以让函数能够在堆栈中以更少的深度运行,从而提高函数的效率。
1 | // 函数中多了两个参数不太优雅?传个参数默认值就可以解决! |
3. 手法优化——循环实现
3.1 普通for循环
使用普通的 for 循环实现斐波那契数列,只需要将前两个数字作为起始值,每次循环加上前两个数字的和,即可实现斐波那契数列。
1 | function fibonacci(n) { |
3.2 空间上的优化
就是省一个数组,如果只求第n个的值,也就没有必要用数组保存了。
1 | function fibonacci(n) { |
3.3 ES6解构赋值
上面的代码看的有些许难受,因为循环的时候引入了一个临时变量tmp
,其实我们用ES6中的解构赋值代码可读性会更好。
1 | function fibonacci(n) { |
3.4 ES6 Generator 函数写法
ES6 中的 Generator 函数是一种可以在函数调用时,暂停和恢复运行的函数。
Generator 函数返回的是一个迭代器,因此可以使用 for…of… 语法进行遍历。for…of… 循环可以自动遍历 Generator 函数运行时生成的 Iterator 对象,从而不需要再调用 next 方法。这样,就可以对 Generator 函数中 yield 生成的数据进行处理。
是不是发现了:这里使用 Generator 函数和 for…of… 来写斐波那契数列也挺合适。
1 | function* fibGenerator() { |