文章目录
斐波那契数列是计算机科学和数学中的一个经典问题,也是面试中经常被问到的算法题目。本文将详细介绍 JavaScript 中实现斐波那契数列的 8 种不同方法,并对它们的性能特点进行分析比较。
什么是斐波那契数列?
斐波那契数列是一个无限序列,其定义如下:
- F(0) = 0
- F(1) = 1
- F(n) = F(n-1) + F(n-2) (当 n ≥ 2 时)
数列的前几项为:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144…
1. 递归实现(最基础版本)
function fibonacciRecursive(n) {
if (n <= 1) return n;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
// 测试
console.log(fibonacciRecursive(10)); // 55
console.log(fibonacciRecursive(20)); // 6765
// console.log(fibonacciRecursive(50)); // 非常慢,可能导致堆栈溢出
特点:
- 代码简洁直观,直接反映数学定义
- 时间复杂度:O(2^n) — 指数级增长,极其低效
- 空间复杂度:O(n) — 由于递归调用栈
- 不适用于大数计算
2. 递归实现(带备忘录优化)
function fibonacciMemoization(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacciMemoization(n - 1, memo) + fibonacciMemoization(n - 2, memo);
return memo[n];
}
// 测试
console.log(fibonacciMemoization(10)); // 55
console.log(fibonacciMemoization(50)); // 12586269025
console.log(fibonacciMemoization(100)); // 354224848179262000000
特点:
- 使用备忘录缓存已计算结果,避免重复计算
- 时间复杂度:O(n) — 线性时间
- 空间复杂度:O(n) — 存储备忘录和调用栈
- 相比基础递归版本性能大幅提升
3. 迭代实现(循环)
function fibonacciIterative(n) {
if (n <= 1) return n;
let a = 0, b = 1, temp;
for (let i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return b;
}
// 测试
console.log(fibonacciIterative(10)); // 55
console.log(fibonacciIterative(50)); // 12586269025
console.log(fibonacciIterative(100)); // 354224848179262000000
特点:
- 使用循环代替递归
- 时间复杂度:O(n) — 线性时间
- 空间复杂度:O(1) — 常数空间,只需存储几个变量
- 性能优异,是最常用的实现方式之一
4. 动态规划实现
function fibonacciDP(n) {
if (n <= 1) return n;
const dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 测试
console.log(fibonacciDP(10)); // 55
console.log(fibonacciDP(50)); // 12586269025
console.log(fibonacciDP(100)); // 354224848179262000000
特点:
- 显式使用数组存储中间结果
- 时间复杂度:O(n)
- 空间复杂度:O(n) — 存储整个数组
- 比迭代版本占用更多内存,但更直观展示动态规划思想
5. 矩阵幂实现(数学优化)
基于矩阵幂运算的数学公式:
[ F(n) F(n-1) ] = [ 1 1 ] ^ (n-1)
[ F(n-1) F(n-2) ] [ 1 0 ]
function matrixMultiply(a, b) {
return [
[a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1]],
[a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1]]
];
}
function matrixPower(mat, power) {
let result = [[1, 0], [0, 1]]; // 单位矩阵
while (power > 0) {
if (power % 2 === 1) {
result = matrixMultiply(result, mat);
}
mat = matrixMultiply(mat, mat);
power = Math.floor(power / 2);
}
return result;
}
function fibonacciMatrix(n) {
if (n <= 1) return n;
const matrix = [[1, 1], [1, 0]];
const resultMatrix = matrixPower(matrix, n - 1);
return resultMatrix[0][0];
}
// 测试
console.log(fibonacciMatrix(10)); // 55
console.log(fibonacciMatrix(50)); // 12586269025
console.log(fibonacciMatrix(100)); // 354224848179262000000
特点:
- 基于数学矩阵运算
- 时间复杂度:O(log n) — 使用快速幂算法
- 空间复杂度:O(1)
- 理论上最优解,适合极大数计算
- 实现较复杂,常数因子较大,在普通应用中可能不如迭代版本快
6. 闭包实现(生成器模式)
function createFibonacciGenerator() {
let a = 0, b = 1;
return function() {
const temp = a;
a = b;
b = temp + b;
return temp;
};
}
// 测试
const generateFibonacci = createFibonacciGenerator();
console.log(generateFibonacci()); // 0
console.log(generateFibonacci()); // 1
console.log(generateFibonacci()); // 1
console.log(generateFibonacci()); // 2
console.log(generateFibonacci()); // 3
console.log(generateFibonacci()); // 5
// 可以无限调用生成数列
特点:
- 使用闭包保存状态
- 每次调用返回下一个斐波那契数
- 适合需要逐个生成数列的场景
- 时间复杂度:O(1) 每次调用
- 空间复杂度:O(1)
7. 生成器函数实现(ES6)
function* fibonacciGenerator() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 测试
const fibGen = fibonacciGenerator();
console.log(fibGen.next().value); // 0
console.log(fibGen.next().value); // 1
console.log(fibGen.next().value); // 1
console.log(fibGen.next().value); // 2
console.log(fibGen.next().value); // 3
console.log(fibGen.next().value); // 5
// 可以无限调用
特点:
- 使用 ES6 生成器函数
- 惰性求值,按需生成
- 代码简洁优雅
- 适合需要逐个获取数列的场景
8. 通项公式实现(Binet公式)
斐波那契数列的通项公式(Binet公式):
F(n) = (φ^n - ψ^n) / √5
其中 φ = (1 + √5)/2 ≈ 1.618(黄金比例)
ψ = (1 - √5)/2 ≈ -0.618
function fibonacciFormula(n) {
const sqrt5 = Math.sqrt(5);
const phi = (1 + sqrt5) / 2;
const psi = (1 - sqrt5) / 2;
return Math.round((Math.pow(phi, n) - Math.pow(psi, n)) / sqrt5);
}
// 测试
console.log(fibonacciFormula(10)); // 55
console.log(fibonacciFormula(20)); // 6765
console.log(fibonacciFormula(50)); // 12586269025
console.log(fibonacciFormula(70)); // 190392490709135
特点:
- 直接使用数学公式计算
- 时间复杂度:O(1) — 理论上
- 实际受限于浮点数精度,n较大时会有精度误差
- JavaScript中大约在n=75左右开始出现精度问题
- 适合中等规模n的计算
性能比较
下表比较了不同实现方式在计算F(40)时的性能表现(测试环境:Node.js 16.x):
方法 | 时间复杂度 | 空间复杂度 | 计算F(40)时间(ms) |
---|---|---|---|
基础递归 | O(2^n) | O(n) | ~1100ms |
备忘录递归 | O(n) | O(n) | ~0.05ms |
迭代 | O(n) | O(1) | ~0.02ms |
动态规划 | O(n) | O(n) | ~0.03ms |
矩阵幂 | O(log n) | O(1) | ~0.1ms |
Binet公式 | O(1) | O(1) | ~0.01ms |
最佳实践建议
- 一般情况:使用迭代法,它在代码简洁性、性能和内存使用之间取得了良好平衡
- 需要多次计算:使用备忘录递归,可以缓存之前的结果
- 极大数计算:考虑矩阵幂方法,虽然实现复杂但时间复杂度最优
- 按需生成数列:使用生成器函数或闭包实现
- 避免使用:基础递归实现,性能极差
完整示例代码
下面是一个完整的实现,包含所有方法并添加了性能测试:
// 1. 基础递归
function fibonacciRecursive(n) {
if (n <= 1) return n;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
// 2. 备忘录递归
function fibonacciMemoization(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacciMemoization(n - 1, memo) + fibonacciMemoization(n - 2, memo);
return memo[n];
}
// 3. 迭代
function fibonacciIterative(n) {
if (n <= 1) return n;
let a = 0, b = 1, temp;
for (let i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return b;
}
// 4. 动态规划
function fibonacciDP(n) {
if (n <= 1) return n;
const dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 5. 矩阵幂
function matrixMultiply(a, b) {
return [
[a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]],
[a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]]
];
}
function matrixPower(mat, power) {
let result = [[1, 0], [0, 1]];
while (power > 0) {
if (power % 2 === 1) {
result = matrixMultiply(result, mat);
}
mat = matrixMultiply(mat, mat);
power = Math.floor(power / 2);
}
return result;
}
function fibonacciMatrix(n) {
if (n <= 1) return n;
const matrix = [[1, 1], [1, 0]];
const resultMatrix = matrixPower(matrix, n - 1);
return resultMatrix[0][0];
}
// 6. 闭包生成器
function createFibonacciGenerator() {
let a = 0, b = 1;
return function() {
const temp = a;
a = b;
b = temp + b;
return temp;
};
}
// 7. ES6生成器
function* fibonacciGenerator() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 8. Binet公式
function fibonacciFormula(n) {
const sqrt5 = Math.sqrt(5);
const phi = (1 + sqrt5) / 2;
const psi = (1 - sqrt5) / 2;
return Math.round((Math.pow(phi, n) - Math.pow(psi, n)) / sqrt5);
}
// 性能测试函数
function testPerformance(fn, n, name) {
const start = process.hrtime.bigint();
const result = fn(n);
const end = process.hrtime.bigint();
console.log(`${name}(${n}) = ${result}, 耗时: ${(end - start) / 1000000n}ms`);
}
// 测试
const testNum = 40;
console.log('--- 性能测试 ---');
testPerformance(fibonacciRecursive, testNum, '基础递归');
testPerformance(fibonacciMemoization, testNum, '备忘录递归');
testPerformance(fibonacciIterative, testNum, '迭代');
testPerformance(fibonacciDP, testNum, '动态规划');
testPerformance(fibonacciMatrix, testNum, '矩阵幂');
testPerformance(fibonacciFormula, testNum, 'Binet公式');
console.log('\n--- 生成器测试 ---');
const generateFibonacci = createFibonacciGenerator();
console.log('闭包生成器前10项:');
for (let i = 0; i < 10; i++) {
console.log(generateFibonacci());
}
const fibGen = fibonacciGenerator();
console.log('\nES6生成器前10项:');
for (let i = 0; i < 10; i++) {
console.log(fibGen.next().value);
}
总结
JavaScript 实现斐波那契数列有多种方法,每种方法都有其适用场景:
- 学习递归:基础递归实现(但不要用于生产环境)
- 一般用途:迭代法或备忘录递归
- 高性能需求:矩阵幂方法
- 数列生成:生成器函数或闭包实现
- 数学探索:Binet公式实现
在实际开发中,迭代法通常是最佳选择,因为它具有优秀的性能(O(n)时间,O(1)空间)和代码简洁性。对于需要多次计算不同斐波那契数的场景,备忘录递归可以进一步提高效率。