如何用栈实现递归与非递归的转换Word文件下载.docx

上传人:b****6 文档编号:18997373 上传时间:2023-01-02 格式:DOCX 页数:16 大小:23.70KB
下载 相关 举报
如何用栈实现递归与非递归的转换Word文件下载.docx_第1页
第1页 / 共16页
如何用栈实现递归与非递归的转换Word文件下载.docx_第2页
第2页 / 共16页
如何用栈实现递归与非递归的转换Word文件下载.docx_第3页
第3页 / 共16页
如何用栈实现递归与非递归的转换Word文件下载.docx_第4页
第4页 / 共16页
如何用栈实现递归与非递归的转换Word文件下载.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

如何用栈实现递归与非递归的转换Word文件下载.docx

《如何用栈实现递归与非递归的转换Word文件下载.docx》由会员分享,可在线阅读,更多相关《如何用栈实现递归与非递归的转换Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。

如何用栈实现递归与非递归的转换Word文件下载.docx

/*根指针进栈*/

while(!

stackempty(S)){

while(gettop(S,p)&

&

p){/*向左走到尽头*/

visit(p);

/*每向前走一步都访问当前结点*/

push(S,p->

pop(S,p);

if(!

stackempty(S)){/*向右走一步*/

2)中序遍历

a)递归方式

1f2a39cc2d]voidinorder_recursive(BitreeT)/*中序遍历二叉树的递归算法*/

inorder_recursive(T->

1f2a39cc2d]void 

inorder_nonrecursive(BitreeT)

/*初始化栈*/

push(S,T);

/*根指针入栈*/

while(!

while(gettop(S,p)&

p) 

/*向左走到尽头*/

push(S,p->

pop(S,p);

/*空指针退栈*/

if(!

/*向右走一步*/

3)后序遍历

1f2a39cc2d]voidpostorder_recursive(BitreeT)/*中序遍历二叉树的递归算法*/

if(T){

postorder_recursive(T->

visit(T);

}

1f2a39cc2d]typedefstruct{

BTNode*ptr;

enum{0,1,2}mark;

}PMType;

/*有mark域的结点指针类型*/

voidpostorder_nonrecursive(BiTreeT)/*后续遍历二叉树的非递归算法*/

PMTypea;

/*S的元素为PMType类型*/

push(S,{T,0});

/*根结点入栈*/

pop(S,a);

switch(a.mark)

case0:

push(S,{a.ptr,1});

/*修改mark域*/

if(a.ptr->

lchild) 

push(S,{a.ptr->

lchild,0});

break;

case1:

push(S,{a.ptr,2});

rchild) 

rchild,0});

case2:

visit(a.ptr);

/*访问结点*/

4)如何实现递归与非递归的转换

通常,一个函数在调用另一个函数之前,要作如下的事情:

a)将实在参数,返回地址等信息传递

给被调用函数保存;

b)为被调用函数的局部变量分配存储区;

c)将控制转移到被调函数的入口.

从被调用函数返回调用函数之前,也要做三件事情:

a)保存被调函数的计算结果;

b)释放被调

函数的数据区;

c)依照被调函数保存的返回地址将控制转移到调用函数.

所有的这些,不论是变量还是地址,本质上来说都是"

数据"

都是保存在系统所分配的栈中的.

ok,到这里已经解决了第一个问题:

递归调用时数据都是保存在栈中的,有多少个数据需要保存

就要设置多少个栈,而且最重要的一点是:

控制所有这些栈的栈顶指针都是相同的,否则无法实现

同步.

下面来解决第二个问题:

在非递归中,程序如何知道到底要转移到哪个部分继续执行?

回到上

面说的树的三种遍历方式,抽象出来只有三种操作:

访问当前结点,访问左子树,访问右子树.这三

种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以

得到:

a)访问当前结点:

对目前的数据进行一些处理;

b)访问左子树:

变换当前的数据以进行下一次

处理;

c)访问右子树:

再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式).

下面以先序遍历来说明:

visit(T)这个操作就是对当前数据进行的处理,preorder_recursive(T->

lchild)就是把当前

数据变换为它的左子树,访问右子树的操作可以同样理解了.

现在回到我们提出的第二个问题:

如何确定转移到哪里继续执行?

关键在于一下三个地方:

a)

确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结

构;

b)确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用

树的"

左子树"

和"

右子树"

c)确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的

"

叶子结点"

.

三.三个例子

好了上面的理论知识已经足够了,下面让我们看看几个例子,结合例子加深我们对问题的认识

.即使上面的理论你没有完全明白,不要气馁,对事物的认识总是曲折的,多看多想你一定可以明

白(事实上我也是花了两个星期的时间才弄得比较明白得).

1)例子一:

1f2a39cc2d]f(n)=n+1;

(n<

2)

f[n/2]+f[n/4](n>

=2);

这个例子相对简单一些,递归程序如下:

intf_recursive(intn)

intu1,u2,f;

if(n<

2) 

f=n+1;

else{

u1=f_recursive((int)(n/2));

u2=f_recursive((int)(n/4));

f=u1*u2;

returnf;

下面按照我们上面说的,确定好递归调用树的结构,这一步是最重要的.首先,什么是叶子结点

我们看到当n<

2时f=n+1,这就是返回的语句,有人问为什么不是f=u1*u2,这也是一个

返回的语句呀?

答案是:

这条语句是在u1=exmp1((int)(n/2))和u2=exmp1((int)(n/4))之后

执行的,是这两条语句的父结点.其次,什么是当前结点,由上面的分析,f=u1*u2即是父结点

.然后,顺理成章的u1=exmp1((int)(n/2))和u2=exmp1((int)(n/4))就分别是左子树和右子

树了.最后,我们可以看到,这个递归函数可以表示成后序遍历的二叉调用树.好了,树的情况分析

到这里,下面来分析一下栈的情况,看看我们要把什么数据保存在栈中,在上面给出的后序遍历的如果这个过程你没

非递归程序中我们已经看到了要加入一个标志域,因此在栈中要保存这个标志域;

另外,u1,u2和

每次调用递归函数时的n/2和n/4参数都要保存,这样就要分别有三个栈分别保存:

标志域,返回量

和参数,不过我们可以做一个优化,因为在向上一层返回的时候,参数已经没有用了,而返回量也

只有在向上返回时才用到,因此可以把这两个栈合为一个栈.如果对于上面的分析你没有明白,建

议你根据这个递归函数写出它的递归栈的变化情况以加深理解,再次重申一点:

前期对树结构和

栈的分析是最重要的,如果你的程序出错,那么请返回到这一步来再次分析,最好把递归调用树和

栈的变化情况都画出来,并且结合一些简单的参数来人工分析你的算法到底出错在哪里.

ok,下面给出我花了两天功夫想出来的非递归程序(再次提醒你不要气馁,大家都是这么过来

的).

1f2a39cc2d]intf_nonrecursive(intn)

intstack[20],flag[20],cp;

/*初始化栈和栈顶指针*/

cp=0;

stack[0]=n;

flag[0]=0;

while(cp>

=0){

switch(flag[cp]){

/*访问的是根结点*/

if(stack[cp]>

=2){/*左子树入栈*/

flag[cp]=1;

/*修改标志域*/

cp++;

stack[cp]=(int)(stack[cp-1]/2);

flag[cp]=0;

}else{ 

/*否则为叶子结点*/

stack[cp]+=1;

flag[cp]=2;

/*访问的是左子树*/

=2){/*右子树入栈*/

cp+=2;

stack[cp]=(int)(stack[cp-2]/4);

/**/

if(flag[cp-1]==2){/*当前是右子树吗?

*/

/* 

*如果是右子树,那么对某一棵子树的后序遍历已经

*结束,接下来就是对这棵子树的根结点的访问

*/

stack[cp-2]=stack[cp]*stack[cp-1];

flag[cp-2]=2;

cp=cp-2;

}else 

/*否则退回到后序遍历的上一个结点*/

cp--;

returnstack[0];

算法分析:

a)flag只有三个可能值:

0表示第一次访问该结点,1表示访问的是左子树,2表示

已经结束了对某一棵子树的访问,可能当前结点是这棵子树的右子树,也可能是叶子结点.b)每

遍历到某个结点的时候,如果这个结点满足叶子结点的条件,那么把它的flag域设为2;

否则根据

访问的是根结点,左子树或是右子树来设置flag域,以便决定下一次访问该节点时的程序转向.

2)例子二

快速排序算法

递归算法如下:

1f2a39cc2d]voidswap(intarray[],intlow,inthigh)

inttemp;

temp=array[low];

array[low]=array[high];

array[high]=temp;

intpartition(intarray[],intlow,inthigh)

intp;

p=array[low];

while(low<

high){

high&

array[high]>

=p) 

high--;

swap(array,low,high);

array[low] 

<

low++;

returnlow;

voidqsort_recursive(intarray[],intlow,inthigh)

if(low<

p=partition(array,low,high);

qsort_recursive(array,low,p-1);

qsort_recursive(array,p+1,high);

需要说明一下快速排序的算法:

partition函数根据数组中的某一个数把数组划分为两个部分,

左边的部分均不大于这个数,右边的数均不小于这个数,然后再对左右两边的数组再进行划分.这

里我们专注于递归与非递归的转换,partition函数在非递归函数中同样的可以调用(其实

partition函数就是对当前结点的访问).

再次进行递归调用树和栈的分析:

递归调用树:

a)对当前结点的访问是调用partition函数;

b)左子树:

c)右子树:

d)叶子结点:

当low<

high时;

e)可以看出这是一个先序调用的二叉树

栈:

要保存的数据是两个表示范围的坐标.

1f2a39cc2d]voidqsort_nonrecursive(intarray[],intlow,inthigh)

intm[50],n[50],cp,p;

m[0]=low;

n[0]=high;

while(m[cp]<

n[cp]){

n[cp]){/*向左走到尽头*/

p=partition(array,m[cp],n[cp]);

/*对当前结点的访问*/

m[cp]=m[cp-1];

n[cp]=p-1;

/*向右走一步*/

m[cp+1]=n[cp]+2;

n[cp+1]=n[cp-1];

3)例子三

阿克曼函数:

1f2a39cc2d]akm(m,n)=n+1;

(m=0时)

akm(m-1,1);

(n=0时)

akm(m-1,akm(m,n-1));

(m!

=0且n!

=0时)[/code:

1f2a39cc2d] 

1f2a39cc2d]intakm_recursive(intm,intn)

if(m==0) 

return(n+1);

elseif(n==0) 

returnakm_recursive(m-1,1);

temp=akm_recursive(m,n-1);

returnakm_recursive(m-1,temp);

这个例子相对难一些,不过只要正确的分析递归调用树和栈的变化情况就不难解决,先卖个关子,晚上再来公布答案,感兴趣的可以先想想.

这个例子相对难一些,不过只要正确的分析递归调用树和栈的变化情况就不难解决,先卖个关子,晚上再来公布答案,感兴趣的可以先想想.[/quote:

782a080549]

递归和非递归,其实都是一样的.非递归需要人为构建维护堆栈.递归只是系统

在帮你维护堆栈而已.数据结构上说得很清楚.

好了,让我们回到递归与非递归的世界中,继续未完的旅途.

这道题的难点就是确定递归调用树的情况,因为从akm函数的公式可以看到,有三个递归调用,一般

而言,有几个递归调用就会有几棵递归调用的子树,不过这只是一般的情况,不一定准确,也不一定非要

机械化的这么作,因为通常情况下我们可以做一些优化,省去其中的一些部分,这道题就是一个例子.

递归调用树的分析:

a)是当m=0时是叶子结点;

b)左子树是akm(m-1,akm(m,n-1))调用中的

akm(m,n-1)调用,当这个调用结束得出一个值temp时,再调用akm(m-1,temp),这个调用是右子树

.c)从上面的分析可以看出,这个递归调用树是后序遍历的树.

栈的分析:

要保存的数据是m,n,当n=0或m=0时开始退栈,当n=0时把上一层栈的m值变为

m-1,n变为1,当m=0时把上一层栈的m值变为0,n变为n+1.从这个分析过程可以看出,我们省略了

当n=0时的akm(m-1,1)调用,原来在系统机械化的实现递归调用的过程中,这个调用也是一棵子树,

不过经过分析,我们用修改栈中数据的方式进行了改进.

0351fd7499]intakm_nonrecursive(intm,intn)

intm1[50],n1[50],cp;

m1[0]=m;

n1[0]=n;

do{

while(m1[cp]>

0){ 

/*压栈,直到m1[cp]=0*/

while(n1[cp]>

/*压栈,直到n1[cp]=0*/

m1[cp]=m1[cp-1];

n1[cp]=n1[cp-1]-1;

/*计算akm(m-1,1),当n=0时*/

m1[cp]=m1[cp]-1;

n1[cp]=1;

/*改栈顶为akm(m-1,n+1),当m=0时*/

n1[cp]=n1[cp+1]+1;

}while(cp>

0||m1[cp]>

0);

returnn1[0]+1;

0351fd7499]

三.递归程序的分类及用途

递归程序分为两类:

尾部递归和非尾部递归.上面提到的几个例子都是非尾部递归,在一个选择分支中有至少

一个的递归调用.相对而言,尾部递归就容易很多了,因为与非尾部递归相比,每个选择分支只有一个递归调用,

我们在解决的时候就不需要使用到栈,只要循环和设置好循环体就可以了.下面再举几个尾部递归的例子吧,比较

简单我就不多说什么了.

1)例子一

[code:

785fd53e3e]g(m,n)=0(m=0,n>

=0)

=g(m-1,2n)+n;

(m>

0,n>

=0)[/code:

785fd53e3e] 

a)递归程序

785fd53e3e]intg_recursive(intm,intn)

if(m==0&

n>

=0) 

return0;

return(g_recurse(m-1,2*n)+n);

785fd53e3e]

b)非递归程序

785fd53e3e]intg_nonrecursive(intm,intn)

for(p=0;

m>

0&

=0;

m--,n*=2) 

p+=n;

returnp;

}[/code

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1