不得不知的几种递归优化方式

201 阅读1分钟

准备

一个记录函数执行时间的函数

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
  • 优点:简洁、明了
  • 缺点:
    1. 随着 n 的增大,耗时呈现指数增长;
    2. 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)
}