数据结构与算法-学习笔记(六)

递归

递归就是求解问题的一种固定编程方式。它的思路就是将问题一层层往外递出去,直到遇到出口后,再一层层向里回归答案。最终得出结果。而每次递出的问题还是用同样的方式解决。

递归需要满足的三个条件

什么样的问题可以用递归来解决呢?

  1. 一个问题的解可以分解成几个子问题的解,子问题解决了,大问题也就解决了:子问题(数据规模更小的问题)。例如:根据n-1时的解求n的解;分治问题。
  2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样。
  3. 存在递归终止条件:某个数据规模时可以直接得到答案了,不需要继续传递。也就是有了出口。
如何编写递归代码

找到递推关系(例:由n-1推出n),找到终止条件。

例如:有n个台阶,每次可以跨1个台阶或2个台阶,问走完n个台阶有多少种走法?

思路:开始走第一步时有两种走法:1个台阶或两个台阶。那么

n个台阶的总走法 = 以1个台阶开头的剩余n-1个台阶的走法 + 以2个台阶开头的剩余n-2个台阶的走法;

依次递推n-1个台阶的走法和n-2个台阶的走法都可以用上面的公式。

总结公式:f(n) = f(n-1) + f(n-2);

找到了递推公式,再找终止条件:当n=?时,可以直接得到具体数据。很容易想到当n=1时只有一种走法f(1) = 1然而根据?的递推公式,它是由两个条件推出来的,因此n=2时,不好再用递推公式,也要用作出口直接得出数据。

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);
}
复制代码
为什么用递归

当数据规模很大时,人脑可能无法一步步循环,一层层往下掉,在一层层返回,计算出问题的结果,但是计算机可以。计算机善于做重复的计算,所以递归正迎合了这点。

因此当思考一个问题A的解时,可以分解成若干子问题B、C、D,可以假设B、C、D都已经解决了,那么如何通过B、C、D的解获得A的解呢?具体的递归细节不用每一步都想清楚。

如?问题,如果我知道了n-1个台阶的走法和n-2个台阶的走法,那很容易就知道n个台阶的走法了。

递归代码要警惕堆栈溢出

根据前面所学的栈的知识,会发现递归函数很容易出现栈溢出的问题,所以当数据规模较大时,递归并不适用。

递归代码要警惕重复计算

有些数据重复计算了,解决的办法可以创建一个map来存储计算过的值。

public int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  // hasSolvedList 可以理解成一个 Map,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;
}
复制代码

除了以上两个常见问题,递归还有很多别的问题。

在时间效率上,递归代码多了很多函数调用;在空间效率上,每次调用都会在栈中保存一次数据,因此空间复杂度也上去了。

如何将递归代码改写为非递归代码呢?
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;
}
复制代码

这就是一个到这考虑的过程,由n=1算出n =2的解,再又2->3->4....->n,最终变成了循环的方式求解,和递归也差不多。

如何调试递归代码
  1. 打印日志发现递归值;
  2. 结合条件断点进行调试。
递归代码的时间复杂度

递归使用的场景:求解一个大问题a可以分解成求解多个子问题b、c。那么a的执行时间就应该等于子问题执行时间的合并结果。

T(a) = T(b) + T(c) + K // k等于将子问题结果合并的时间
复制代码

因此,不仅递归求解问题可以写递推公式,递推代码的时间复杂度也可以写成递推公式。

转载于:https://juejin.im/post/5bdabfd76fb9a0225f32a2fe

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值