《数据结构》遍历二叉树的非递归算法的疑问。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《数据结构》遍历二叉树的非递归算法的疑问。相关的知识,希望对你有一定的参考价值。

问题来自《数据结构》(严蔚敏、吴伟民著,C语言描述,清华大学出版社,1997年4月第1版),第6章树与二叉树,6.3遍历二叉树和线索二叉树。

遍历二叉树的非递归算法,教材的描述如下(见130页):

仿照递归算法执行过程中递归工作栈的状态变化状况可直接写出相应的非递归算法。例如,从中序遍历递归算法执行过程中递归工作栈的状态可见:(1)工作记录中包含两项,其一是递归调用的语句编号,其二是指向根结点的指针,则当栈顶记录中的指针非空时,应遍历左子树,即指向左子树根的指针进栈;(2)若栈顶记录中的指针值为空,则应退至上一层,若是从左子树返回,则应访问当前层即栈顶记录中指针所指的根结点;(3)若是从右子树返回,则表明当前曾的遍历结束,应继续退栈。从另一角度看,这意味着遍历右子树时不再需要保存当前层的根指针,可直接修改栈顶记录中的指针即可。由此可得两个中序遍历二叉树的非递归算法如下:

=============================================================

算法一:

Status InOrderTraverse(BiTree T, Status(*Visit)(TElemType e))

//采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
//中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit。

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);
if(!Visit(p->data))
return ERROR;
Push(S,p->rchild);
//if
//while
return OK;
//InOrderTraverse 算法一
============================================================
============================================================
算法二:

Status InOrderTraverse(BiTree T, Status (*Visit)(TelemType e))

//采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
//中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit。
InitStack(S);
p=T;
while(p||!StackEmpty(S))

if(p)

Push(S,p);
p=p->lchild;
//if 根指针进栈,遍历左子树
else

Pop(S,p);
if(!Visit(p->data))
return ERROR;
p=p->rchild;
//else 根指针退栈,访问根结点,遍历右子树
//while
InOrderTraverse() 算法二
============================================================

我的问题是,文字描述中提到的“从中序遍历递归算法执行过程中递归工作栈的状态可见:……”的这一部分。我不明白什么是“栈顶记录中的指针”,什么是“工作记录”。不明白第(2)句话中,先说“栈顶记录中的指针值为空”的时候怎么怎么样,后面为什么又会有对“栈顶记录中指针所指的根结点”的访问?这不是前后矛盾的吗?在读完后面的代码并作了认真的分析之后,仍不明白这一段话到底描述了一个什么样的过程,不理解转化成非递归过程的具体实现。请达人指教。
参考:

对代码中与栈有关的函数的说明:
InitStack(&S) 构造一个空栈S;
Push(&S,e) 栈已存在时,插入元素e为新的栈顶元素;
StackEmpty(S) 栈已存在时,若为空栈返回TRUE,否则返回FALSE;
GetTop(S,&e) 栈已存在且非空时,用e返回S的栈顶元素;
Pop(&S,&e) 栈S存在且非空时,删除栈顶元素,并用e返回其值;
——摘自该书45页。

中序遍历二叉树的操作递归定义:
若二叉树为空,则空操作;
否则
(1) 中序遍历左子树;
(2) 访问根结点;
(3) 中序遍历右叉树;
——摘自该书128页。

递归过程转为非递归过程的实质:
将原由系统管理的递归工作栈改为由程序员管理。在非递归过程中,递归工作栈同样起着两个作用:其一是将递归调用室的实在参数和返回地址传递给下一层递归;其二是保存本层参数和局部变量,以便从下一层返回本层时继续恢复使用。如果将栈的每一层视为一项待处理的事务,则栈顶记录则是当前急待处理的事务。
——摘自《数据结构》PASCAL语言描述,严蔚敏、吴伟民著,清华大学出版社。

首先敬仰一下楼主的勤奋!

我主要针对第二个算法说,我觉得上面这段话也是在讲第二个算法。其实两个算法差不太多。
1. 栈顶记录中的指针其实就是指栈顶,每次push()进去或者pop()出来的那个p。他代表的是正在访问的节点得下一个节点。比如,访问一个树t的左子树t->lchild时,栈顶就是t;访问t->lchild->lchild时,栈顶就是t->lchild。访问t->rchild时,栈顶为NULL;访问t->lchild->rchild时,栈顶为t;访问t->rchild->lchild时,栈顶也是t;访问t->rchild->rcchild时,栈顶仍为NULL。他的意义就是,在访问完了当前的子树之后,就会去访问栈顶记录的指针对应的节点的数据。
2. 关于“工作记录”那个词,我觉得还是别深究了。那段话意思是要仿照编译器把递归编译成迭代的思路来自己写迭代算法,可是实际上后面给出的算法里根本没有严格执行上述思路,写出来的算法并不是严格意义上的可以一般性替换递归的迭代算法。所以追究那个词也没意义,明白迭代遍历的算法怎么用就够了。等以后对递归有了更深刻的认识,自然就明白了。其实就是函数递归调用自身之前像中断那样保存自己的工作环境和进度。
3. (2)句并不矛盾。他说“指针为空时”和“指针指向的xxx”中间不是有句“退回上一层”么,那就表示pop(),于是原来那个在栈顶的空指针弹出去了,原来在第二位的指针现在到了栈顶。于是后面那句指的是对这个指针进行操作。
参考技术A 二叉树递归算法与非递归的算法本质区别不大。递归算法通过不断的函数调用,来实现,在这个过程中,没递归调用一次,系统堆栈区保存本次调用的断点,以及相关寄存器信息,之后进行下次调用,之后堆栈进行上面所说的过程。例如进行先序遍历,先遍历左子树的节点,再遍历左子树的左子树,之后这个过程进行循环。到最后一个左子树的时候,由于p--next==null,递归条件结束,函数return,这时栈中保存的是,上次调用函数的相关断点极寄存器信息,之后函数向上一点一点返回。其实非递归调用把这个栈显式花了,只不过是你自己建造了栈进行显式调用。

23 遍历二叉树的非递归算法

例子:中序遍历非递归算法

技术图片

 

 

实现代码:

技术图片
//中序遍历的非递归算法
int InOrderTraverse_No_DiGui(BiTree T){
    BiTree p;       //顶底指向二叉树中节点的游标
    InitStack(S);       //初始化栈
    p = T;      //p指向所给的二叉树根节点
    while(p || !StackEmpty(S)){
        if(p){      //若当前节点非空
            Push(S, p);     //将当前节点入栈
            p = p->lchild;      //游标指向当前节点的左孩子
        }else{
            Pop(S, q);      //出栈,将栈顶节点返回到一个二叉树节点类型的变量 q 中
            printf("%c", q->data);      //输出该节点的数据域
            p = q->rchild;      //游标指向当前节点的右孩子
        }
    }//while

    return 1;       //遍历结束
}
View Code

 

 

 

以上是关于《数据结构》遍历二叉树的非递归算法的疑问。的主要内容,如果未能解决你的问题,请参考以下文章

二叉树的非递归遍历

二叉树的非递归遍历

二叉树的非递归遍历

[算法]二叉树的非递归遍历算法

数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树

数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树