目录
一、概述
递归严格说起来不算一种算法,只能说是一种问题处理思想或者问题处理技巧。只要问题同时满足以下三个条件,就可以考虑用递归来解决:
- 一个问题的解可以分解为几个子问题的解
- 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 存在递归终止条件
个人觉得,写递归代码最关键的是写出递推公式,找到终止条件,找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
二、示例分析
下面举一个例子,然后一步一步实现一个递归代码,帮你理解递归思想:
假如这里有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?
解析:
我们先假设n个台阶有x个走法,即x = f(n)。
对于第一步,我们有两种走法:
- 走1个台阶,剩余n-1个台阶。
- 走2个台阶,剩余n-2个台阶。
所以由此我们可以推导出f(n)=f(n-1) + f(n-2),递归格式我们已经推导出来了,下面我们再来分析一下终止条件:
随着我们不同走法,最后台阶要么剩下1个,要么剩下2个,如果是1个,很简单,下一步只能走1个台阶,即f(1)=1。如果剩余2个台阶,下一步我们有两个选择,即一次走两个台阶和两次每次走1个台阶,即f(2) = 2。
综上,递推公式为f(n) = f(n-1) + f(n-2)。
终止条件为:f(1) = 1或者f(2) = 2
有了递推公式和终止条件,我们就可以以此写出代码了,但在写递归代码时候,需要注意以下两点:
- 警惕堆栈溢出,如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。可以通过在代码中限制递归调用的最大深度的方式来解决这个问题。
- 要警惕重复计算,可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。
- 函数调用耗时多、空间复杂度高
参考代码如下:
int f(int n) {
if (1 == n||0 == n) {
return 1;
} else {
return (f(n - 1) + f(n - 2));
}
}
三、小结
递归是一种非常高效、简洁的编码技巧。只要是满足“三个条件”的问题就可以通过递归代码来解决。编写递归代码的关键就是正确姿势是写出递推公式,找出终止条件,然后再翻译成递归代码。递归代码虽然简洁高效,但是,递归代码也有很多弊端。比如,堆栈溢出、重复计算、函数调用耗时多、空间复杂度高等,所以,在编写递归代码的时候,一定要控制好这些副作用。