其实过程很简单:一直往左走 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