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

本文深入探讨了数据结构与算法中的递归原理,包括递归的三个基本条件、递归的缺点及解决方案,如栈溢出和重复计算。通过递归跳台阶问题举例,展示了如何利用散列表避免重复计算。此外,还讨论了在查找用户最终推荐人时可能遇到的死循环问题,并提出了解决方法。最后,提到了内存栈帧与栈深度对递归运算的影响,强调了正确理解和优化递归的重要性。
摘要由CSDN通过智能技术生成

  数据结构&算法模块总结

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值