二叉树四种遍历(迭代、递归)


题目来自leetcode:
前序: 前序遍历
中序: 中序遍历
后序: 后序遍历

1、颜色标记法

颜色标记法兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码

核心思想如下:

  • 使用颜色标记节点的状态,新节点为白色(0),已访问的节点为灰色(1)。
  • 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点(中序)依次入栈。
  • 如果遇到的节点为灰色,则将节点的值输出。

中序遍历:

vector<int> inorderTraversal(TreeNode* root) {
    using PIT = pair<int, TreeNode*>;
    vector<int> res;
    stack<PIT> ista({{0, root}});
    while(ista.size()){
        auto [type, node] = ista.top();  ista.pop();
        if(type == 0){
            if(node->right) ista.push({0, node->right});
            ista.push({1, node});
            if(node->left)  ista.push({0, node->left});
        }else{
            res.push_back(node->val);
        }
    }
    return res;
}

栈是一种 先进后出的结构,因此入栈顺序必须调整为遍历顺序的倒序。

如果是前序遍历,入栈顺序为 右,左,中

后序遍历,入栈顺序中,右,左

2、常规迭代

1)先序遍历

方法一(较简单)
访问栈顶节点,并将右子树、左子树按顺序入栈(先右再左,左子树比右子树先出栈)。左子树访问完才会访问右子树,各节点按照访问顺序分别入栈,转到第一步。
简化:
while(栈非空){
栈顶出栈、访问->右子树进栈->左子树进栈
}
【TIP】空节点入栈和不入栈分别有一种写法

//左右子树都入栈,空节点不入栈
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
       vector<int> res;
        if(!root) return res;
        stack<TreeNode*> Stack;
        Stack.push(root);
        while(!Stack.empty())
        {
            TreeNode* curNode=Stack.top();
            res.push_back(curNode->val);
            Stack.pop();
            if(curNode->right)
                Stack.push(curNode->right);
            if(curNode->left)
                Stack.push(curNode->left);
        }
        return res;
    }
};
//另一种写法,空节点入栈,可以处理空节点
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
       vector<int> res;
        if(!root) return res;
        stack<TreeNode*> Stack;
        Stack.push(root);
        while(!Stack.empty())
        {
            TreeNode* curNode=Stack.top();
            res.push_back(curNode->val);
            Stack.pop();
            if(curNode==NULL)
            	continue;
            Stack.push(curNode->right);
            Stack.push(curNode->left);
        }
        return res;
    }
};

方法二(通用法)
内循环:访问当前节点并将右子树入栈,往左子树方向一直访问下去。左子树访问到底退出循环,进入外循环。
外循环:将栈顶右子树取出,转到上一步。
数据结构——邓俊辉
因为左链已经访问完毕,所以只需将右子树入栈。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> S;
        vector<int> v;
        TreeNode* rt = root;
        while(rt || S.size()){
            while(rt){
                S.push(rt->right); //NULL有可能入栈,但并不影响
                v.push_back(rt->val);
                rt=rt->left;
            }
            rt=S.top();
            S.pop();
        }
        return v;        
    }
}

方法二修改版
用左链代替右子树入栈,出栈时,用节点右子树代替出栈节点。
在这里插入图片描述

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> S;
        vector<int> v;
        TreeNode* rt = root;
        while(rt || S.size()){
            while(rt){
                S.push(rt); 
                v.push_back(rt->val);
                rt=rt->left;
            }
            rt=S.top();
            S.pop();
            rt=rt->right;//rt可能为NULL,但并不影响
        }
        return v;    
}

2)中序遍历

方法一
在这里插入图片描述因为中序遍历得先访问左节点,所以沿左侧分支寻找最左节点时并没有访问根节点。因此,需要将根节点入栈(后进先出),出栈时同时访问右节点(将右节点设为根节点,完成此子任务)。
PS:由先序遍历方法二第二个版本推广得到,只有几行有变化。

class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> S;
        vector<int> v;
        TreeNode* rt = root;
        while(rt || S.size()){
            while(rt){
                S.push(rt);
                rt=rt->left;
            }
            rt=S.top();
            S.pop();
            v.push_back(rt->val);
            rt=rt->right; //转向右子树(可能为空,但不影响)
        }
        return v;        
    }

先序中序对比

class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> S;
        vector<int> v;
        TreeNode* rt = root;
        while(rt || S.size()){
            while(rt){
                S.push(rt);
 	 //先序遍历:v.push_back(rt->val);
                rt=rt->left;
            }
            rt=S.top();
            S.pop();
 //中序遍历:v.push_back(rt->val);
            rt=rt->right; //转向右子树(可能为空,但不影响)
        }
        return v;        
    }

方法二

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s1;
        vector<int> res;
        if(root!=NULL){
            while(root!=NULL || !s1.empty()){
                if(root!=NULL){
                    s1.push(root);
                    root=root->left;
                }
                else{
                    root=s1.top();
                    res.push_back(root->val);
                    s1.pop();
                    root=root->right;
                }
            }
        }
        return res;
    }
};

3)后序遍历

方法一
将前序遍历的中左右变为中右左,然后再将中右左反向!
反向即为先进后出,用第二个栈实现!!!

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s1;
        stack<TreeNode*> s2;
        vector<int> res;
        if(!root) return res;
        s1.push(root);
        while(!s1.empty()){
            root=s1.top();
            s1.pop();
            s2.push(root);
            if(root->left)
                s1.push(root->left);
            if(root->right)
                s1.push(root->right); 
        }
        while(!s2.empty()){
            res.push_back(s2.top()->val);
            s2.pop();
        }
        return res;
    }
};

方法二(辅助标记)
由中序遍历推广得到,前半部分几乎一样。
不同点在于:沿左链入栈到左链最低点后,并没有直接弹出栈顶,而是
先判断:
该节点是否有右节点 或者 右节点是否访问完毕

若右节点访问完毕 或者 无右节点,才开始访问操作:
弹出并访问该节点,并把该节点赋值给pre(辅助标记,表示前一个被访问的节点),令节点为空,开始对下一个栈顶的判断。

若右节点没有被访问(pre不是右节点):将右节点独立为子任务,遍历该子任务。

class Solution {
public:
        stack<TreeNode*> mystack;
        vector<int> ans;
        TreeNode* curr = root;
        TreeNode* pre = NULL;//辅助标记:前一个被访问的节点
        while(curr || !mystack.empty())
        {
            while(curr)
            {
                mystack.push(curr);
                curr = curr->left;
            }
            curr = mystack.top();
            //若前一个访问的是右节点(已访问过)或者没有右节点,则访问该节点
            if(!curr->right || pre == curr->right){
                mystack.pop();
                ans.push_back(curr->val);    
                pre = curr;//将该节点设为前一个访问的节点
                curr = NULL;
            }else{//右节点还没有被访问,则将右节点独立为子任务进行遍历
                curr = curr->right;
                pre = NULL;
            }
        }
        return ans;
}

3、递归版本

根据遍历顺序,分别把遍历函数放在左右子树递归语句前中后即可。

//前序:
class Solution {
public:
    vector<int> vec;
    vector<int> preorderTraversal(TreeNode* root) {
        if(root==NULL) return vec;
        vec.push_back(root->val);
        if(root->left) preorderTraversal(root->left);
        if(root->right) preorderTraversal(root->right);
        return vec;
    }
};
//中序:
 class Solution {
public:
    vector<int> vec;
    vector<int> preorderTraversal(TreeNode* root) {
        if(root==NULL) return vec;
        if(root->left) preorderTraversal(root->left);
        vec.push_back(root->val);
        if(root->right) preorderTraversal(root->right);
        return vec;
    }
};
//后序:
 class Solution {
public:
    vector<int> vec;
    vector<int> preorderTraversal(TreeNode* root) {
        if(root==NULL) return vec;
        if(root->left) preorderTraversal(root->left);
        if(root->right) preorderTraversal(root->right);
        vec.push_back(root->val);
        return vec;
    }
};

层序遍历

在这里插入图片描述

队列

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        int res=root->val;
        queue<TreeNode*> bfsque;
        bfsque.push(root);
        while(!bfsque.empty()){
            res=bfsque.front()->val;
            for(int i=bfsque.size();i>0;i--){
                TreeNode* tmp=bfsque.front();
                bfsque.pop();
                if(tmp->left)   bfsque.push(tmp->left);
                if(tmp->right)  bfsque.push(tmp->right);
            }
        }
        return res;
    }
};

非层次遍历做法:

class Solution {
public:
    int maxdepth=-1;
    int res=0;
    int findBottomLeftValue(TreeNode* root) {
        dfs(root,0);
        return res;
    }
    void dfs(TreeNode* root, int depth){
        if(!root)   return;
        if(depth>maxdepth){
            res=root->val;
            maxdepth=depth;
        }
        dfs(root->left,depth+1);
        dfs(root->right,depth+1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值