现在的位置: 首页 > 综合 > 正文

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

2018年02月18日 ⁄ 综合 ⁄ 共 2814字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.