前言:
这一篇博客记录一下求斐波那契数列第n项值得几种方法,用到了递归和迭代的方法,所以首先我们来区分一下递归和迭代,再来记录几种方法。
递归和迭代
联系
递归与迭代都是循环的一种。
区别
1、程序结构不同
- 递归是重复调用函数自身的循环。
- 迭代是函数内部代码的循环。
- 注意:迭代和普通循环的区别:迭代时,循环代码中参与计算的变量也是要返回的结果,当前保存的结果作为下一次循环计算的初始值。
2、算法结束方式不同
- 递归是在遇到满足终止条件的情况时结束循环。
- 迭代是使用计数器结束循环。
- 注意:很多情况会采用多种循环混合采用。
3、效率不同
- 在循环的次数较大时,迭代的效率明显高于递归。
求斐波那契数列第n项值
递归方法:
思想:
利用斐波那契数列计算公式f(n) = f(n-1) + f(n-2),递归调用函数自己。时间复杂度为O(2^n)。
代码:
// 斐波那契数列--递归:O(2^n)
function fib(n) {
if(n<2) {
return n;
}
return fib(n-1) + fib(n-2);
}
console.log(fib(8));
注意:
此段代码中if(n<2) {return 0;}其实不够严谨,因为这样的话即使传入参数n为负数(如-1)也会返归n,在没有严格规定实参必须为非负整数的情况下,最好还是写成if(n == 0) return 0; if(n == 1) return 1;。
递归方法优化:
思想:
上述递归的方法,每一次执行到f(n-1)和f(n-2)时都要重新计算一遍,还要开辟新的空间来保存它们,比较浪费空间,可以优化为:开辟一个缓存区,存放计算过的每一个值,下一次用到时直接从缓存区查找,找不到则再计算,如此,可以减少空间上的浪费。
代码:
// 优化递归--结构体当缓存区
function fib1(n) {
const cache = {
'0': 0,
'1': 1
}
function helper(n) {
if(n in cache) return cache[n];
const res = helper(n-1) + helper(n-2);
cache[n] = res;
return res;
}
return helper(n);
}
console.log(fib1(8));
迭代方法:
思想:
此方法的思路是将每一项计算出来的f(n)存入数组,使用迭代的方法,每一次迭代完成,数组中就多了一项元素。
此处存疑?
上面说到,迭代是循环中参与计算的变量就是要返回的变量,而我的代码这里参与计算的是数组,返回的却是数组的第n项,不知道这个算不算是正宗的迭代。
代码:
// 迭代:O(n)
function fib2(n) {
let arr = [0, 1];
for (let i = 2; i<=n; i++) {
arr.push(arr[i-1] + arr[i-2]);
}
return arr[n];
}
console.log(fib2(8));
迭代方法优化:
思想:
上一段代码中,随着n的值的增大,数组会无限变长,而我们知道斐波那契数列是无穷的,所以当n值很大时,就对空间浪费巨大。反正计算新的值时也就只会用到它的前两个值,每一次从末尾推入一个新元素,就同时从开头删去一个元素,让数组的长度始终只用保存两个元素,大大减少了空间的使用。
代码:
// 优化迭代--保持数组长度总是为2,节约空间
function fib3(n) {
let arr = [0, 1];
if(n == 0) return 0;
if(n == 1) return 1;
for (let i = 2; i<=n; i++) {
// 分成两步实现
/* // 将0号元素从数组删除
let one = arr.shift();
// 添加新的元素
arr.push(one + arr[0]); */
// 一步实现
arr.push(arr.shift() + arr[0]);
}
return arr[1];
}
console.log(fib3(0));