准备
一个记录函数执行时间的函数
function getRuntime(fn, n) {
const start = Date.now()
console.log(`输出:fibonacci(${n})=` + fn(n))
console.log('用时:' + ((Date.now() - start) / 1000) + 's')
}
一个递归
以斐波那契数列为例
function fibo(n) {
if (n === 1 || n === 2) return 1
return fibo(n - 1) + fibo(n - 2)
}
getRuntime(fibo, 43)
// 输出:fibonacci(43)=433494437
// 用时:7.69s
- 优点:简洁、明了
- 缺点:
- 随着 n 的增大,耗时呈现指数增长;
- 8G内存 n=48 时发生栈溢出,导致页面卡死
优化
1. 递归中加入缓存(Memorize)
原理:递归慢的原因有一大部分是由于重复计算导致的,这个方法通过一个数组 cached 记忆已经计算过的结果,避免重复计算从而加快速度,但是参数过大时仍然有爆栈的风险。
function fibo1(n) {
const cached = [0, 1, 1]
return (function fibonacci(n) {
let result
if (cached[n]) {
result = cached[n]
} else {
if (!cached[n - 1]) cached[n - 1] = fibonacci(n - 1)
if (!cached[n - 2]) cached[n - 2] = fibonacci(n - 2)
result = cached[n - 1] + cached[n - 2]
}
return result
})(n)
}
2. 循环替代递归
function fibo2(n) {
let first = 1
let second = 1
let result = 0
for (let i=2; i < n; i++) {
result = first + second
first = second
second = result
}
return n <= 2 ? 1 : result
}
递归其实是循环的简洁形式,但是递归需要记录之前变量环境,即压栈,因此存在栈溢出的问题。
3. 尾递归优化
function fibo3(n) {
function fib(first, second, secondIdx, targetIdx) {
if (targetIdx === secondIdx - 1) return first
if (secondIdx === targetIdx) return second
return fib(second, first + second, secondIdx + 1, targetIdx)
}
return fib(0, 1, 1, n)
}