递归

什么是递归

递归的定义

在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。若调用自身,称之为直接递归。若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归
任何间接递归都可以等价的转换为直接递归。
如果一个递归过程或递归函数中递归调用语句是最后一句执行语句,则称这种递归调用为尾递归

例:设计求n!的递归算法。

int fun(int n)
{
    if(n==1)
        return(1);
    else
        return(fun(n-1)*n);
}

直接调用fun(n-1),所以是一个直接递归函数。且是最后一条语句,所以又属于尾递归。

能够用递归解决的问题应该满足以下三个条件:

  1. 需要解决的问题可以转换为一个或多个子问题求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同。
  2. 递归调用的次数必须是有限的。
  3. 必须有结束递归的条件来终止递归。

何时使用递归

在以下三种情况下,常常用到递归的方法。

1. 定义是递归的

有许多数学公式、数列等的定义是递归的。例如,求n!和Fibonacci数列等。可以将其递归定义直接转化为对应的递归算法。

2. 数据结构是递归的

有些数据结构是递归的。例如单链表,其结点类型声明如下:

typedef struct LNode
{
	ElemType data;
	struct LNode *next;
}LinkList;

结构体LNode的定义中,指针域next是一种指向自身类型的指针,所以它是一种递归数据结构。

对于递归数据结构,采用递归的方法编写算法既方便又有效。例如,求一个不带头结点的单链表L的所有data域(假设为int型)之和的递归算法如下:

int Sum(LinkList *L)
{
	if(L==NULL)
		return 0;
	else
		return(L->data+Sum(L->next));
}

例:分析二叉树的二叉链存储结构的递归性,设计求非空二叉链bt中所有结点值之和的递归算法,假设二叉链的data域为int型。

// 二叉树的结点类型定义如下
typedef struct BNode
{
	int data;
	struct BNode *lchild,*rchild;
}BTNode;
int Sumbt(BTNode *bt)
{
	if(bt->lchild==NULL && bt->rchild==NULL)
		return bt->data;
	else
		return (Sumbt(bt->lchild)+Sumbt(bt->rchild)+bt->data);
}

3. 问题的求解方法是递归的

典型的由Hanoi问题求解。
规则:每次只能移动一个盘片;盘片可以插在X,Y和Z中任一塔座;任何时候都不能将一个较大的盘片放在较小的盘片上。
将Hanoi(n,x,y,z)表示将n个盘片从x通过y移动到z上,递归分解的过程是:

Hanoi(n,x,y,z){
	Hanoi(n-1,x,z,y);
	move(n,x,z); // 将第n个圆盘从x移动到z
	Hanoi(n-1,y,x,z);
}

递归模型

递归模型是递归算法的抽象,它反映一个递归问题的递归结构
例如求n!的递归算法对应的递归模型:
fun(1) = 1 n=1 (1)
fun(n)=n*fun(n-1) n>1 (2)
(1)式给出递归的终止关系,(2)式给出fun(n)的值与fun(n-1)的值之间的关系,(1)式称为递归出口,(2)式称为递归体

一个递归模型是由递归出口递归体两部分组成。
递归出口的一般格式如下:

f(s1)=m1					

s1和m1均为常量,有些递归问题可能有几个递归出口。
递归体的一般格式如下:

f(Sn+1)=g(f(Si),f(Si+1),...,f(Sn),cj,cj+1,...,cm)

sn+1是一个递归的大问题,si,si+1,…,sn是递归的小问题,cj,cj+1,…,cm是若干个可以直接(用非递归)解决的问题,g是一个非递归函数,可以直接求值。

递归算法的执行过程

  • 递归程序虽然每次调用的是相同的子程序,但它的参量、输入数据等均有变化。
  • 随着调用的不断深入,必定会出现调用到某一层的函数时,不再执行递归调用而终止函数的执行,即遇到递归出口。
  • 递归调用时函数嵌套调用的一种特殊情况,即它是调用自身代码。
  • 由于每次调用时,它的参量和局部变量均不相同,因而也就保证了各个复制件执行时的独立性。
  • 系统为每一次调用开辟一组存储单元,用来存放本次调用的返回地址以及被中断的函数的参量值
  • 这些单元以系统栈的形式存放,每调用一次进栈一次,当返回时出栈,把当前栈顶保留的值送回相应的参量中进行恢复,并按栈顶中的返回地址,从断点继续执行。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    从以上过程可以得出:
  1. 每递归调用一次,就需进栈一次,最多的进栈元素个数称为递归深度,当n越大,递归深度越深,开辟的栈空间也越大。
  2. 每当遇到递归出口或完成本次执行时,需退栈一次,并恢复参量值,当全部执行完毕时,栈应为空。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 在递归函数执行时,形参会随着递归调用发生变化,但每次调用后会恢复为调用前的形参,将递归函数的非引用型形参的取值称为状态。
  • 递归函数的引用型形参在执行后会回传给实参,有时类似全局变量,不作为状态的一部分。

递归算法设计

递归与数学归纳法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数学归纳法是一种论证方法,而递归是算法和程序设计的一种实现技术,数学归纳法是递归的基础

递归算法设计的一般步骤

递归算法设计要先给出递归模型,再转换成对应的C/C++语言函数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

递归数据结构及其递归算法设计

1. 递归数据结构的定义

采用递归方式定义的数据结构称为递归数据结构。在递归数据结构定义中包含的递归运算称为基本递归运算
在这里插入图片描述
在这里插入图片描述

2. 基于递归数据结构的递归算法设计

1) 单链表的递归算法设计

在设计不带头结点的单链表的递归算法时:
设求解以L为首结点指针的整个单链表的某功能为“大问题”。
而求解除首结点外余下结点构成的单链表(由L->next表示,而该运算为递归运算)的相同功能为“小问题”。
由大小问题之间的解关系得到递归体。
在考虑特殊情况,通常是单链表为空或者只有一个结点时,这是很容易求解,从而得到递归出口。
在这里插入图片描述
在这里插入图片描述

2)二叉树的递归算法设计

二叉树是一种典型的递归数据结构,当一颗二叉树采用二叉链b存储时:
设求解以b为根结点的整个二叉树的某功能为“大问题”。
求解其左、右子树的相同功能为“小问题”。
由大小问题之间的解关系得到递归体。
再考虑特殊情况,通常是二叉树为空或者只有一个结点时,这是很容易求解,从而得到递归出口。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

递归算法设计示例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值