递归

递归

递归是一种常用的算法(或者是编程技巧),利用递归求解问题的时候,去的过程(一直向下向深处)叫做“递”,回来的过程(由下级深处运算携带结果返回过程)叫“归”。

时间复杂度:递归代码中多了很多函数调用,当函数调用数量较大时,时间成本还是挺高的

空间复杂度:每次递归调用都会在栈中保留一次临时变量,所以空间复杂度通常并不是O(1),可能是O(n)

递归需要满足的三个条件

  1. 一个问题的解可以分解成几个子问题的解
  2. 这个问题和分解之后的子问题,除了数据规模不同外,求解思路完全一样
  3. 有递归终止条件
编写递归代码

关键:写递归公式,找到终止条件,把问题抽象成一个递归公式,不用想一层层的调用关系,不用试图分解递归每个步骤

写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。

假如问题A可以分解为B、C、D,在写的时候可以站在B、C、D已经被解决了的角度去思考,而且,考虑关系的时候只需要考虑问题和子问题之间的关系,不用一层一层往下捋清关系(如考虑:子问题和子子问题的关系,子子问题和子子子问题的关系),屏蔽掉递归细节。

需要注意问题

可能导致堆栈溢出

栈主要是用来保存临时变量,堆是存放对象实例。当递归求解的数据规模较大,一直压入栈,就可能会导致栈溢出

解决办法:在代码中限制递归调用的最大深度。当递归调用超过一定深度之后,就不再继续递归了直接返回报错。

缺点:最大允许的递归深度和当前线程剩余的栈空间大小有关,事前无法计算。

可能存在重复计算

比如计算一个函数F(5)的值,需要计算F(4)和F(3),而计算F(4)仍需要计算F(3),因此F(3)就被重复计算了很多次,存在重复计算问题。

**解决办法:**可以通过一种数据结构(如:散列表、Map)来保存已经求解过的数据,在递归求解之前先判断散列表中是否有对应的结果,如果有就直接取出,没有则继续运算,并将运算结果保存在表中。

可能出现无限递归情况

如果出现脏数据,如:计算A的等于B结果,计算B的结果又等于A的结果,就容易出现死循环

递归代码改为非递归代码(迭代)

递归优点:表达力强,代码简洁

​ 缺点:空间负责度高、有堆栈溢出风险,存在重复计算、过多函数调用耗时较多

//递归
public int steps(int n){
    if (n == 1) return 1;
    if (n == 2) return 2;
    return steps(n - 1) + steps(n - 2);
}

//非递归(迭代)
public int steps2(int n) {
    if (n == 1) return 1;
    if (n == 2) return 2;

    int count = 0;
    int pre = 2;
    int prepre = 1;
    for (int i = 3; i <= n; i++) {
        count = pre + prepre;
        prepre = pre;
        pre = count;
    }
    return count;
}

几乎所有的递归都可以改写为迭代循环,因为递归本身就是借助栈来实现的,只不过我们使用的栈是系统或者虚拟机本身提供的,我们没有感知罢了。如果我们自己在内存堆上实现栈,手动模拟入栈、出栈过程,这样任何递归代码都可以改写成看上去不是递归代码的样子。

但是,将递归改为了“手动”递归,本质并没有变,而且也并没有解决前面讲到的某些问题,徒增了实现的复杂度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值