前言
盗梦空间想象大多数人都看过: 电影讲述的是主人公诺兰进入希里安. 墨菲梦境植入想法的行动. 为了向希里安. 墨菲梦植入理念, 影片进入四层梦境, 即所谓:"梦中的梦中 梦中人的梦中".
有一对兔子, 每隔三个月会产下一对小兔子, 小免子每隔三个月, 也会产生新的一对免子, 问 36 个月后, 共有多少对兔子.
诸如此类: 其实就是 "递归", 今天就一起进入 "递归" 的世界看看
正文
一, 递归的定义
1. 递归是一种应用广泛的算法, 既能运用到软件开发中成为高效, 简洁的编码技巧也能应用到生活中解决实践递归问题, 比如 DFS 深度优先搜索, 前中后序二叉树遍历等, 又比如计算不断繁衍的后台个数等等;
2. 程序调用自身的方式称为递归调用, 去调用的过程称为递, 回来的过程称为归.
3. 基本上, 所有的递归问题都可以用递推公式来表示, 比如
- f(n) = f(n-1) + 1;
- f(n) = f(n-1) + f(n-2);
- f(n)=n*f(n-1);
二, 为什么使用递归?
1. 递归在解决某些问题的时候使得我们思考的方式得以简化, 代码也更加精炼, 容易阅读
2. 递归在处理问题时要反复调用函数, 这增大了它的空间和时间开销, 空间复杂度高, 有堆栈溢出风险, 存在重复计算, 过多的函数调用会耗时较多等问题.
三, 什么样的问题可以用递归解决呢?
一个问题只要同时满足以下 3 个条件, 就可以用递归来解决:
1. 问题的解可以分解为几个子问题的解. 何为子问题? 就是数据规模更小的问题.
2. 问题与子问题, 除了数据规模不同, 求解思路完全一样
3. 存在递归终止条件, 不能无限循环.
四, 如何实现递归
1. 递归代码编写
写递归代码的关键就是将大问题分解为小问题, 写出递推公式, 找出终止条件, 最后将递推公式和终止条件翻译成代码.
举一个栗子:
假如这里有 n 个台阶, 每一次你可以跨过一或二个台阶, 请问有几种走法?
根据第一步的走法把走法分为两类, 第一步走一个台阶或者走两个台阶, 所以 n 个台阶的走法就等于先走一阶的走法加上先走两个台阶的走法, 递归公式为:
f(n) = f(n-1)+f(n-2)
当只有一个台阶时, 我们就不需要递归了, 所以终止条件为:
f(1)=1
但是只有它还不足够, n=2 时, f(2)=f(1)+f(0) 还有 f(0)=1, 也就是第 0 阶也要有一种走法, 不和逻辑, 所以终止条件还有一个:
f(2)=2
编写为代码为:
- int f(int n) {
- if (n == 1) return 1;
- if (n == 2) return 2;
- return f(n-1) + f(n-2);
- }
2. 递归代码理解
对于递归代码, 若试图想清楚整个递和归的过程, 实际上是进入了一个思维误区.
那该如何理解递归代码呢? 如果一个问题 A 可以分解为若干个子问题 B,C,D, 你可以假设子问题 B,C,D 已经解决. 而且, 你只需要思考问题 A 与子问题 B,C,D 两层之间的关系即可, 不需要一层层往下思考子问题与子子问题, 子子问题与子子子问题之间的关系. 屏蔽掉递归细节, 这样子理解起来就简单多了.
因此, 理解递归代码, 就把它抽象成一个递推公式, 不用想一层层的调用关系, 不要试图用人脑去分解递归的每个步骤.
五, 递归常见问题及解决方案
1. 警惕堆栈溢出: 可以声明一个全局变量来控制递归的深度, 从而避免堆栈溢出.
代码实现:
- // 全局变量, 表示递归的深度.
- int depth = 0;
- int f(int n) {
- ++depth;
- if (depth> 1000) throw exception;
- if (n == 1) return 1;
- return f(n-1) + 1;
- }
2. 警惕重复计算: 通过某种数据结构来保存已经求解过的值, 从而避免重复计算.
如用散列表来保存已存在的值, 改写上面的代码如下:
- 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;
- }
来源: https://www.cnblogs.com/clwydjgs/p/9810969.html