前序、中序、后序遍历的多种非递归实现

来源:互联网 发布:type c数据接口 编辑:程序博客网 时间:2024/05/21 06:53
       我们先看看用前序递归如何访问整棵树:先访问根节点,然后访问左节点,再访问右节点。如果不用递归,那该怎么做呢?仔细看一下递归程序,就会发现,其实每次都是走树的左分支(left),直到左子树为空,然后开始从递归的最深处返回,然后开始恢复递归现场,访问右子树。

       其实过程很简单:一直往左走 root->left->left->left...->null,由于是前序遍历,因此一遇到节点,便需要立即访问;由于一直走到最左边后,需要逐步返回到父节点访问右节点,因此必须有一个措施能够对节点序列回溯。所以我们想到了用栈记忆的方法:在访问途中将依次遇到的节点保存下来。由于节点出现次序与恢复次序是反序的,因此是一个先进后出结构,需要用栈。使用栈记忆的实现有两个版本:第一个版本是跟踪指针移动用栈保存中间结果的实现方式,第二个版本是直接模拟递归。

一、跟踪指针移动用栈保存中间结果

       前序遍历是在入栈前访问,中序遍历是在出栈时访问,后序遍历用这种方法不太好实现。

节点定义:

typedef struct BiNode{  int data;  struct BiNode * lchild;  struct BiNode * rchild;}BiNode,*BiTree;

前序遍历:

void preOrderBiTree(BiNode * root){if(root == NULL)return;BiNode * node = root;stack<BiNode*> nodes;while(node || !nodes.empty()){while(node != NULL){nodes.push(node);printf("%d",node->data);node = node -> lchild;}node = nodes.top();//回溯到父节点nodes.pop();node = node -> rchild;}}

       每次都将遇到的节点压入栈,当左子树遍历完毕后才从栈中弹出最后一个访问的节点,访问其右子树。在同一层中,不可能同时有两个节点压入栈,因此栈的大小空间为O(h),h为二叉树高度。时间方面,每个节点都被压入栈一次,弹出栈一次,访问一次,复杂度为O(n)。

中序遍历:

void midOrderBinaryTree(BiNode * root){if(root == NULL)return;BiNode * node = root;stack<BiNode*> nodes;while(node || !nodes.empty()){while(node != NULL){nodes.push(node);node = node ->lchild;}node = nodes.top();printf("%d ",node ->data);nodes.pop();node = node ->rchild;}}


二、直接用栈来模拟递归,先序非常简单,而中序与后序难度一致,并且需要一个标记位来标记该节点是否被入栈过(其左右子树是否被访问过)。中序遍历和后序遍历的区别是当节点弹出后如果其isPushed为false,前者是右子树、该节点、左子树的次序入栈,后者是该节点、右子树、左子树的次序入栈。

前序遍历:

void preOrderBinaryTree(BiNode * pRoot){    if(pRoot == NULL)        return;stack<BiNode *> sData;    sData.push(pRoot);    BiNode * pNode = NULL;    while(!sData.empty()){        pNode = sData.top();        printf("%d ",pNode -> data);        sData.pop();                if(pNode -> rchild != NULL){            sData.push(pNode -> rchild);        }        if(pNode -> lchild != NULL){            sData.push(pNode -> lchild);        }    }}

        每次将节点压入栈,然后弹出,压右子树,再压入左子树,在遍历过程中,遍历序列的右节点依次被存入栈,左节点逐次被访问。同一时刻,栈中元素为m-1个右节点和1个最左节点,最高为h。所以空间也为O(h);每个节点同样被压栈一次,弹栈一次,访问一次,时间复杂度O(n)


节点定义:

typedef struct BiNodeWithTag{int data;struct BiNodeWithTag * lchild;struct BiNodeWithTag * rchild;bool isPushed;}BiNodeWithTag,*BiNodeWithTagPointer;

中序遍历:

void midOrderBinaryTree(BiNodeWithTag * root){if(root == NULL)return;stack<BiNodeWithTag *> nodes;BiNodeWithTag * node = root;nodes.push(node);while(!nodes.empty()){node = nodes.top();nodes.pop();if(node ->isPushed){printf("%d ",node ->data);continue;}if(node ->rchild != NULL)nodes.push(node ->rchild);nodes.push(node);node ->isPushed = true;if(node ->lchild != NULL)nodes.push(node ->lchild);}}

       对比先序遍历,这个算法需要额外的增加O(n)的标志位空间。另外,栈空间也扩大,因为每次压栈的时候都压入根节点与左右节点,因此栈空间为O(n)。时间复杂度方面,每个节点压栈两次,作为子节点压栈一次,作为根节点压栈一次,弹栈也是两次。

后序遍历:

void postOrderBinaryTree(BiNodeWithTag * root){if(root == NULL)return;stack<BiNodeWithTag *> nodes;BiNodeWithTag * node = root;nodes.push(node);while(!nodes.empty()){node = nodes.top();nodes.pop();if(node ->isPushed){printf("%d ",node ->data);continue;}if(node ->rchild != NULL)nodes.push(node ->rchild);if(node ->lchild != NULL)nodes.push(node ->lchild);nodes.push(node);node ->isPushed = true;}}

参考:

http://blog.csdn.net/kofsky/article/details/2886453