递归过程的详解(普通递归以及二叉搜索树的遍历递归)

1.递归就是有去(递去)有回(归来)

有去:是指把问题分解成无数的小问题,一层一层地解决,最终到达临界点之后,即解决完最后一个需要调用自身函数处理的问题之后,有回:将解决的结果原路返回到原点,原问题解决。

 

递归的基本思想,是把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。

最重要的一点就是假设子问题已经解决了,现在要基于已经解决的子问题来解决当前问题;或者说,必须先解决子问题,再基于子问题来解决当前问题

或者可以这么理解:递归解决的是有依赖顺序关系的多个问题。

我们假设一个抽象问题有两个时间点要素:开始处理,结束处理。

 

那么递归处理的顺序就是,先开始处理的问题,最后才能结束处理。

假设如下问题的依赖关系:

【A】----依赖---->【B】----依赖---->【C】

我们的终极目的是要解决问题A,

那么三个问题的处理顺序如下:

开始处理问题A;

由于A依赖B,因此开始处理问题B;

由于B依赖C,开始处理问题C;

结束处理问题C;

结束处理问题B;

结束处理问题A

对于软件来说,函数的调用关系就是一个广义递归的过程,如下,

func_A()

{

func_B();

}

func_B()

{

func_C();

}

func_C()

{

/

}

 

调用函数A;

调用函数B;

调用函数C;

函数C返回;

函数B返回;

函数A返回;

2递归函数的具体执行过程:

1.给出一个值4267,我们需要依次产生字符‘4’,‘2’,‘6’,和‘7’。就如在printf函数中使用了%d格式码,它就会执行类似处理。

分析:首先我们会想到用4267取余,然后除以10再区域,如此循环。但这样输出的顺序不会是7,6,2,4吗?于是我们就利用递归的堆栈结构的特性:先进后出

当递归函数调用自身时,情况于是如此。每进行一次新的调用,都将创建一批变量,他们将掩盖递归函数前一次调用所创建的变量。当我追踪一个递归函数的执行过程时,必须把不同次调用的变量区分开来,以避免混淆。

以下即为递归调用函数执行过程的变量变化的过程。

程序中的函数有两个变量:参数value和局部变量quotient。下面的一些图显示了堆栈的状态,当前可以访问的变量位于栈顶。所有其他调用的变量饰以灰色的阴影,表示他们不能被当前正在执行的函数访问。
    假定我们以4267这个值调用递归函数。当函数刚开始执行时,堆栈的内容如下图所示:

 

执行除法之后,堆栈的内容如下:

接着,if语句判断出quotient的值非零,所以对该函数执行递归调用。当这个函数第二次被调用之初,堆栈的内容如下:

 

堆栈上创建了一批新的变量,隐藏了前面的那批变量,除非当前这次递归调用返回,否则他们是不能被访问的。再次执行除法运算之后,堆栈的内容如下:

quotient的值现在为42,仍然非零,所以需要继续执行递归调用,并再创建一批变量。在执行完这次调用的出发运算之后,堆栈的内容如下:

此时,quotient的值还是非零,仍然需要执行递归调用。在执行除法运算之后,堆栈的内容如下:

不算递归调用语句本身,到目前为止所执行的语句只是除法运算以及对quotient的值进行测试。由于递归调用这些语句重复执行,所以它的效果 类似循环:当quotient的值非零时,把它的值作为初始值重新开始循环。但是,递归调用将会保存一些信息(这点与循环不同),也就好是保存在堆栈中的变量值。

现在quotient的值变成了零,递归函数便不再调用自身开始执行返回过程,而是开始打印输出。然后函数返回,并开始销毁堆栈上的变量值。

接着函数返回,它的变量从堆栈中销毁。接着,递归函数的前一次调用重新继续执行,她所使用的是自己的变量,他们现在位于堆栈的顶部。因为它的value值是42,所以调用putchar后打印出来的数字是2。
  输出42:

接着递归函数的这次调用也返回,它的变量也被销毁,此时位于堆栈顶部的是递归函数再前一次调用的变量。递归调用从这个位置继续执行,这次打印的数字是6。在这次调用返回之前,堆栈的内容如下:
  输出426:

现在我们已经展开了整个递归过程,并回到该函数最初的调用。这次调用打印出数字7,也就是它的value参数除10的余数。
  输出4267

然后,这个递归函数就彻底返回到其他函数调用它的地点。
如果你把打印出来的字符一个接一个排在一起,出现在打印机或屏幕上,你将看到正确的值:4267。

递归的使用条件:

存在一个递归调用的终止条件;每次递归的调用必须越来越靠近这个条件;只有这样递归才会终止,否则是不能使用递归的!

递归的基本原理
1每一次函数调用都会有一次返回.当程序 执行到某一级递归的结尾处时,它会转移到前一级递归继续执行.

2递归函数中,位于递归调用前的语句和各级被调函数具有相同的顺序.如打印语句 #1 位于递归调用语句前,它按照递归调用的顺序被执行了 4 次.

3每一级的函数调用都有自己的局部变量.

4递归函数中,位于递归调用语句后的语句的执行顺序和各个被调用函数的顺序相反.
 即位于递归函数入口前的语句,从外往里执行;位于递归函数入口后面的语句,由里往外执行。

5虽然每一级递归有自己的变量,但是函数代码并不会得到复制.

6递归函数中必须包含可以终止递归调用的语句。

一旦你理解了递归(理解递归,关键是脑中有一幅代码的图片,函数执行到递归函数入口时,就扩充一段完全一样的代码,执行完扩充的代码并return后,继续执行前一次递归函数中递归函数入口后面的代码),阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能正确的完成任务。

返回的过程是隐秘的,但是确实存在的过程。

以下是二叉搜索树的递归过程:以中序遍历为例

二叉搜索树的结构

递归条件不满足时,return返回是此函数的结束处,相应的变量的参数也会返回到上一层的参数处,覆盖原来的参数,继续此函数之后未完成的函数动作,如果此函数执行完则跳出此层递归,继续返回上次未完成的函数处(递归调用处)。

函数返回的动作类似于中断执行一样,递归条件不满足时,函数调回原断点执行处。

1.ptràA,调用函数inorder(A.lc)参数为B,执行if后语句

2.ptr—>B, 调用函数inorder(B.lc),参数为D,执行if后语句

3. ptr—>D,调用函数inorder(D.lc),参数为F,执行if后语句

4. ptr—>F,调用函数inorder(F.lc),参数为H,执行if后语句

5. ptr—>H,调用函数inorder(H.lc),参数为空,ptr->null,

6. ptr->null,没有进入if,return退出当前函数,到5,ptr—>H,继续执行接下来动作;执行lin5print(),打印H,

7.之后执行line 6:inorder(H.rc),参数为ptr->null,不进入if语句,返回上层5,ptr—>H继续未完成的函数动作,5已经执行完,返回到4 ptr—>F,继续执行未完成的动作处执行line5 print(),打印F

8.ptr->F,执行inorder(F.rc),prt—>I,

9. prt—>I,执行inorder(I.lc)参数为空,不进入if语句,return8,prt—>I,返回上层,执行lin5print(),打印I

9.之后执行line 6:inorder(I.rc),参数为ptr->null,不进入if语句,返回return4,4已经执行完,返回3未执行的动作。参数prt->D

10执行lin5print()打印D,执行inorder(D.rc),ptr->G,进入if语句

11.执行inorder(G.lc)参数为ptr—>null,不进入if语句,return返回参,数为ptr->G执行line5print()打印G.

12 inorder(I.rc),参数为ptr—>null,不进入if语句,return到3,3已经执行完,return2,参数ptr—>B.,执行line5print()打印B

13.执行inorder(Brc),参数ptràE,

14. 执行inorder(E.lc),参数ptr->null,不再调用,返回上一层return,prt->E,执行line5print()打印E

15 执行inorder(E.rc),参数ptr->null,不再调用,返回上一层return2,2已经执行完,返回1,prt->A,执行line5print()打印A

16.执行inorder(A.rc),参数ptr->C,

17.执行inorder(C.lc),参数ptr->null,不再调用,return返回上一层17ptr->C,执行line5print()打印C,

18. 执行inorder(C.rc)

最终此二叉搜索树的中序遍历结果为HFIDGBEAC .

 

 

  • 55
    点赞
  • 160
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值