迭代法求二叉树前中后序遍历

序言

递归法求解三种遍历方式是十分简单的,但迭代法却不太简单,本文给出几种方式和模板。

节点类型

struct TreeNode {
	 int val;
	 TreeNode* left;
	 TreeNode* right;
	 TreeNode(int x) : val(x), left(NULL), right(NULL) {}
	 
};

前序遍历

题目

前序遍历是中左右的循序,根据栈的先入后出的特点,入栈的顺序跟遍历的顺序相反,这样出栈的时候就可以得到想要的顺序。

递归思路:先树根,然后左子树,然后右子树。每棵子树递归。

在迭代算法中,思路演变成,每到一个节点 A,就应该立即访问它。

因为,每棵子树都先访问其根节点。对节点的左右子树来说,也一定是先访问根。在 A 的两棵子树中,遍历完左子树后,再遍历右子树。因此,在访问完根节点后,遍历左子树前,要将右子树压入栈。


class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        stack < TreeNode*> st;
		vector< int > result;
		st.push(root);
		while (!st.empty())
		{
			TreeNode* node = st.top();
			st.pop();
			if (node != NULL)
				result.push_back(node->val);
			else
				continue;
			st.push(node->right);
			st.push(node->left);
	    }
		return result;
    }
};

后序遍历

题目

我们可以用与前序遍历相似的方法完成后序遍历。后序遍历与前序遍历相对称。

思路: 每到一个节点 A,就应该立即访问它。 然后将左子树压入栈,再次遍历右子树。
因为前序为中左右,上述完成后为中右左,结果逆序为左右中(后序遍历)

遍历完整棵树后,结果序列逆序即可。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) 
    {
        stack < TreeNode*> st;
		vector< int > result;
		st.push(root);
		while (!st.empty())
		{
			TreeNode* node = st.top();
			st.pop();
			if (node != NULL)
				result.push_back(node->val);
			else
				continue;			
			st.push(node->left);
			st.push(node->right);
	    }
        reverse(result.begin(), result.end());
		return result;
    }
};

中序遍历

题目

思路:每到一个节点 A,因为根的访问在中间,将 A 入栈。然后遍历左子树,接着访问 A,最后遍历右子树。

在访问完 A 后,A 就可以出栈了。因为 A 和其左子树都已经访问完成。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) 
    {
        stack < TreeNode*> st;
		vector< int > result;
		TreeNode* node = root;
		while (node||st.size())
		{
			while (node)
			{
				st.push(node);
				node = node->left;
			}
			if (st.size()) 
			{
				node = st.top(), st.pop();
				result.push_back(node->val);
				node = node->right;
				//左子树遍历完成,开始遍历右子树
			}
		}
		return result;
    }
};

更巧妙的模板题解

原文

思路:我们需要一个标志区分每个递归调用栈,这里使用nullptr来表示,代表要处理的节点。

刚刚在迭代的过程中,其实我们有两个操作,一个是处理:将元素放进result数组中,一个是访问:遍历节点。

为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,要先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

但中序遍历不是,这就造成了处理顺序和访问顺序是不一致的。所以在改进方法里用nullptr标记要处理的结点

前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;  //保存结果
        stack<TreeNode*> call;  //调用栈
        if(root!=nullptr) call.push(root);  //首先介入root节点
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();  //访问过的节点弹出
            if(t!=nullptr){
                if(t->right) call.push(t->right);  //右节点先压栈,最后处理
                if(t->left) call.push(t->left);
                call.push(t);  //当前节点重新压栈(留着以后处理),因为先序遍历所以最后压栈
                call.push(nullptr);  //在当前节点之前加入一个空节点表示已经访问过了
            }else{  //空节点表示之前已经访问过了,现在需要处理除了递归之外的内容
                res.push_back(call.top()->val);  //call.top()是nullptr之前压栈的一个节点,也就是上面call.push(t)中的那个t
                call.pop();  //处理完了,第二次弹出节点(彻底从栈中移除)
            }
        }
        return res;
    }
};
来源:力扣(LeetCode)

后序遍历

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> call;
        if(root!=nullptr) call.push(root);
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();
            if(t!=nullptr){
                call.push(t);  //在右节点之前重新插入该节点,以便在最后处理(访问值)
                call.push(nullptr); //nullptr跟随t插入,标识已经访问过,还没有被处理
                if(t->right) call.push(t->right);
                if(t->left) call.push(t->left);
            }else{
                res.push_back(call.top()->val);
                call.pop();
            }
        }
        return res;   
    }
};
来源:力扣(LeetCode)

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> call;
        if(root!=nullptr) call.push(root);
        while(!call.empty()){
            TreeNode *t = call.top();
            call.pop();
            if(t!=nullptr){
                if(t->right) call.push(t->right);
                call.push(t);  //在左节点之前重新插入该节点,以便在左节点之后处理(访问值)
                call.push(nullptr); //nullptr跟随t插入,标识已经访问过,还没有被处理
                if(t->left) call.push(t->left);
            }else{
                res.push_back(call.top()->val);
                call.pop();
            }
        }
        return res;
    }
};
来源:力扣(LeetCode)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值