如何规避深度递归导致爆栈问题

1,067 阅读2分钟

深度递归导致爆栈问题

斐波那契数列
function fib(n) {
  if ( n === 0) return 0;
  if ( n === 1) return 1;
  return fib(n - 2) + fib(n - 1);
}
fib(10); // 可以成功输出
fib(1000); // 爆栈
分析

上面的实现方式,使用的是递归调用的方式;每执行一次fib函数,都会创建自己的执行上下文,fib(n) = fib(n - 1) + fib(n - 2); fib(n - 1) = fib(n-2) + fib(n - 3); 如下图(图片借用修言大佬的数据结构和算法): 在执行fib(n)时调用了fib(n-1)和fib(n-2); 此时fib(n)的执行栈还存在,并没有释放,因为他的结果依赖于fib(n -1)和fib(n-2);以此类推,你会发现最先释放的调用栈时最小的数。如图所示: 打开我们的调试器,会发现call Stack中一直不断的在新增,直到最小数的求值完成后开始依次释放。如果n的值越大,那这个树就越复杂,执行栈就会爆。面对这样的问题我们应该怎么处理呢?我们有以下3种解决办法:

1.尾递归

每次调用函数时不生成新的运行栈,利用上一次的结果


/**
 * a充当收集器,收集上一次运行栈的返回值,之后栈空间会被回收
 * a和b参与每次的计算
 * n是斐波那契数列执行的次数
 */
function fibonacci(n, a, b) {
  if (n === 0) return 0;
  if (n === a || n === b) return 1;
  return fibonacci(n - a, a, b) + fibonacci(n - b, a, b);
}

2.动态规划思想,自上而下,非递归(迭代方式)

function fac(n){
    if(n == 1 || n == 2) return 1;
    a = 1;
    b = 1;
    res = 0;
    for(i = 3;i<=n;++i){
        res = a + b;
        a = b;
        b = res;
    }
    return res;
 }
3.记忆法,避免一些重复的计算

如图(图片借用修言大佬的数据结构和算法) 可以看出,红色部分的都是存在重复计算的,那怎么去除这些多余的计算呢,可以将一些计算过的结果存储下来。代码如下:

function fib(n) {
  const fn = [];
  fn[1] = 1;
  fn[2] = 2;
  for(let i = 3; i <= n; i++) {
    fn[i] = fn[i - 1] + fn[i - 2];
  }
  return fn[n];
}
深拷贝(DFS)

我们在实现对象的深拷贝时,我们首先会想到循环递归的方式去实现,例如下代码:

function deepCopyDFS(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return obj;
  }

  let res;
  if (Array.isArray(obj)) {
    res = [];
  } else {
    res = {};
  }

  for (const i in obj) {
    if (obj.hasOwnProperty(i)) {
      res[i] = deepCopyDFS(obj[i]);
    }
  }
  return res;
}
对象深拷贝(BFS)

但是为了性能考虑我们其实可以考虑用广度遍历的方式实现:

function deepCopyBFS(obj) {
  if (typeof obj !== 'object' || obj == null) return obj;
  const root = {};
  const stack = [{
    parent: root,
    key: undefined,
    data: obj
  }];

  while(stack.length) {
    const top = stack.pop();
    const parent = top.parent;
    const data = top.data;
    const key = top.key;

    let res = top.parent;
    if (typeof key != 'undefined') {
      res = parent[key] = {};
    }

    for (let i in data) {
      if (data.hasOwnProperty(i)) {
        if (typeof data[i] === 'object') {
          stack.push({
            parent: res,
            key: i,
            data: data[i]
          });
        } else {
          res[i] = data[i];
        }
      }
    }
  }

  return root;
}