前端面试题 - 91. 尾递归优化

183 阅读2分钟

尾递归优化是一种优化技术,它可以使得一些递归函数在被调用时不再占用额外的内存空间。在一个正常的递归函数中,每次递归调用都会创建一个新的堆栈帧,来保存当前函数的所有变量和参数。这个堆栈帧会一直存在,直到递归调用结束并返回值。

而尾递归则是一种特殊的递归形式,其中递归调用是在函数的最后一步执行的,并且其返回值是直接被当前函数所返回的。因此,递归调用的结果就可以覆盖当前函数的栈帧,从而避免了创建新的堆栈帧,减少了内存的使用。

一般来说,只有满足以下两个条件的递归函数才能够进行尾递归优化:

  1. 递归调用必须出现在函数的最后一步(即递归函数的返回语句之前)。
  2. 递归调用的返回值必须是当前函数的返回值。

以下是一个非尾递归的阶乘函数的示例:

function factorial(n) {
  if (n === 0) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

对于一个较大的 n,这个函数可能会导致栈溢出。为了避免这种情况,我们可以使用尾递归优化:

function factorial(n, acc = 1) {
  if (n === 0) {
    return acc;
  } else {
    return factorial(n - 1, n * acc);
  }
}

在这个版本的函数中,我们引入了一个额外的参数 acc,用来保存阶乘的积。每次递归调用时我们将 n 和它的积传递给下一个递归函数,这就避免了不必要的栈帧的创建。当递归结束时,我们返回 acc,就得到了最终的结果。

需要注意的是,尾递归优化并不是所有的编程语言都支持的特性。然而,JavaScript 在 ES6 版本中已经支持尾递归优化,但是需要在严格模式下才能启用。

以下是一个使用尾递归优化的阶乘函数的示例代码:

'use strict';

function factorial(n, acc = 1) {
  if (n === 0) {
    return acc;
  } else {
    return factorial(n - 1, n * acc);
  }
}

console.log(factorial(5)); // 输出 120

以上就是关于尾递归优化的问题和解法的详细解读和JS代码示例。

对于上面的阶乘函数,我们可以使用 while 循环来模拟尾递归优化。具体实现如下:

function factorial(n) {
  let acc = 1;
  while (n > 0) {
    acc *= n;
    n--;
  }
  return acc;
}

在这个版本的函数中,我们使用一个 while 循环来代替递归调用。每次循环时,我们将 n 和 acc 更新,并将 n 减 1。当 n 减到 0 时,循环结束,返回 acc。