数据结构(C语言版)-递归学习笔记

递归,介绍了解决某一类问题的思维方式。在一个函数定义中出现了对自己本身的调用,称为直接递归。一个函数p的定义中包含了对函数q的调用,而q的实现过程中又调用了p,即函数调用形成了一个环状调用链,这种方式称之为间接递归。

一个最简单递归程序设计的实例。

例子1 编写一个递归函数,以正整数n为参数,求n的阶乘值 n!

显然当 ,n!是建立在(n-1)!的基础上的。由于求解(n-1)!的过程与求解n!的过程完全相同,只是具体实参不同,因而在进行程序设计时,不必再仔细考虑(n-1)!的具体实现,只需借助递归机制进行自身调用即可。求n!(用Fact(n)表示)的具体实现过:

int Fact(int n)

{

    int m;

    if (n==0)

    {

        m = 1;

        return m;

    }

    else

    {

        m = n*Fact(n-1);

        return m;

    }

}

由以上实例可以看出,要使用递归技术进行程序设计,首先必须将要求解的问题分解成若干子问题,这些子问题的结构与原问题的结构相同,但规模较原问题较小。由于子问题与原问题结构相同,因而他们的求解过程相同,在进行程序设计时,不必再仔细考虑子问题的求解,只需借助递归机制进行函数自身调用加以实现,然后利用所得到的子问题的解组合成原问题的解即可;而递归程序在执行过程中,通过不断修改参数进行自身调用,将子问题分解成更小的子问题进行求解,直到最终分解成的子问题可以直接求解为止。因此,递归程序设计时必须要有一个终止条件,当程序的执行使终止条件得到满足时,递归过程便结束并返回;否则递归将会无休止的进行下去,导致程序的执行无法正常终止。

综上所述,递归程序设计具有以下两个特点:

(1)具备递归出口。递归出口定义了递归的终止条件,当程序的执行使它得到满足时,递归执行过程便终止。有些问题的递归程序可能存在几个递归出口。

(2) 在不满足递归出口的条件下,根据所求解问题的性质,将原问题分解成若干个子问题,子问题的求解通过以一定的方式修改参数进行函数自身调用加以实现,然后将子问题的解组合成原问题的解递归调用时,参数的修改最终必须保证递归出口得以满足。

递归的执行过程  

例子2  编写一个递归函数,以正整数n为参数,该函数所实现的功能是:在第一行输出一个1,在第2行输出2个2,在第3行输出3个3,…,在第n行输出n个n。例如,当n=5时,该函数的结果为

1

22

333

4444

55555

假设该函数采用print(n)表示。显然,print(n)所实现的功能只需在print(n-1)的基础上,再在第n行打印n个n即可;而实现print(n-1)的过程与实现print(n)的过程完全相同,只是参数不同而已,因而可以通过递归方式加以实现。在递归执行过程中,当n=0时递归应该终止。

具体算法:

void print(int  n)

{

    int i;

    if (n!=0)

    {

        print(n-1);

        for (i = 0; i < n;++i)

        {

            cout << n << '\t';

        }

        cout << endl;

    }

}

以下是n=5, print(5)的执行过程。

...

Print(5)的执行由(1)(2)两部分顺序组成,而(1)中print(4)的执行又由(3)(4)两部分顺序组成,(3)中print(3)的执行又由(5)(6)两部分顺序组成…直到(9)(10)。(9)中的print(0)执行时,由于n=0满足递归的终止条件,递归将不再继续下去,于是接下来执行(10),当(10)完成以后,意味着(7)中的print(1)完成了,于是执行(8),(8)的完成意味着(5)中的print(2)结束了,于是执行(6)…直到(2)完成后print(5)的执行才结束,函数调用最后的输出结果由(10)(8)(6)(4)(2)部分的输出结果顺序组成。

这个例子中实现的递归函数print(n)没有返回值,因此执行过程较简单。对有返回值的递归函数是怎么执行的呢?

例子3. 编写一个递归函数,以一个正整数n为参数,求第n项Fibonacci级数的值。

可知当 时,第n项的Fibonacci级数的值等于第n-1项和第n-2项Fibonacci级数的值相加之和,而第n-1项和第n-2项Fibonacci级数值的求解又分别取决于它们各自的前两项之和,总之,Fibona(n-1)和Fibona(n-2)的求解过程与Fibona(n)的求解过程相同,只是具体实参不同。利用以上这种性质,我们在进行程序设计时便可以使用递归技术。Fibona(n-1)和Fibona(n-2)的求解只需要调用函数Fibona 自身加以实现即可。

具体算法见算法:

int Fibona(int  n)

{

    int m;

    if (n==1 ||n==2)

    {

        return 1;

    }

    else

    {

        m = Fibona(n-1) + Fibona(n-2);

        return m;

    }

}

函数Fibona(5)的执行过程如图。

...

实线表示调用动作,虚线表示返回动作,虚线上所标的值为该次函数调用后带回的返回值。无论是实线还是虚线上均标有一个编号,表示递归程序执行过程中,所有动作发生的先后顺序;图中每一方框均表示一次新的调用,因而执行过程中它们使用的是不同的存储空间。

在计算Fibona(5)时,形参n=5,由于n>2,所以执行:

m = Fibona(n-1) + Fibona(n-2);

return m;

这一分支,也即相当于执行

m = Fibona(4) + Fibona(3);

return m;

本次调用动作在图中用标有(1)的实线段表示,其对应的语句执行序列存放于方框S1中。

为了求得方框S1中m的值,必须求出Fibona(4) 和 Fibona(3)的值。程序先求Fibona(4)的值,这是一次新的调用,必须重新为它的形参n、局部变量m和返回的地址分配存储空间,且此时n=4,这次调用仍然满足n>2,所以执行

m = Fibona(3) + Fibona(2);

return m;

此次调用动作体现为图中标(2)的有向实线段,其对应的语句执行序列存放于方框S2中。同样为了求得方框S2中m的值,必须求出Fibona(3)和 Fibona(2)的值。于是又执行Fibona(3)… 一直到某次调用能有一个确切的返回值,如图中有向虚线段(5),其返回值为1;接下来考虑方框S3中Fibona(1)的调用(体现为有向实线段(6)),此次调用直接返回值1(有向虚线段(7))。这时,方框S3中赋值语句m= Fibona(2) + Fibona(1)右端的两个函数调用均已分别返回了值1,于是可以求出m的值为2,然后执行方框S3中的return m,将m=2的值返回给方框S2中的Fibona(3),之后再求方框S2中的Fibona(2)的值,如此反复进行下去,直至最后求出Fibona(5)的值为5.

 

 

 

 

 

 

你可能感兴趣的:(数据结构)