递归原理
函数调用栈实例:主函数 main() 调用 funcA() , funcA() 调用 funcB() , funcB() 再自我调用(递
归)
函数调用栈的基本单位是帧(frame)。每次函数调用时,都会相应地创建一帧, 记录该函数实例在二
进制程序中的返回地址(return address),以及局部变量、传入参数等, 并将该帧压入调用栈。若在
该函数返回之前又发生新的调用,则同样地要将与新函数对应的一帧压入栈中,成为新的栈顶。函数一
旦运行完毕,对应的帧随即弹出,运行控制权将被交还给该函 数的上层调用函数,并按照该帧中记录的
返回地址确定在二进制程序中继续执行的位置。
在任一时刻,调用栈中的各帧,依次对应于那些尚未返回的调用实例,亦即当时的活跃函数实例
(active function instance)。特别地,位于栈底的那帧必然对应于入口主函数main(), 若它从调用栈
中弹出,则意味着整个程序的运行结束,此后控制权将交还给操作系统。
此外,调用栈中各帧还需存放其它内容。比如,因各帧规模不一,它们还需记录前一帧的起始地址,以
保证其出栈之后前一帧能正确地恢复。
作为函数调用的特殊形式,递归也可借助上述调用栈得以实现。比如在上图中,对应于 funcB() 的自
我调用,也会新压入一帧。可见,同一函数可能同时拥有多个实例,并在调用栈中 各自占有一帧。这些
帧的结构完全相同,但其中同名的参数或变量,都是独立的副本。比如在 funcB() 的两个实例中,入
口参数 m 和内部变量 i 各有一个副本。
编写递归函数分析
分析:
什么样的问题, 能够写成递归函数?
1、下一步计算需要用到上一步结果
2、答案由多个值构成, 值之间的选择具有相关性
3、如何结束递归
4、如何传递递归(下一层递归)
例子一
递归求阶乘
计算阶乘(factorial)
n ! = { 1 n = 0 n ∗ ( n − 1 ) ! n > 0 n!=\begin{cases} 1 & n=0 \\ n*(n-1)! & n>0 \end{cases} n!={1n∗(n−1)!n=0n>0
code
/***
* 递归求阶乘
*/
public class A {
public static void main(String[] args) {
System.out.println(jie(3));
}
public static int jie(int n) {
if (n == 0) return 1;
else return n * jie(n - 1);
}
}
例子二
计算斐波那契数列
Fibonacci sequence:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ……
f
(
n
)
=
{
0
n
=
0
1
n
=
1
f
(
n
−
2
)
+
f
(
n
−
1
)
n
>
1
f(n)=\begin{cases} 0 & n=0 \\ 1 & n=1 \\ f(n-2)+f(n-1) & n>1 \end{cases}
f(n)=⎩⎪⎨⎪⎧01f(n−2)+f(n−1)n=0n=1n>1
/**
* 计算 斐波那契数列
**/
public class B {
public static void main(String[] args) {
System.out.println(shulie(3));
}
public static int shulie(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
else return shulie(n-1)+shulie(n-2);
}
}
例子三
计算最大公约数(辗转相除法)
gcd(12, 32) = 4
gcd(a, b)
gcd(32, 12)
gcd(12, 8)
gcd(8, 4)
gcd(4, 0)
g c d ( a , b ) = { a b = 0 g c d ( b , a % b ) b ≠ 0 gcd(a,b)=\begin{cases} a & b=0 \\ gcd(b,a \% b) & b\neq 0 \end{cases} gcd(a,b)={agcd(b,a%b)b=0b=0
public class C {
public static void main(String[] args) {
System.out.println(gcd(32,12));
}
public static int gcd(int a, int b) {
if (b == 0) return a;
else return gcd(b, a % b);
}
}
小结
上述递归例子十分明确都包含了递归结束的条件以及传递下一层(递归公式)