二叉树的各种遍历方法(递归,迭代)

0.碎碎念

        什么序遍历就是“中”在什么地方序遍历就是“左右”,序遍历就是“”。

        为了便于理解所谓的“序”,你可以这样理解:

以前序(中左右为例子)

        首先从 1 开始(),遍历到 2 (),这时相当于原图变成了:

处理完这“左大树”,才会去处理“右大树

        同时,左大树可以再分为“左中树”->"左小树"(如果树够复杂),直到分成了        这样的无孩子节点,就可以结束一个“左小树”的遍历了。

        聪明的你或许会发现,这和递归很像?的确,这种一层套一层无疑是递归的主场,但利用栈的话用迭代也能实现,但这是提升项了,最好也能掌握,这能拓展你的思维。

        0.1关于节点的构造

                这里都用的是Leetcode的构造方式:
 

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

                后面三行是构造函数,我后面会单独出一篇文章讲讲构造函数(望捧场!)

1.递归大类

        递归类遍历是二叉树遍历中最简单易懂最容易实现的,那就不需要太多废话是吧()

        1.1.前序遍历

        中左右决定了每一次访问新树(左右孩子)都要先把当前节点输出,那代码非常好写:

class Solution {
public:
vector<int> ans;
void travel(TreeNode* x){
    if (x == nullptr)
     return;  // 如果当前节点为空,直接返回
    ans.push_back(x->val);     // 将当前节点的值加入到结果列表中
    travel(x->left);           // 递归地遍历左子节点
    travel(x->right);          // 递归地遍历右子节点
}
    vector<int> preorderTraversal(TreeNode* root) {
        travel(root);
        return ans;
    }
};

        其中,末尾的节点可以看成一个左右孩子都是空节点的树,所以在程序看来树是长这样:

访问到了空节点就代表到底了,该输出了。这能帮助你理解后面的迭代法。

        1.2.中序遍历

        同理可得:

class Solution {
public:
vector<int> ans;
void travel(TreeNode* x){
    if(x == nullptr)
    return;
    travel(x->left);
    ans.push_back(x->val);
    travel(x->right);
}
    vector<int> inorderTraversal(TreeNode* root) {
        travel(root);
        return ans;
    }
};

        1.3.后序遍历

class Solution {
public:
vector<int> ans;
void travel(TreeNode* x){
    if(x == nullptr)
    return;
    travel(x->left);
    travel(x->right);
    ans.push_back(x->val);
}
    vector<int> postorderTraversal(TreeNode* root) {
        travel(root);
        return ans;
    }
};

        1.4.总结

        可以看出,递归法非常的简单粗暴且明了,前中后序只需要更改几行代码便可实现,这是因为我们可以让左子树,中节点,右子树来按照我们想要的顺序来处理,且互不干扰

2.迭代大类

        2.1.局限

        如果是前序遍历的话,我们可以用如下代码简单实现:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> travel;
        vector<int> ans;
        travel.push(root);
        while(!(travel.empty())){
            TreeNode* temp = travel.top();
            travel.pop();
            if(temp == nullptr)
            continue;
            travel.push(temp->right);
            travel.push(temp->left);
            ans.push_back(temp->val);
        }
        return ans;
    }
};

        在每次处理一个新元素时,我们都会先输出这个元素,因为是前序遍历(中左右),不管有没有左右子树都会先输出处理元素。

                                                                那中序,后序呢?

以中序为例,是左中右,你的操作元素(中)是要在左子树全部操作完再输出的!

这就代表你无法像类似前序这样只用栈来进行中序遍历,因为你不知道该什么时候输出中节点!

那解决方法也很简单 ------------------------标记中节点!

        2.2中节点标记法

        我们知道了,之所以无法用栈来处理中后序的原因是不知道什么时候输出中结点,那么我们只要找到一种方法标记中节点即可。

        这里我推荐在每次处理中节点后压入一个空节点

为什么这样会比较好呢?

        遍历二叉树我们会输出答案的标志是什么?必然是遍历到尾了----访问到了空节点,所以我们在中节点后压入一个空节点,就能让我们在左子树全部处理完后正确的输出空节点(中序),而且不用单独写一个判断。

        当然,你闲的没事也可以压入一个值为114514的节点,只要能起到标记作用就好。

那这样的话,代码就很简单了:

        2.2.1标记法前序

                        

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> a;
        vector<int> ans;
        a.push(root);
        if(!root)
        return ans;
        while(a.empty() == false){
            TreeNode* cur = a.top();
            a.pop();
            if(cur == nullptr){
                cur = a.top();
                a.pop();
                ans.push_back(cur->val);
            }else{
                if(cur->right)a.push(cur->right);
                if(cur->left)a.push(cur->left);
                a.push(cur);
                a.push(nullptr);
            }
        }
        return ans;
    }
};

        2.2.2标记法中序

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> a;
        vector<int> ans;
        a.push(root);
        if(!root)
        return ans;
        while(a.empty() == false){
            TreeNode* cur = a.top();
            a.pop();
            if(cur == nullptr){
                cur = a.top();
                a.pop();
                ans.push_back(cur->val);
            }else{
                if(cur->right)a.push(cur->right);
                a.push(cur);
                a.push(nullptr);
                if(cur->left)a.push(cur->left);
            }
        }
        return ans;
    }
};

        2.2.3标记法后序

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> a;
        vector<int> ans;
        a.push(root);
        if(!root)
        return ans;
        while(a.empty() == false){
            TreeNode* cur = a.top();
            a.pop();
            if(cur == nullptr){
                cur = a.top();
                a.pop();
                ans.push_back(cur->val);
            }else{
                a.push(cur);
                a.push(nullptr);
                if(cur->right)a.push(cur->right);
                if(cur->left)a.push(cur->left);
            }
        }
        return ans;
    }
};

        至于为什么前序(中左右)的代码中节点是最后压入的,因为栈是先进后出,后压入才能做到第一个操作的是中节点!

3.总结

        总体上二叉树遍历就这几种,但是还有一种O(1)的逆天遍历:莫里斯(Morris)遍历

大家可以自行去了解。总体来说,个人觉得递归法是最优解,但指不定哪个HR就问你迭代呢()  

                有不足之处还望多多指正!

题目链接:

        144. 二叉树的前序遍历

        94. 二叉树的中序遍历

        145. 二叉树的后序遍历

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值