您的位置:首页 > 其它

二叉树系列(2)前序中序后序遍历的递归和非递归实现

2013-09-01 23:31 330 查看
递归地对二叉树进行遍历的做法比较好理解。有一颗树,长成这个样子。



下面分别是对它进行三序遍历的结果:

前序递归

//前序递归
void pre_order(link t, void (*visit)(link))
{
	if(!t)
		return;
	visit(t);
	pre_order(t->l, visit);
	pre_order(t->r, visit);
}
前序遍历输出结果



中序递归

//中序递归
void in_order(link t, void (*visit)(link))
{
	if(!t)
		return;
	in_order(t->l, visit);
		visit(t);	
	in_order(t->r, visit);
}
中序遍历输出结果



后序递归

//后序递归
void post_order(link t, void (*visit)(link))
{
	if(!t)
		return;
	post_order(t->l, visit);
	post_order(t->r, visit);
	visit(t);	
}
后序遍历输出结果



递归的写法非常直观好理解。比较困难的是非递归的写法。而且,比较不对称的结果是,三种顺序的非递归写法的难度是不同的。容易程度是,前序>中序>后续。

先从最容易的非递归前序遍历开始。基本思路是,维护一个栈。对于当前节点,输出它,然后依次入栈当前节点的右儿子,左儿子,然后弹栈。不断进行这个过程直到栈空。

//前序非递归
void pre_order_2(link t, void (*visit)(link))
{
	if(t==NULL)
		return;
	std::stack<link> s;    	
	link p = t;	
	s.push(p);
    while(!s.empty())
    {
        p = s.top();
        s.pop();
		visit(p);
		if(p->r != NULL)
			s.push(p->r);
		if(p->l != NULL)
			s.push(p->l);
        
    }
}


中序非递归的基本思路和前序非递归的基本思路类似,也要借助栈,不过,中序非递归需要把当前节点的左儿子,左孙子,左重孙......等一路左到叶结点的所有左节点全部压栈之后,再弹栈,再把弹栈的栈顶的右儿子入栈。

//中序非递归
void in_order_2(link t, void (*visit)(link))
{
	std::stack<link> s;
    link p=t;
    while( p!=NULL || !s.empty())// p!=NULL这个条件是为了兼容第1次循环的条件设置。
    {
        while(p!=NULL)
        {
            s.push(p);
            p=p->l;
        }
		//找到当前节点的最深最深最深的左儿子
		
        if(!s.empty())
        {
            p=s.top();            
			visit(p);
            s.pop();
            p=p->r; 			
        }
    }    
}


另外一种写法

class Solution { //Leetcode的题目
public:
    vector<int> inorderTraversal(TreeNode *root) {
        vector<int> res_vec;
        if(root==NULL)
            return res_vec;
        TreeNode* cur_node=root;
        std::stack<TreeNode*> stack_left;
        while(cur_node!=NULL){
            stack_left.push(cur_node);
            cur_node=cur_node->left;
        }
        while(!stack_left.empty()){
            cur_node=stack_left.top();
            res_vec.push_back(cur_node->val);
            cur_node=cur_node->right;
            stack_left.pop();
            while (cur_node!=NULL) {
                stack_left.push(cur_node);
                cur_node=cur_node->left;
            }
        }
        return res_vec;
    }
};


后序遍历的非递归写法有一部分和中序遍历的非递归写法类似,它们都会先入栈当前节点的所有左后代。后序遍历更困难的一点是,当出栈一个节点时,需要判断这是否是它的第一次出栈,如果是第一次出栈,说明当前节点的有节点还没有被访问,所以当前节点不宜访问,于是重新进栈,并且把它的右儿子进栈。如果是第二次出栈,则说明已经访问了它的右儿子了,因此可以访问当前节点了。实现的方法参考了这里,如下。

第一种后序遍历的非递归实现,基于preorder前序遍历进行修改。

class Solution {
//第一种,基于前序遍历进行修改
public:
    vector<int> postorderTraversal(TreeNode *root) {
        vector<int> res;
        stack<TreeNode*> node_stack;
        if(root==NULL)
            return res;
        node_stack.push(root);
        TreeNode* cur=root;
        TreeNode* pre=NULL;
        while(!node_stack.empty()){
            cur=node_stack.top();
            if((cur->left==NULL&&cur->right==NULL)||((pre!=NULL)&&(pre==cur->left || pre==cur->right))) {
            //为什么基于前序遍历改后序遍历,这里是 (pre==cur->left || pre==cur->right) 这两个条件? 
            //因为栈的构建顺序依次是cur, cur->right, cur->left,所以pre可能是左子树或右子树。
            //如果基于中序遍历改后序遍历,则条件变为了(cur->right == pre),因为中序遍历的栈的构建顺序是cur的左儿子,左左儿子,左左左儿子,cur->right。这个顺序说明pre只能是cur的右子树。
                res.push_back(cur->val);
                node_stack.pop();
                pre=cur;
            }else{
                if(cur->right!=NULL)
                    node_stack.push(cur->right);
                if(cur->left!=NULL)
                    node_stack.push(cur->left);
            }
        }
        return res;
    }
};


第二种后序遍历的非递归实现,基于inorder 中序遍历进行修改。

class Solution {
//第二种,基于中序遍历进行修改
public:
    vector<int> postorderTraversal(TreeNode *root) {
        stack<TreeNode*> node_stack;
        vector<int> result;
        TreeNode* p = root;
        TreeNode* cur = NULL;
        TreeNode* pre = NULL;
        while (p != NULL) {
            node_stack.push(p);
            p=p->left;
        } 

        while (!node_stack.empty()) {
            pre = cur;
            cur = node_stack.top();
            if ( (cur->left == NULL && cur->right == NULL) || (cur->right == pre)) {
            //此时是基于中序遍历改后序遍历,条件变为了(cur->right == pre),因为中序遍历的栈的构建顺序是cur的左儿子,左左儿子,左左左儿子,cur->right。这个顺序说明pre只能是cur的右子树。
            //如果基于前序遍历改后序遍历,相应条件变为 (pre==cur->left || pre==cur->right) 这两个条件。
            //因为栈的构建顺序依次是cur, cur->right, cur->left,所以pre可能是左子树或右子树。
                node_stack.pop();
                result.push_back(cur->val);
            }else{
                cur = cur->right;
                while (cur != NULL) {
                    node_stack.push(cur);
                    cur=cur->left;
                }
            } 
        }
        return result;
    }
};


第三种后序遍历的非递归实现,用一个变量来表示当前元素是否是第2次出现在栈顶,如果是第2次出现则可以弹出,否则说明右孩子还没有访问到,当前元素应该继续入栈。

//后序非递归第三种
void post_order_3(link t, void (*visit)(link))
{
	std::stack<tag_node*> s;
    link p=t;
    tag_node *temp;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)              
        {
            tag_node* t_node=(tag_node*)malloc(sizeof(tag_node));
            t_node->node_link=p;
            t_node->is_first=true;
            s.push(t_node);
            p=p->l;
        }
        if(!s.empty())
        {
            temp=s.top();
            s.pop();
            if(temp->is_first==true)     //表示是第一次出现在栈顶 
             {
                temp->is_first=false;
                s.push(temp);
                p=temp->node_link->r;
            }
            else                        //第二次出现在栈顶 
             {
				visit(temp->node_link);
				free(temp);
                p=NULL; //避免下一次循环的时候重复找P的最深左子节点
            }
        }
    }    
	return;
}


完整的头文件和源文件在下一篇中给出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: