递归( recursion):程序调用自身的编程技巧。
一、递归的概念
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
循环都可以改写成递归,递归未必能改写成循环
二、递归的实现步骤
递归是一种你理解了很简单用起来超方便,不理解感觉超困难的一种方法。
注:千万不要尝试在大脑中模拟递归的实现过程,你会感受到传说级别的痛苦,尤其是复杂度爆棚的问题
第一步:明确函数想要干什么
明确函数的功能是什么,从而确定函数的框架,包括函数名,参数,返回值
第二步:寻找边界条件或递归结束条件
找出当参数为啥时,递归结束,之后直接把结果返回
必须能根据这个参数的值,能够直接知道函数的结果是什么。
例如:当 n = 1 时,f(1) = 1。
找到条件后,为了更加严谨防止因漏掉某些特殊值而导致整个函数无法结束,应该仔细考虑边界条件的适用参数
第三步:找出函数的等价关系式
不断缩小参数的范围,来找到原函数的一个等价关系式
例如:假设f(n) 的等价关系式为 n * f(n-1),返回时就可以使用 n * f(n-1)。
注:等价关系式的寻找是三步骤之中最难的一个
三、实例解释
斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34....,即第一项 f(1) = 1,第二项 f(2) = 1....., 第 n 项为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。
1、递归函数功能
假设 f(n) 的功能是求第 n 项的值,代码如下:
int f(int n){
}
2、找出递归结束的条件
显然,当 n = 1 或者 n = 2 ,我们可以轻易着知道结果 f(1) = f(2) = 1。所以递归结束条件可以为 n <= 2。代码如下:
int f(int n){
if(n <= 2){
return 1;
}
}
第三要素:找出函数的等价关系式
由题目,能够知道 f(n) = f(n-1) + f(n-2)。
int f(int n){
// 1.先写递归结束条件
if(n <= 2){
return 1;
}
// 2.接着写等价关系式
return f(n-1) + f(n - 2);
}
四、递归的优化
如何优化?一般可以把计算的结果保存起来,例如,把 f(4) 的计算结果保存起来,当再次要计算 f(4) 的时候,我们先判断一下,之前是否计算过,如果计算过,直接把 f(4) 的结果取出来就可以了,没有计算过的话,再递归计算。
用什么保存呢?可以用数组或者 HashMap 保存,我们用数组来保存把,把 n 作为我们的数组下标,f(n) 作为值,例如,arr[n] = f(n)。f(n) 还没有计算过的时候,我们让 arr[n] 等于一个特殊值,例如 arr[n] = -1。
判断时,如果 arr[n] = -1,则证明 f(n) 没有计算过,否则, f(n) 就已经计算过了,且 f(n) = arr[n]。直接把值取出来就行了。
// 我们实现假定 arr 数组已经初始化好的了。
int f(int n){
if(n <= 1){
return n;
}
//先判断有没计算过
if(arr[n] != -1){
//计算过,直接返回
return arr[n];
}else{
// 没有计算过,递归计算,并且把结果保存到 arr数组里
arr[n] = f(n-1) + f(n-1);
reutrn arr[n];
}
}
五、能够解决的问题
-
数据的定义是按递归定义的。如Fibonacci函数。
-
问题解法按递归算法实现。如Hanoi问题。
3.数据的结构形式是按递归定义的。如二叉树、广义表等。