1 什么是递归
- 递归是一种非常高效,简洁的编码技巧,一种应用非常广泛的算法。比如 DFS 深度优先搜索、前后中序二叉树遍历等都是使用递归。
- 方式或函数调用自身的方式,称之为递,返回称之为归。
- 基本上,所有的递归问题都可以用递归公式来表示,例如:
f(n) = f(n-1) +1;
f(n) = f(n-1) + f(n+2)
f(n) = n * f(n-1);
为什么使用递归?
- 好处:代码表达力强,简洁。
- 坏处:空间复杂度高,有堆栈溢出风险,存在重复计算,过多的函数调用会耗时较多。
什么样的问题可以用递归解决?
如果一个问题同时满足以下 3 个条件,就可以用递归来解决:
- 问题的解可以分解成几个子问题的解,何为子问题?就是数据规模更小的问题。
- 问题和子问题,除了数据规模不同,求解思路完全相同。
- 存在递归终止条件。
如何实现递归
- 递归代码编写
- 写递归代码的关键在于,如果找到将大问题分解成子 问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递归公式和终止条件翻译成代码。
- 递归代码理解
- 对于递归代码,若试图搞清楚整个递和归的过程,实际上是进入了思维误区。
- 那改如何理解递归代码呢?
- 如果一个 A 问题可以分解成 B,C,D 子问题,那你可以假设子问题 B,C,D 已经被解决,而且,你只需要思考问题 A 和问题 BCD 两层之间的关系即可。不需要一层层往下思考子问题和子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样理解起来就简单多了。
- 因此,理解递归代码,就把他抽象成一个递归公式,不要想象成一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
递归常见问题及解决方案
- 警惕堆栈溢出:可以使用一个全局计数器控制堆栈的深度。
- 警惕重复计算:可以使用 Set 结构保存已经解开过的值,避免重复计算。
如何将递归代码改成非递归代码?
笼统的讲,所有的递归代码都可以改写成迭代循环的非递归写法。
如何实现?
抽象出递归公式、初始值,和边界条件,然后用迭代循环实现。
引用
《王争——数据结构和 算法之美》