斐波那契 fibonacci 数列是经典的入门算法题,定义是:从第3项开始,前面相邻两项之和,构成了后一项。也就是,从第3项开始,每一项等于前面两项之和。
从定义看,最直观的就是递归,走一版:
版本1:
/**
* 计算斐波那契数列第n项的值
* @param {*} n
* @returns 第n项的值
*/
function fibonacci(n){
if(n<=1) {
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
}
const rest1 = fibonacci(10); // 89, 耗时约4毫秒
const rest2 = fibonacci(20); // 10946, 耗时约5毫秒
const rest3 = fibonacci(30); // 1346269, 耗时约13毫秒
const rest4 = fibonacci(40); // 165580141, 耗时约975毫秒
// 下面这个要慎重执行,会非常非常耗时
const rest5 = fibonacci(50); // 20365011074, 耗时约141740毫秒
从上述结果看到,随着参数(项数)的增加,到达一定的值后耗时将会相当长,因为需要递归调用的项是几何倍数增加的。
下面实现一版尾递归调用优化版本,理解上去会晦涩一点,但是性能会得到极大的提升,调用栈会极大的缩短。
版本2:
// 尾调用优化版本, ES6的实现都要求解释引擎底层实现尾调用优化。
// 在递归中使用尾调用优化,将会极大的提升递归的性能。
function fibonacci2(n, cur=1, total =1) {
console.log(`fibonacci2 :: enter, n = ${n}, cur = ${cur}, total = ${total}`);
if(n<=1) {
return total;
}
return fibonacci2(n-1, total, total+cur);
}
const rest21 = fibonacci2(50); // 20365011074, 耗时也只有14毫秒,性能极大提升。
const rest22 = fibonacci2(10); // 573147844013817200000, 耗时也只有26毫秒
还可以通过Generator遍历器生成函数来实现,也很有意思。也是效率更高的一个版本
版本3:
function* fibonacci3(n){
let [pre, cur] = [0, 1];
for(let index = 0; index <n ;index++) {
yield cur;
[pre, cur] = [cur, pre + cur];
}
}
function outputFib() {
const start = new Date().getTime()
const fibGen = fibonacci3(51); // 因为最后一项取不出来,因此这里参数加一个1
let total=1;
for(const rst of fibGen) {
total = rst;
};
console.log(`total = ${total}, duration = ${new Date().getTime() - start}`);
// total = 20365011074, duration = 1
}
outputFib();
// 耗时基本只需要1毫秒或者0毫秒!!!
// 及时将参数调整成100,也是1毫秒或者0毫秒
版本2和版本3,从实际耗时看,版本3耗时更短。
从时间复杂度分析看的话,版本2的时间复杂度是T(n) =O(n) ,版本3的时间复杂度也是T(n) =O(n),但是实际的时候,版本3会多一倍的循环,但是耗时还更短!!!
有点不可思议。个人觉得是因为版本2中,涉及更多的数学运算,就比较耗时。
以上测试数据在我的电脑上进行,我的机器配置:
CPU: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz 3.70 GHz
内存:16G