“从前有座山,山里有座庙……”
递归就像这个故事——只要你不喊停,它就永远讲下去。
而斐波那契数列,就是那个最经典的“无限套娃”案例。
🌟 开场白:谁还没被 fib(100) 卡死过?
如果你刚学编程,第一次写斐波那契函数,十有八九会这样:
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
然后你兴冲冲地 console.log(fib(50)) ——
结果电脑风扇狂转,浏览器卡成PPT,最后弹出:“页面无响应”。
别慌,这不是你的错,是递归在“报复社会”。
今天,我们就来聊聊这个看似简单、实则暗藏玄机的斐波那契数列,以及如何用缓存+闭包把它从“时间黑洞”变成“闪电侠”。
🧠 一、递归:优雅但“烧钱”
斐波那契的定义人畜无害:
- f(0) = 0
- f(1) = 1
- f(n) = f(n-1) + f(n-2)
用递归实现?简直天作之合!代码短得能塞进微信昵称。
但问题来了:它的时间复杂度是 O(2ⁿ) !
想象一下,计算 fib(5) 时,你会重复计算 fib(3) 两次、fib(2) 三次……
到了 fib(100),你的电脑可能已经算到宇宙热寂了(夸张了,但差不多)。
更惨的是——调用栈爆炸!
JavaScript 引擎的调用栈深度有限,通常几千层就崩了。
所以 fib(10000)?别想了,直接报错:Maximum call stack size exceeded。
递归像极了爱情:看起来很美,用起来很痛。
💡 二、救星登场:记忆化缓存(Memoization)
既然重复计算是罪魁祸首,那我们就记住算过的值!
思路很简单:空间换时间。
用一个对象(比如 cache)把已经算过的 fib(n) 存起来,下次直接拿。
const cache = {};
function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) {
cache[n] = n;
return n;
}
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
}
这下 fib(100) 秒出结果!🎉
时间复杂度从 O(2ⁿ) 降到了 O(n) ,空间复杂度 O(n),稳如老狗。
但……全局变量 cache 有点丑,还可能被污染。
有没有办法把它“藏”起来?
🔒 三、闭包魔法:IIFE 来护驾!
这时候,立即执行函数表达式(IIFE) 就派上用场了。
我们用 IIFE 创建一个私有作用域,把 cache 关在里面,只暴露 fib 函数:
const fib = (function() {
const cache = {}; // 私有变量,外部无法访问
return function(n) {
if (n in cache) return cache[n];
if (n <= 1) {
cache[n] = n;
return n;
}
cache[n] = fib(n - 1) + fib(n - 2); // 注意:这里调用的是外层的 fib
return cache[n];
};
})();
这就是传说中的 “闭包缓存” ——
数据安全、代码整洁、性能拉满,三位一体!
而且,因为 cache 是自由变量,每次调用 fib 都能“记住”历史,真正做到一次计算,终身受益。
🎯 四、进阶思考:还有没有更快的?
当然有!比如:
- 动态规划(DP) :从底向上迭代,O(1) 空间。
- 矩阵快速幂:O(log n) 时间,适合超大 n。
- Binet 公式:用数学公式直接算(但有浮点精度问题)。
但对于大多数前端场景,带缓存的递归已经足够优雅又高效。
🤓 五、彩蛋:为什么叫“斐波那契”?
其实原名叫 Leonardo of Pisa,但他爸叫 Bonacci,所以他自称 Filius Bonacci(Bonacci 之子),缩写就成了 Fibonacci。
所以,“斐波那契”其实是“老邦家的儿子”……
听起来是不是瞬间接地气了?😄
✅ 总结:递归不是原罪,滥用才是
| 方法 | 时间复杂度 | 空间复杂度 | 是否实用 |
|---|---|---|---|
| 普通递归 | O(2ⁿ) | O(n) | ❌ 别用 |
| 记忆化递归 | O(n) | O(n) | ✅ 推荐 |
| 动态规划 | O(n) | O(1) | ✅ 更优 |
| 矩阵快速幂 | O(log n) | O(1) | ⚠️ 复杂场景 |
记住:
递归是思维的利器,缓存是性能的铠甲,闭包是封装的艺术。
下次再写斐波那契,别再让电脑“算到天荒地老”啦!