数据结构与算法—递归与非递归转换
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
递归与非递归转换的基础知识是能够正确理解三种树的遍历方法:前序,中序和后序,第一篇就是关于这三种遍历方法的递归和非递归算法。
一、为什么要学习递归与非递归的转换的实现方法?
1>并不是每一门语言都支持递归的。
2>有助于理解递归的本质。
3>有助于理解栈,树等数据结构。
二、三种遍历树的递归和非递归算法
递归与非递归的转换基于以下的原理:所有的递归程序都可以用树结构表示出来。需要说明的是,这个”原理”并没有经过严格的数学证明,只是我的一个猜想,不过在至少在我遇到的例子中是适用的。学习过树结构的人都知道,有三种方法可以遍历树:前序,中序,后序。理解这三种遍历方式的递归和非递归的表达方式是能够正确实现转换的关键之处,所以我们先来谈谈这个。需要说明的是,这里以特殊的二叉树来说明,不过大多数情况下二叉树已经够用,而且理解了二叉树的遍历,其它的树遍历方式就不难了。
1>前序遍历
a>递归方式:
void preorder_recursive(Bitree T> /* 先序遍历二叉树的递归算法 */
{
if (T> {
visit(T>。 /* 访问当前结点 */
preorder_recursive(T->lchild>。 /* 访问左子树 */
preorder_recursive(T->rchild>。 /* 访问右子树 */
}
}
b>非递归方式
void preorder_nonrecursive(Bitree T> /* 先序遍历二叉树的非递归算法 */
initstack(S>。
push(S,T>。 /* 根指针进栈 */
while(!stackempty(S>> {
while(gettop(S,p>&&p> { /* 向左走到尽头 */
visit(p>。 /* 每向前走一步都访问当前结点 */
push(S,p->lchild>。
}
pop(S,p>。
if(!stackempty(S>> { /* 向右走一步 */
pop(S,p>。
push(S,p->rchild>。
}
}
}
2>中序遍历
a>递归方式
void inorder_recursive(Bitree T> /* 中序遍历二叉树的递归算法 */ {
if (T> {
inorder_recursive(T->lchild>。 /* 访问左子树 */
visit(T>。 /* 访问当前结点 */
inorder_recursive(T->rchild>。 /* 访问右子树 */
}
b>非递归方式
void inorder_nonrecursive(Bitree T>
{
initstack(S>。 /* 初始化栈 */
push(S,T>。 /* 根指针入栈 */
while (!stackempty(S>> {
while (gettop(S, p> && p> /* 向左走到尽头 */
push(S,p->lchild>。
pop(S,p>。 /* 空指针退栈 */
if (!stackempty(S>> {
pop(S,p>。
visit(p>。 /* 访问当前结点 */
push(S,p->rchild>。 /* 向右走一步 */
}
}
}
3>后序遍历
a>递归方式
void postorder_recursive(Bitree T> /* 中序遍历二叉树的递归算法 */ {
if (T> {
postorder_recursive(T->lchild>。 /* 访问左子树 */
postorder_recursive(T->rchild>。 /* 访问右子树 */
visit(T>。 /* 访问当前结点 */
}
}
b>非递归方式
typedef struct {
BTNode* ptr。
enum {0,1,2} mark。
} PMType。 /* 有mark域的结点指针类型 */
void postorder_nonrecursive(BiTree T> /* 后续遍历二叉树的非递归算法*/
{
PMType a。
initstack(S>。 /* S的元素为PMType类型 */
push (S,{T,0}>。 /* 根结点入栈 */
while(!stackempty(S>> {
pop(S,a>。
switch(a,mark>
{
case 0:
push(S,{a.ptr,1}>。 /* 修改mark域 */
if(a.ptr->lchild>
push(S,{a,ptr->lchild,0}>。 /* 访问左子树 */
break。
case 1:
push(S,{a,pt,2}>。 /* 修改mark域 */
if(a.ptr->rchild>
push(S,{a.ptr->rchild,0}>。 /* 访问右子树 */
break。
case 2:
visit(a.ptr>。 /* 访问结点 */
}
}
}
三、实现递归与非递归的换转原理和例子
一)原理分析
通常,一个函数在调用另一个函数之前,要作如下的事情:a>将实在参数,返回地址等信息传递给被调用函数保存。 b>为被调用函数的局部变量分配存储区。c>将控制转移到被调函数的入口。
从被调用函数返回调用函数之前,也要做三件事情:a>保存被调函数的计算结果。b>释放被调函数的数据区。c>依照被调函数保存的返回地址将控制转移到调用函数。所有的这些,不论是变量还是地址,本质上来说都是”数据”,都是保存在系统所分配的栈中的。
ok,到这里已经解决了第一个问题:递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:控制所有这些栈的栈顶指针都是相同的,否则无法实现同步。
下面来解决第二个问题:在非递归中,程序如何知道到底要转移到哪个部分继续执行?回到上面说的树的三种遍历方式,抽象出来只有三种操作:访问当前结点,访问左子树,访问右子树。这三种操作的顺序不同,遍历方式也不同。如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:a>访问当前结点:对目前的数据进行一些处理。b>访问左子树:变换当前的数据以进行下一次处理。c>访问右子树:再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式>。
下面以先序遍历来说明: