【Day 11 LeetCode】二叉树的遍历

一、二叉树的遍历

二叉树的遍历主要分为深度优先遍历广度优先遍历
深度优先是先往深处走,走到尽头返回;广度优先遍历是一层一层往下遍历。其中,深度优先遍历对应三种顺序,前序、中序、后序遍历,特点也很好记,就是根节点的位置。根节点位于前面就是前序,遍历顺序为 根节点-左子树-右子树;相应的,中序遍历的顺序是 左子树-根节点-右子树,后序遍历的顺序是 左子树-右子树-根节点。
深度优先遍历可以采用递归法或迭代法实现,时空复杂度都是O(n)O(n)O(n)(迭代法需要用栈来记录父节点,递归需要调用系统堆栈,所以空间复杂度是O(n)O(n)O(n))。
还有一种特殊的算法叫做Morris遍历,其思想是将空闲右子树的节点指向父节点,从而不需要记录父节点,空间复杂度为O(1)O(1)O(1)
下面介绍使用递归、迭代和Morris三种方法的深度优先遍历 和 迭代方法的广度优先遍历。

1、深度优先遍历–递归写法

前序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 前序  根-左-右
        ans.push_back(node->val);
        dfs(node->left, ans);
        dfs(node->right, ans);
    }
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

中序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 中序  左子树-根-右子树
        dfs(node->left, ans);
        ans.push_back(node->val);
        dfs(node->right, ans);
    }
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

后序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 后序  左-右-根
        dfs(node->left, ans);
        dfs(node->right, ans);
        ans.push_back(node->val);
    }
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

2、深度优先遍历–迭代写法

迭代写法就是要清楚用栈存节点的目的是为了保存根节点,再根据各个顺序的特点在恰当的时机保存节点值。
前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> q;
        vector<int> ans;
        while(root || !q.empty()){
            while(root){
                q.push(root);
                ans.push_back(root->val); // 先根节点
                root = root->left;
            }
            root = q.top();q.pop();
            root = root->right;
        }
        return ans;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> q; // 用栈来记录深度遍历的路径
        vector<int> ans;
        while(root || !q.empty()){
            // 找到这条路径的最左叶节点
            while(root){
                q.push(root);
                root = root->left;
            }
            root = q.top();q.pop();
            ans.push_back(root->val);
            root = root->right; // 左-根-右
        }
        return ans;
    }
};

后序遍历
当套用中序遍历的迭代版本模板,我始终没想清楚怎么在该基础上写出后序遍历版本。因为上面中序遍历的栈是记录根节点,而后序遍历是根节点的顺序是在最后,再弹出根节点开始右子树时,等遍历完右子树,根节点信息就已经丢失。
需要巧妙的转换一下,后序遍历顺序是左子树-右子树-根,反转一下就是 根-右子树-左子树。这下就与前序遍历类似了。就可以用一套遍历套路解决三种顺序了

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> q;
        vector<int> ans;
        while(root || !q.empty()){
            while(root){
                q.push(root);
                ans.push_back(root->val); // 先根节点
                root = root->right;
            }
            root = q.top();q.pop();
            root = root->left;
        }
        reverse(ans.begin(), ans.end()); // 反转
        return ans;
    }
};

3、深度优先遍历–Morris写法

Morris方法的思想是将空闲右子树的节点指向父节点,从而不需要开辟额外的空间记录父节点。需要搞清楚Morris的代码是在哪进入左子树、右子树。
前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        vector<int> ans;
        while(cur){
            if(cur->left){
                TreeNode* pre = cur->left;
                while(pre->right && pre->right != cur)
                    pre = pre->right;
                if(pre->right == nullptr){
                    pre->right = cur;
                    ans.push_back(cur->val);
                    cur = cur->left; // 进入左子树
                }else{
                    cur = cur->right;
                    pre->right = nullptr;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->right; // 进入右子树
            }
        }
        return ans;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        TreeNode* cur = root;
        while(cur){
            if(cur->left){
                TreeNode* pre = cur->left;
                // 找到非空左子树的“前驱节点”
                while(pre->right && pre->right!=cur)
                    pre = pre->right;
                if(pre->right==nullptr){
                    // 让“前驱节点”指向当前节点,本质是 记录该左子树的父节点
                    pre->right = cur;
                    cur = cur->left;
                }else{
                    ans.push_back(cur->val);
                    pre->right = nullptr;
                    cur = cur->right;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->right;
            }
        }
        return ans;
    }
};

后序遍历
与迭代版本遇到的问题一样,所以先讲前序遍历的版本左右互换,得到顺序为 根-右-左的结果,再将结果反转就得到后序遍历结果。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        vector<int> ans;
        while(cur){
            if(cur->right){
                TreeNode* pre = cur->right;
                while(pre->left && pre->left != cur)
                    pre = pre->left;
                if(pre->left==nullptr){
                    pre->left = cur;
                    ans.push_back(cur->val);
                    cur = cur->right; // 进入右子树
                }else{
                    cur = cur->left;
                    pre->left = nullptr;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->left; // 进入右子树
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

4、层序遍历

层序遍历思路比较清晰,使用队列记录每一层的节点。
迭代写法:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        if(root==nullptr)
            return ans;
        deque<TreeNode*> q;
        q.push_back(root);
        while(!q.empty()){
            vector<int> tmp;
            int n = q.size();
            while(n--){
                TreeNode* node = q.front();
                tmp.push_back(node->val);
                q.pop_front();
                if(node->left)
                    q.push_back(node->left);
                if(node->right)
                    q.push_back(node->right);
            }
            ans.push_back(tmp);
        }
        return ans;
    }
};

递归写法(通过在递归函数中传入深度参数实现):

class Solution {
    void dfs(TreeNode* node, vector<vector<int>> &ans, int depth){
        if(node==nullptr)
            return;
        if(depth==ans.size())
            ans.push_back(vector<int>());
        ans[depth].push_back(node->val);
        dfs(node->left, ans, depth+1);
        dfs(node->right, ans, depth+1);
    }
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        dfs(root, ans, 0);
        return ans;
    }
};

二、写在后面

今天完成了二叉树深度优先遍历三种顺序(前序、中序、后序)的三个方法(递归、迭代、Morris)的编写,还有广度优先遍历的递归和迭代写法。具体思路细节之后有时间补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值