# 数据结构与算法之美专栏笔记——递归篇

从一个问题开始

假设A的推荐人是B,B的推荐人是C,如何来查找A的最终推荐人呢?

这里直接给出答案——

long findRootRerfererId(long actorId){
    Long refererId = select referer_id from [table] where actor_id = actorId;
    if(refererId == null)
        return actorId;
    return findRootRerfererId(refererId);
}

递归概述

递归是一种应用非常广泛的算法,亦可称作编程技巧。

递归的引出

递归顾名思义,包含递与归两个过程。我们用下面的例子来体会一下。

假设你去电影院看电影,你想知道自己坐在第几排,所以你询问前排的人,但前排的人有相同的困惑,他又去问他的前排,直到第一排的人明确自己坐在第几排。然后每一排的人将结果告诉自己的后排,整个递归的过程就完成了。

这个小例子中,第一排是终止条件,不断问前排的人是递归条件。无疑是很典型的递归问题,但面对递归问题更通常的反应是看了全会,写了全废。所以我们还要按部就班的学习递归的知识。。。

递归的三个条件

  1. 一个问题的解可以分解为几个子问题的解
  2. 设个问题与分解后的问题除了问题规模不同外,求解思路完全相同
  3. 存在递归终止条件

如何编写递归代码

编写递归代码的核心无非就两个:递归公式+终止条件。我们通过下面的小例子来理解下——

假设有n个台阶,每次你自己可以跨越1个台阶或者2个台阶。请问走这n个台阶共有多少种走法?

我们会想到,可以按照第一步的走法将问题分解为两个子问题,也就是走一步之后的走法和走两步之后的走法,对应的递归公式就是
f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1)+f(n-2) f(n)=f(n1)+f(n2)
再来确定终止条件,显然,当只有一个台阶时就没必要再递归了,也就是:
f ( 1 ) = 1 f(1)=1 f(1)=1
将这个递归终止条件带入递归公式后,发现f(2)是无法确定的,所以终止条件还应该包含
f ( 2 ) = 2 f(2)=2 f(2)=2
表示的含义是走两个台阶有两种走法。最后我们将其转换为代码实现——

int f(int n){
    if(n == 1)
    return 1;
    if(n == 2)
        return 2;
    return f(n-1)+f(n-2);
}

站在巨人的肩膀——

写递归代码的关键在于找到将大问题转化为小问题的关键,并且基于此写下递归公式,然后再推敲递归终止条件,最终将递归公式和递归条件转化为代码实现。

递归代码要警惕堆栈溢出

递归代码在简洁可读性强的同时,却也带来了堆栈溢出的隐患。

之前提到过系统没调用一个函数,会将临时变量作为栈帧压栈,等到函数返回再弹栈。而递归代码因为调用的函数层数多,在函数调用时不断申请栈空间。从而导致空间复杂度不理想。

那怎样避免呢?理想的方法是根据场景选择递归,如果递归层数不多,则限制递归层数,这时使用递归是合适的。

递归代码要警惕重复计算

在刚才走台阶的例子中,就不可避免的遇到了重复计算的问题,这样对问题的求解并没有障碍,但并不优雅,我们要使用哈比表这样的数据结构动态存储每个子问题的结果。

一点思考

在问题规模较大时,有什么比较好的调试方式——

  • 打印日志发现递归值
  • 结合条件断点进行调试

日拱一卒,功不唐捐。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值