我们先看看用前序递归如何访问整棵树:先访问根节点,然后访问左节点,再访问右节点。如果不用递归,那该怎么做呢?仔细看一下递归程序,就会发现,其实每次都是走树的左分支(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