前尘篇----递归函数

归函数

  递归函数(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++的递归函数来说,递归终止条件决定了这个递归调用链有多长。

 

posted on 2018-10-13 15:22  古I月  阅读(291)  评论(0编辑  收藏  举报

导航