数据结构与算法---递归

如何理解递归

递归是一种应用非常广泛色算法。DFS深度优先搜索、前中后序二叉树遍历都会用到递归。递归对后续复杂算法的理解有很大帮助。

递归需要满族三个条件

  • 一个问题的解可以分解成为几个子问题的解
  • 这个问题与分解后的子问题,除了数据规模不同,求解思路完全一样
  • 存在递归终止条件

如何编写递归代码

关键两点:
写出递归公式
找到终止条件

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

例子:假如这里有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?
第一步有两种走法 1.走一步 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);
}

编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归代码要警惕堆栈溢出

递归求解的规模很大,不停的压栈,就会StackOverflow

怎么避免StackOverflow?
设置递归深度超过 k就报错结束递归


// 全局变量,表示递归的深度。
int depth = 0;

int f(int n) {
  ++depth;
  if (depth > 1000) throw exception;
  
  if (n == 1) return 1;
  return f(n-1) + 1;
}

但是这个方法并不能完全解决问题,因为最大允许的递归深度跟当前线程剩余的栈空间大小有关

递归代码警惕重复计算

在这里插入图片描述
这里f(3)被重复计算,怎么避免这个问题???
通过散列表来保存已经求结果的f(k),当遇到f(3)先检查散列表是否计算过


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 hasSolvedList.get(n);
  }
  
  int ret = f(n-1) + f(n-2);
  hasSolvedList.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;
}

是不是所有递归代码都可以改成非递归?
理论上是,可以手动模拟函数压栈实现,但是效率不一定提高,因为本质一样的
参考
https://www.cnblogs.com/bakari/p/5349383.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值