如何用栈实现递归与非递归的转换Word文件下载.docx
- 文档编号:18997373
- 上传时间:2023-01-02
- 格式:DOCX
- 页数:16
- 大小:23.70KB
如何用栈实现递归与非递归的转换Word文件下载.docx
《如何用栈实现递归与非递归的转换Word文件下载.docx》由会员分享,可在线阅读,更多相关《如何用栈实现递归与非递归的转换Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。
/*根指针进栈*/
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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 何用 实现 递归 转换