递归
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.对于复杂的或者明显的递归思想则使用递归,这样易于理解和维护。