前尘篇----递归函数
归函数
递归函数(recursive function)就是方法自己调用自己。在直接递归种,递归函数 f 的代码包含了调用 f 的语句,而在间接递归中,递归函数 f 调用了 g , g 又调用了 h,如此进行下去,直到又调用了 f 。
递归的数学函数
数学中常有这样的函数,它自己定义自己。例如n的阶乘函数f(n) = n!,n为整数:
f(n) = ¦ 1 (n <= 1)
¦nf(n-1) n>1
当n小于或等于1时,f(n)的值为1,例如f(-3) = f(0) = f(1) = 1。当n大于1时,f(n)是递归定义的,因为右侧也有f。但这不会导致循环定义,因为右侧f的参数小于左侧f的参数。例如f(2) = 2f(1),因为f(1) = 1,所以f(2) = 2*f(1) = 2*1 = 2,依次类推,f(3) = 3f(2) = 3* (2 * f(1)) = 3* (2* 1) = 6。
假定f(n)是直接递归的。要使函数f(n)的递归定义有一个完全的形式,需要满足如下条件:
(1)有一个基础部分(base component),它包含n的一个或多个值,最这些值,f(n)是直接定义的,即不用递归就能求解。为简单起见,我们假定f的定义域是非负整数,基础部分包括0<= n <=k,其中k为非负常数。(n>=k 的情形也是有可能的,但是很少见)
(2)在递归部分(recursive component),右侧f有一个参数小于n,因此重复应用递归部分可以把右侧f的表达式转变为基础部分。
在示例公式中,基础部分是f(n) = 1,n<=1;递归部分是f(n) = nf(n-1),右侧f的参数是n-1,比n小,重复应用递归部分将f(n-1)变成f(n-2),f(n-3),……直到f(1),而f(1)属于基础部分,例如:f(5) = 5f(4) = 5*(4f(3)) = 5*(4 * (3*f(2))) = 5* ( 4 * (3 * (2 * f(1) ) ) ) = 120* f(1)。注意,递归部分的每一次都使得我们更加高考金基础部分。最后应用基础部分,我们得到了f(5) = 120。
递归的另一个实例是斐波那契数列:F0 = 0,F1 = 1, Fn = Fn-1 + Fn-2 (n>1) (公式1-2)
其中F0和 F1是基础部分,Fn = Fn-1 + Fn-2是递归部分,右侧的函数参数比n小。要是公式(1-2)成为完全递归形式,从n>1开始反复应用递归部分,每次n的值都要减去1或2,最终将右侧F的表达式转化为基础部分的表达式。
数学中的归纳证明
归纳证明是与递归函数有关的第二个概念。
在一个归纳证明中我们要证明如下函数成立:
使用C++递归函数
正确的递归函数必须要包含基础部分。每一次递归调用,其参数值都比上一次的参数值要小,从而重复调用递归函数使参数值达到基础部分的值。例如我们使用递归函数来计算阶乘,其基础部分是f(n) = 1 ,n = 1.递归部分是 f(n) = n*f(n-1), n>1.那么这个递归过程我们可以用如下递归函数完成:
int factorial(int n) { //计算n! if(n <= 1 ) return 1; //基础部分 else return n*f(n-1); //递归部分 即 f(n) = n*f(n-1) }
这里在计算机具体实现的时候,是利用了栈帧结构。我们来考虑一下递归的过程,假设我们计算了f(3),那么它的栈帧结构形貌大致如下:
f(3)的栈帧:f(3)的形参入栈,f(3)的局部变量入栈,f(3)的返回地址入栈(下一条指令的地址),f(3)的EBP入栈。(这时,形参的值没有达到基础部分的要求,那么递归调用f(2) |
f(2)的栈帧:f(2)的形参入栈,f(2)的局部变量入栈,f(2)的返回地址入栈,f(2)的EBP入栈,一样的形参没有达到基础笨的的要求,那么递归调用f(1) |
f(1)的栈帧:f(1)的形参入栈,f(1)的局部变量入栈,f(1)的返回地址入栈,f(1)的EBP如在,此时CPU在执行f(1)的代码时发行形参已经递归调用到了1,满足了基础本分的要求,那么此时f(1) = 1, f(1)退栈, |
递归调用到f(1),可以计算出f(1)的值后f(1)退栈,此时,这个递归函数的栈帧结构为:
f(3)的栈帧:f(3)的形参入栈,f(3)的局部变量入栈,f(3)的返回地址入栈(下一条指令的地址),f(3)的EBP入栈。(这时,形参的值没有达到基础部分的要求,那么递归调用f(2) |
f(2)的栈帧:f(2)的形参入栈,f(2)的局部变量入栈,f(2)的返回地址入栈,f(2)的EBP入栈,一样的形参没有达到基础笨的的要求,那么递归调用f(1) |
此时发(2)处于栈的最顶部,这意味着f(2)这个函数得到了被CPU执行的机会,f(2)在执行到return 2*f(1),这条指令时,CPU将从EAX中拿f(1)计算的结果1,这事f(2)就被计算完了return 2,并把结果保存在EAX寄存器中,然后将f(2)的栈帧退栈。此时这个递归体系的栈帧结果如下:
f(3)的栈帧:f(3)的形参入栈,f(3)的局部变量入栈,f(3)的返回地址入栈(下一条指令的地址),f(3)的EBP入栈。(这时,形参的值没有达到基础部分的要求,那么递归调用f(2) |
这个时候,f(3)得到了被CPU执行的机会,在执行到return 3*f(2)这一句时,CPU将从EAX寄存器中拿到上一次函数执行的结果2,那么f(3)的结果就是3*2=6,CPU将这个结果保存在EAX寄存器中,等待其它函数使用,此时清理f(3)函数的栈帧。至此,这个递归过程全部执行结束。
对于C++的递归函数来说,递归终止条件决定了这个递归调用链有多长。