递归和迭代_详解递归

0d2a3dba72ce0bc3d7ba27966bf9031a.png

835abccb0428f72b8758161bd132b881.png认识递归

1.1 定义与分类

在定义一个过程或函数时,出现直接或者间接调用自己的成分,称之为递归。

(1)若直接调用自己,称为直接递归;若间接调用自己,则称为间接递归,示意图如下。

7c209b4944183f366a9e2ee6e73ab1d2.png

直接递归

ebc1fab323f09e19566e988c91bb30f0.png

间接递归

(2)如果一个递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归,如上面的求n!(直接递归)。

1.2 何时使用递归

(1)定义是递归的。有许多数学公式、数列等的定义是递归的,如上面的求n!(直接递归),二叉树的定义也是递归的。

(2)数据结构是递归的,如单链表:

f44eb7261e78633b627ad2aaf34299f2.png

(3)问题的求解方法是递归的,如最著名的Hanoi塔问题。

1.3 递归模型

(1)建立模型

为了更好的利用递归求解问题,我们现在通过求n! 递归算法认识递归结构,并抽象出递归模型。

0bc9f5ef1b95b81bd398b65786317872.png

一般地,一个递归模型是由递归出口和递归体两部分组成。递归出口确定递归到何时结束;递归体确定递归求解时的递推关系。如此,我们类推出递归出口和递归体的一般格式:

1)递归出口,一般格式如下:f(s1) = m1

  其中,s1m1均为常量,有些递归问题可能有几个递归出口。如求n!,fun(1)=1就是f(s1) = m1

2)递归体,一般格式如下:f(sn)=g(f(si),f(si+1),…,f(sn-1),cjcj+1,…,cm)。

  其中,g是一个非递归函数,cjcj+1,…,cm为常量。如求n!,fun(n)=n*fun(n-1)(n>1)就是f(sn)=g(f(si),f(si+1),…,f(sn-1),cjcj+1,…,cm),只是“cjcj+1,…,cm”均为0,“f(si),f(si+1),…,f(sn-1)”中仅存在一个fun(n-1),此外f(sn)为f(n),g为n*fun(n-1)

(2)递归思路

在递归模型中,我们可以看到递归体等式左边的f(sn) 与等式右边的“f(si),f(si+1),…,f(sn-1)”格式一样,其代表了如下的含义:

c9f8dd8dc2e20466fcac322d18dbb4c5.png

递归的思路就是:把一个不能或不好直接求解的“大问题”转化成一个或几个与“大问题”相似的“小问题”来解决;再把这些“小问题”进一步分解成更小的相似的“小问题”来解决。我们现在以求6!为例,看一下递归问题的求解过程。

1fafe7550fe80231e25cdd2114e2c0ad.png

835abccb0428f72b8758161bd132b881.png递归与栈和迭代

2.1 递归与栈

在前边讲解递归思路时,我们用图大致分解了一下6!的求解过程,那么这个过程具体是通过什么技术实现的呢?函数执行是通过操作系统中的栈(下文简称为系统栈)实现的。

之前所讲栈,是数据结构中的概念,是一种数据结构,是限定仅在表尾进行插入或删除操作的线性表,而系统栈是计算机系统中的概念,它们都具有先进后出的特点。一个进程使用的内存都可以按照功能大致分为代码区、数据区、堆区和栈区4个部分,内存的栈区实际上指的就是系统栈。系统栈由系统自动维护,它用于实现高级语言中函数的调用(即栈用于维护函数调用的上下文,离开了栈函数调用就没法实现)。因为函数调用经常是嵌套的,在同一时刻,栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧,即系统栈又分为若干个栈帧,栈帧记录在栈上面,栈上保持了N个栈帧的实体。每个函数执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、函数出口等信息,每一个函数从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程,且当前正在运行的函数的栈帧总是在栈顶,体现先进后出的特点!依旧以6!的求解过程为例,如下动图演示通过栈帧实现递归求解算法。

fb757459a4741db776d382111f13dbf7.gif

2.2 递归与迭代

(1)递归常被用来描述以自相似方法重复事物的过程,形象的说就是A调用A,在函数的定义中直观的表现为:直接或间接调用函数自身;(2)迭代则是重复反馈过程的活动,是利用已知的变量值,根据递推公式不断将每一次迭代的结果作为下一次迭代的初始值,从而得到变量新值的一种编程思想,其形象的说是A重复调用B,在函数内直观的表现为:某段代码实现循环。接下来就以著名的斐波那契数列来举例看递归和迭代的不同实现方式,斐波那契数列:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

dbaa4b1817f177368530183bad17a488.png

那么,迭代与循环又该如何区分呢?迭代是利用循环语句实现重复反馈过程的活动,而循环,单纯是实现反复执行某个功能,以减少重复书写。那么看到包含循环语句的代码时,该如何判断其是否是迭代呢?迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。

835abccb0428f72b8758161bd132b881.png递归算法转非递归

3.1 尾递归算法转非递归

通常尾递归算法可以通过循环或者迭代方式转换为等价的非递归算法,如求n!。

b92fa2976daa336eefaa6e556fe73efe.png

3.2 非尾递归算法转非递归

非尾递归算法,在理解递归调用实现过程的基础上,可以用栈模拟递归执行过程,从而将其转换为等价的非递归算法。例如二叉树的后序遍历,可以使用递归算法实现,也可以通过栈实现后序非递归算法,算法具体实现将在二叉树遍历时讲解。

835abccb0428f72b8758161bd132b881.png递归算法的设计

在遇到1.2中所述的三类问题时,我们可以使用递归思想设计问题的求解算法。但是,像1.2中的“(3)问题的求解方法是递归的”,该如何判断谁是这类问题呢?这就要用到1.2中的递归思路和递归模型。

判断是否可以将问题进行“大问题”转化成一个或几个与“大问题”相似的“小问题”,而“小问题”又可以继续转换成更小的相似的“小问题”,直至某个粒度的“小问题”有对应的值(即递归出口)来停止转换的逐步分解。若可以分解,就代表可以使用递归思路求解此“大问题”,剩下就是根据建立的递归模型设计递归算法。

具体步骤:(1)对原问题(即“大问题”)进行分析,找出相似的合理的 “小问题”;(2)对此“小问题”按相同的方法继续进行转换,直至某个粒度的“小问题”是可解的;(3)如此,既可根据“大问题”与“小问题”之间的关系,给出递归体,并根据有解的这个粒度的“小问题”(即特定情况的解,如f(1)或f(0)),给出递归出口;(4)根据递归模型,设计出求解原问题的递归算法。如以孩子兄弟链作为树的存储结构,设计一个求树t高度的递归算法。

46333b72af8d6bbf95ac2007883e793d.png

设f(t)为树t的高度,根据上图可得到如下递归模型:

递归出口:f(t) = 0        (当t=NULL时);

递归体:f(t) = MAX{f(p)}+1  (其它情况,其中p是指向t的孩子)。END 0a5bf4313f9cb05234333f3078f8efc2.png长按 关注轻松学习 2887bac50980a58642f0683324906c28.gif 给个“赞”和“在看”,是对简堂最大的支持!
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值