(5)递归原理与虚拟机栈场景应用

  数据结构&算法模块总结

1.递归需要满足的三个条件


①一个问题的解可以分解为几个子问题的解

②这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样(子问题和父问题完全一样)

③存在递归终止条件

如经典递归跳台阶代码:

int f(int n) {

  if (n == 1) return 1;

  return f(n-1) + 1;

}

2.递归缺点


(1) 深度问题        

      递归会利用栈保存临时变量如果递归过深,会造成栈溢出。解决方案是控制递归的深度。

// f(1) = 1;
// f(2) = 2;
// f(n) = f(n-1)+f(n-2)
int f(int n) {
  if (n == 1) return 1;     //增加 递归终止条件
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

(2) 重复计算问题

  递归分解的子问题、子子问题可能存在相同的情况,如果都一一计算的话,就会发生重复计算。 解决方案是使用散列表来保存结算结果,每次开始计算前检查散列表是否已经有结算结果
    上面跳台阶问题的计算过程如下:

 可以看到有很多重复的f(n)值,如果把重复值记录下来,那么很多递归过程就可以不用进行了。

public int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  // key是n,value是f(n)。将重复结果缓存起来,避免重复递归
  if (hasSolvedList.containsKey(n)) {
    return hasSovledList.get(n);
  }
  
  int ret = f(n-1) + f(n-2);
  hasSovledList.put(n, ret);
  return ret;
}

(3)递归死循环“环”问题

        例如: 用户A推荐用户B来注册,用户B又推荐了用户C来注册。我们可以说,用户C的“最终推荐人”为用户A,用户B的“最终推荐人”也为用户A,而用户A没有“最终推荐人”。

        在数据库表中,我们可以记录两行数据,其中actor_id表示用户id,referrer_id表示推荐人id。因此, 给定一个用户ID,如何查找这个用户的“最终推荐人”?

    可能产生的问题:

  • 递归深(见上解决办法)

  • 如果数据库里存在脏数据,我们还需要处理由此产生的无限递归问题。比如demo环境下数据库中,测试工程师为了方便测试,会人为地插入一些数据,就会出现脏数据。如果A的推荐人是B,B的推荐人是C,C的推荐人是A,这样就会发生死循环,即 A-B-C-A“环”。

//原始代码
long findRootReferrerId(long actorId) {
  Long referrerId = select referrer_id from [table] where actor_id = actorId;
  if (referrerId == null) return actorId;
  return findRootReferrerId(referrerId);
}

//改进代码,缺点是内存占用量变大了(会最少)
long findRootReferrerId(long actorId, Set hasSolvedSet) {
  Long referrerId = select referrer_id from [table] where actor_id = actorId;
  if (referrerId == null) return actorId;
  if(hasSolvedSet.contains(referrId)) return ;  //如果已经处理过了则退出
  return findRootReferrerId(referrerId, hasSolvedSet.add(referredId));
}

【补充:内存栈帧和栈深度区别】

//如果栈帧中占用内存较大,那么栈的最大深度也会降低
//Exception in thread "main" java.lang.StackOverflowError
int f(int n) {
    ArrayList<Integer>list =new ArrayList<>();
    list.add(1);
    if (n == 1) return 1;
    return f(n-1) + 1;
}
main(String[] args) {
    f(10000);
}
//正常执行不报错
//原因分析:JVM默认单个线程栈的大小为512k。 当设置-Xss变大后,上面代码就可以执行了。由此可知,递归运算能否正常执行由
  栈帧+栈深度共同决定
int f(int n) {
        if (n == 1) return 1;
        return f(n-1) + 1;
    }
main(String[] args) {
    f(10000);
}

版权声明:本文为qq_35494772原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。