数据结构&算法模块总结
- (1)复杂度分析原理与方法
- (2)数组与链表原理和使用场景讲解
- (3)栈原理与应用场景讲解
- (4)队列原理与应用场景讲解
- (5)递归原理与虚拟机栈场景应用
- (6)二分查找及其应用场景
- (7) Redis有序集合跳表实现原理
- (8) 散列表(Hash Table)原理和业界应用场景
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.StackOverflowErrorint 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);}