导语:给定一个用户ID, 如何查找这个用户的“最终推荐人”?
如何理解“递归”
问题
问: 看电影时想知道自己在第几排?
答: 问前一排,前一排再问前一排,直到问到第一排,再返回每一排位置,最终得到结果
解析: 去的过程叫“递”, 回来的过程叫“归”
递推公式
- 基本上,所有的递归问题都可以用递推公式来表示
f(n) = f(n - 1) + 1, 其中 f(1) = 1
代码实现
int f(int n)
{
if(n == 1) return 1;
return f(n-1) + 1;
}
递归需要满足的三个条件
一个问题的解可以分解为几个子问题(数据规模更小)的解
分解后的子问题,除了数据规模不同,求解思路完全相同
存在递归终止条件
如何编写递归代码
问题
n个台阶,每次你可以跨1个台阶或2个台阶,n个台阶有多少种走法?
方法
写出递归公式,找到终止条件
f(1) = 1;
f(2) = 2;
f(n) = f(n-2) + f(n-1);
代码实现
int f(int n)
{
if(n == 1) return 1;
if (n == 2) return 2;
return f(n-1) + f(n);
}
总结
- 将大问题分解为小问题的规律
- 写出递推公式
- 确定终止条件
- 将递推公式和终止条件翻译成代码
递归代码要警惕堆栈溢出
- 函数调用使用栈保存临时变量
- 函数执行完成返回时出栈
- 一直压入栈,会有堆栈溢出风险
如何避免出现堆栈溢出
- 限制递归深度
int f(int n){ ++depth; if (depth > 1000) { throw exception; } if (n == 1) return 1; return f(n-1) + 1; }
- 缺点:站空间无法确定,递归深度不可靠
递归代码要警惕重复计算
通过一个数据结构(比如散列表)来保存已经求解的结果f(k)
public int f(int n){
if (n == 1) return 1;
if (n == 2) return 2;
if (hasSolvedList.containsKey(n)) {
return hasSolvedList.get(n);
}
int ret = f(n-1) + f(n-2);
hasSolvedList.put(n, ret);
return ret;
}
时间复杂度:
- O(n)
- 无法确定,因为函数调用会有额外开销
空间复杂度:
- O(n),在递归结束之前,所有函数的变量都会被保存,所以空间复杂度为O(n)
递归代码改写非递归代码
Exapmle1
int f(int n)
{
if(n == 1) return 1;
return f(n-1) + 1;
}
等价于
int f(int n)
{
int ret = 1;
for (int i = 2; i <= n; i++) {
ret = ret + 1;
}
return ret;
}
Exapmle2
int f(int n)
{
if(n == 1) return 1;
if (n == 2) return 2;
return f(n-1) + f(n);
}
等价于
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;
}
递归调试方式
- 打印日志发现递归值
- 结合条件断点调试
解答开篇
如何找到最终推荐人
long findRootReferererId(long actorId) {
Long referrerId = select refrerrer_id from [table] where actor_id = actorId;
return findRootReferererId(referrerId);
}
存在问题
- 递归深度未知
- 无限递归 (A-B-C-A)
总结
- 满足三个条件即可递归
- 可拆分
- 可重复
- 有终止
- 递归三步骤
- 写出递归公式
- 找出终止条件
- 翻译递归代码
- 递归弊端
- 堆栈溢出
- 重复计算
- 函数调用耗时多
- 空间复杂度高
…
扩展
斐波那契数列(Fibonacci sequence)
定义
这个数列从第3项开始,每一项都等于前两项之和
实现
- 写出递归公式
f(n) = f(n-2) + f(n-1);
- 终止条件
f(0) = 0; f(1) = 1;
- 翻译递归代码
int Fibonacci(n){ if (n == 0) return 0; if (n == 1) return 1; //在return时打印即可打印所有数列 return f(n-2) + f(n-1); }
- 写出递归公式
详见: 黄铮的 数据结构与算法之美 课程系列