方法递归
方法递归是编程中一种常见且强大的技术,它允许一个方法在其定义内部调用自身。这种自我调用的过程使得方法可以重复执行,直到满足某个特定的结束条件。
递归方法通常用于解决那些可以分解为相似但规模更小的子问题的问题,如树的遍历、排序算法(如归并排序、快速排序)、图的搜索(如深度优先搜索DFS)、数学中的阶乘、斐波那契数列等。
递归的两个关键要素
-
基准情形(Base Case):这是递归的终止条件,它定义了在什么情况下递归调用应该停止,并返回一个值(可能是直接的结果,也可能是进一步计算所需的中间值)。基准情形是防止无限递归的关键。
-
递归步骤(Recursive Step):在递归方法中,除了基准情形外,还包含一个或多个递归调用,这些调用将问题分解为更小的子问题。递归步骤定义了如何根据问题的规模缩小问题,并调用自身来解决这些子问题。
递归的形式
递归可以通过多种形式实现,但主要可以归结为两种基本的思路:
直接递归:
直接递归是最常见的递归形式,其中方法直接调用自身来解决子问题。这种递归通常用于解决那些可以分解为相同类型但规模更小的子问题的任务,如阶乘计算、斐波那契数列等。
public int factorial(int n) {
if (n <= 1) { // 基准情形
return 1;
} else {
return n * factorial(n - 1); // 递归步骤
}
}
间接递归:
间接递归涉及到两个或多个方法互相调用对方,形成一个循环的调用链。这种递归在实际应用中较少见,因为直接递归通常更为直接和高效。间接递归更多是在理论探讨或特殊应用场景中出现。
// 假设有两个方法,A 和 B,它们相互调用对方
public void A() {
// 一些操作
B(); // 调用B
}
public void B() {
// 一些操作
A(); // 调用A,形成间接递归
}
但注意,如果A
和B
方法之间没有适当的基准情形来阻止无限递归,上面的代码将导致栈溢出错误。
案例导学-计算n的阶乘
分析
① 假设我们认为存在一个公式是f(n)=1*2*3*4*...(n-1)*n;
② 那么公式的等价形式就是:f(n)=f(n-1)*n;
图例
如果求5的阶乘,我们来手工演算一下过程。
递归算法三要素
•递归的公式:f(n)=f(n-1)*n
•递归的终结点:f(1)
•递归的方向必须走向终结点
代码:
过程:
例题一:
猴子吃桃问题:
猴子第一天摘下若干桃子,当即吃了一半。觉得好不过瘾,于是又多吃了一个。第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个。以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃一个。等到第10天的时候。发现桃子只有一个了。问猴子第一天摘了多少个桃子?
上面那个案例导学的启发。根据题意我们是不是可以找到桃子的数量与天数的关系?
也就是自变量是天数,我们设为x。因变量就是桃子的数量即:f(x)。
此时递归的终结条件,是不是f(10)=1。
现在我们需要找到天数与桃子数量之间的关系,根据题意:每天都是吃前天剩余桃子数量的一半,又多吃一个。
f(x) = f(x-1) / 2 - 1 。 每天吃的 = 前天剩余桃子的一半 - 1。
但是这个式子右边绝对不能作为返回结果。因为我们知道的是第10天桃子的数量,所以我们只能从后往前推。只有我们知道了后一天的吃桃子情况我们才能得到前一天的结果。
所以变形得:f(x) / 2 - 1=f(x+1)
也即:f(x)=2*f(x+1)+2;
代码:
public class Main {
public static void main(String[] args) {
int result=f(1);
System.out.println(result);
}
public static int f(int x){
if(x==10){
return 1;
}else {
return 2*f(x+1)+2;
}
}
}
最终结果:1534