递归
有两个最难以解的知识点,一个是动态规划,一个是递归
深度优先遍历,前中后序二叉树遍历都会用到递归算法
所有的递归问题都可以用递推公式来表示
f(n) = f(n-1)+1,其中f(1)=1
写递归代码最关键的是 写出递推公式,找到终止条件,剩下将递推公式转化为代码就很简单了
递归需要满足三个条件
- 一个问题的解可以分解成几个子问题的解
- 这个问题与分解后的子问题,除了数据规模不同,求解思路完全一样
- 存在递归终止条件
假设有n个台阶,每次可以跨1个台阶或者2个台阶,走这n个台阶有多少走法?
- 如7个台阶可以,2-2-2-1这样,也可以1-2-1-1-2这样
- 可以根据第一步的走法把 所有走法分两类,
- 第一类是第一步走了1个台阶,另一类是第一步走了2个台阶
- 所以,n个台阶的走法等于
- 先走1阶后 n-1个台阶的走法 + 先走2阶后 n-2个台阶的走法,公式为
- f(n) = f(n-1) + f(n-2)
- 终止条件是f(1)=1,f(2)=2
最后得到的结果是
int f(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return f(n-1) + f(n-2);
}
写递归代码的关键
- 找到如何将大问题分解为小问题的规律, 并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码
- 只要遇到递归,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑分解递归的每个步骤
注意事项
- 递归代码要警惕堆栈溢出
- 递归代码要警惕重复计算(下台阶问题,可以用hash表保存重复值
- 递归的空间复杂度很高
下台阶的整个计算分解过程如下图
下台阶改成非递归实现
int f(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
int ret = 0;
int pre = 2;
int prepre = 1;
for(int i=3;i<=n;i++) {
ret = pre + prepre;
prepre = pre;
pre = ret;
}
return ret;
}
笼统的讲,所有的递归代码都可以改写为迭代循环的非递归写法。
如何做?抽象出递推公式、初始值和边界条件,然后用迭代循环实现。
寻找最终推荐人
/**
寻找最终推荐人的方式
不过这段代码有问题
1.递归可能很深
2.脏数据情况下可能会死循环
更高级的处理方式,自动检测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);
}