数据结构与算法——递归

递归

1.递归的定义

1.1递归的定义

若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的。

若一个函数直接地或间接地调用自己,则称为这个函数是递归的函数。

在定义一个函数时出现调用本函数的语句,称之为递归。

1.2递归的类型

在C/C++程序中,每次调用函数时,系统都会为参数和局部变量分配新的存储空间,因此函数调用它自身是可能的。这种函数称为递归函数

当函数调用它自身时,这个过程称为直接递归

如果函数p调用q,而q又调用p,称为间接递归

如果递归函数中递归调用语句时最后一条执行语句,则称这种递归调用为尾递归

//求n!(n为正整数)的递归函数
int fac(int n)
{
    if(n==0||n==1)
        return 1;
    else
        return n*fac(n-1);
} 
//函数fac(n)直接调用fac(n-1)自身,是直接递归。
//递归调用是最后一条语句,又属于尾递归。

2.递归的实现过程 

2.1定义是递归的 

许多数学公式、数列等的定义是递归的。这些问题的求解过程可以将其递归定义直接转化为对应的递归算法

例如:

//斐波那契数列
int Fib(int n)
{
    if (n<=1)
        return n;
    else
        return Fib(n-1)+Fib(n-2);
}

 2.2数据结构是递归的

typedef int ElemType;//ElemType数据元素的数据类型
typedef struct LNode//LNode为结点类型名
{
    ElemType data;    //data代表数据元素
    struct LNode *next;//next为指向下一结点的指针
}LinkNode;            //单链表结点类型

递归数据结构的应用: 

对于递归数据结构,采用递归的方法编写算法既方便又有效。

//查找链表最后一个结点并打印其数值
void Find(Node *p)
{
    if(p->next==NULL)
        cout<<p->data<<endl;
    else
        Find(p->next);
}

//在链表中查找等于给定值的结点并打印其数值
void FindItem(Node *p,ElemType x)
{
    if(p!=NULL)
        if(p->data==x)
            cout<<p->data<<endl;
        else
            FindItem(p->next,x);
}

//求一个不带头结点的单链表值域(假设为int类型)之和
int Sum(Node *p)
{
    if(p==NULL)
        return 0;
    else
        return (p->data+Sum(p->next));
}

 2.3问题方法是递归的

汉诺塔(Tower of Hanoi)问题

任务:将塔座A上的圆盘移动到塔座C上。

规则:每次移动一个圆盘;圆盘只能在塔座A、B、C之间移动;大圆盘不能放在小圆盘上。

算法实现:

//n表示需要移动盘子的数量,X表示塔源,Y表示借用塔,Z表示目标塔
void Hanoi(int n,char X,char Y,char Z)
{
    if(n==1)
        cout<<X<<"->"<<Z<<"\t";
    else
    {
        //借助Z塔,将前n-1个盘子从X塔移动到Y塔
        Hanoi(n-1,X,Z,Y);
        
        //将X塔上剩下的1个盘子移到Z塔
        cout<<X<<"->"<<Z<<"\t";

        //借助X塔,将前n-1个盘子从Y塔移动到Z塔
        Hanoi(n-1,Y,X,Z)
    }
}

 求解过程:

2.4递归模型

递归模型是递归算法的抽象,反映一个递归问题的递归结构。

例如,阶乘递归算法对应的递归模型如下:

fun(1)=1                                   (1)

fun(n)=n*fun(n-1)      n>1         (2)

第一个式子是递归的终止条件,称为递归出口

第二个式子是fun(n)的值与fun(n-1)的值之间的关系,称为递归体

递归模型与执行过程

递归模型是由递归出口递归体两部分组成。

1.递归体:其表示“大问题”与“小问题”的关系。

2.递归出口:其为“最小问题”,可以直接求得结果。

递归的执行过程又分解求值两部分构成:

1.分解:其是指把“最大问题”逐步分解为“小问题 ”直至“最小问题”;

2.求值:其是指根据求得的“最小问题”的值重新代入递归体,最终的到“最大问题”的值

示例:

//递归求解3!的过程
int f(int n)
{
    if(n==0||n==1)//递归出口
        return 1;
    else
        return f(n-1)*n;//递归体
}

3.递归的实现

3.1递归调用的实现

递归函数的运行过程类似于多个函数的嵌套调用,差别仅在于“”调用函数和被调用函数是同一个函数“。

递归调用在内部实现时并不是把每次调用所需的代码都放入内存,而是采取代码共享的方式。

系统为每次调用开辟一组存储单元,用来存放本次调用的相关数据。

 3.2递归工作栈

为了保证”每一层的递归调用“都是对”本层“的数据进行操作,在执行递归函数的过程中需要一个”递归工作栈“。

”递归工作栈“的作用:

1.将递归调用时的实参和函数返回地址传递给下一层执行的递归函数;

2.保存本层的参数局部变量,以便从下一层返回时重新使用它们。

递归工作栈示例:

int fac(int n)
{
    int z;
    if(n<=1)
        z=1;
    else;
        z=n*fac(n-1);
    return z;
}

int main()
{
    int n=4;
    cout<<fac(n)<<endl;
    return 0;
}

 

4.递归的设计方法

4.1递归函数的一般设计格式

Recursion(参数表n)
{
    if(递归结束条件)//递归出口
        return (一般为常量);//可直接求解的步骤
    else
        return Recursion(较小的n);//递归项
}
//若递归路线为多条,则else分支不止一个。

示例:

//用递归的方法求数组中n个整数之和,设A为整数数组,从位置0到n-1放置n个整数
int sum(int n)
{
    if(n==1)
        reutrn A[0];//直接求解
    else
        return sum(n-1)+A[n-1];
    //求前面n-1个整数的和,再加最后一个整数A[n-1]
}

5.递归算法到非递归算法的转换

5.1递归算法的基本特性

1.分而治之,把复杂问题分解为简单问题。对求解某些复杂问题,递归算法十分有效;

2.时间效率通常比较差。求解某些问题时,先用递归算法分析问题,再用非递归算法具体求解问题。需要把递归算法转换为非递归算法

5.2转换方法

直接转化尾递归,可用循环结构的算法替代。

间接转化,需要使用:自己用栈模拟系统的运行时栈,通过分析只保存必须保存的信息。

间接转化,需要使用系统栈:利用系统栈保存参数。由于系统不了解程序的业务逻辑,仅从技术上转换,有可能保存不必要的信息。

6.总结

递归:易于理解、结构清晰;但可能需要进行大量系统栈操作,压入函数参数和返回地址,递归的层次于系统的栈大小受限有关。

非递归:效率比较高;但有时非常难以理解,在从递归向非递归转换的时候需要大量的模仿栈操作。

建议:

1.简单的问题尽量使用循环解决;

2.对于复杂的或者明显的递归思想则使用递归,这样易于理解和维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值