day14 | 二叉树基础、前中后遍历

82 阅读3分钟

二叉树基础

  • 平衡二叉搜索树(AVL):map, set, multimap, multiset底层实现都是AVL树,增删操作时间复杂度为logn
  • 树的顺序存储:父节点数组下标为i,左孩子为2i+1、右孩子为2i+2
  • 遍历方式:深度优先遍历(前中后序【递归法、迭代法】)、广度优先遍历(层次遍历【迭代法】)
  • 二叉树节点的定义
struct TreeNode{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}; 

递归遍历

题目:144 145 94

要点

  1. 确定递归函数的参数和返回值
  2. 确定中止条件
  3. 单层递归的逻辑

前序遍历

class Solution {
public:
    void preorder(TreeNode* cur, vector<int>& result) {
        if (cur == nullptr) return;           //bug! cur本身就为空了,不存在cur->val
                                              //相当于遍历,每次都还要访问叶节点的左右孩子,才返回
        result.push_back(cur->val);
        preorder(cur->left, result);
        preorder(cur->right, result);
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        preorder(root, res);
        return res;
    }
};

后序遍历

void postorder(TreeNode* cur, vector<int>& result) {
    if (cur == nullptr) return;

    postorder(cur->left, result);
    postorder(cur->right, result);
    result.push_back(cur->val);
}

中序遍历

void inorder(TreeNode* cur, vector<int>& result) {
    if (cur == nullptr) return;

    inorder(cur->left, result);
    result.push_back(cur->val);
    inorder(cur->right, result);
}

总结

递归遍历主要是递归函数的三要素,将结果存放在递归函数参数中(用引用的方式)

迭代遍历

题目:144 145 94

要点

  • 遍历树的逻辑可分为:访问节点、处理节点;前序遍历正好两个逻辑是同时的
  • 中左右,先把右分支的压入栈,再压左分支
  • 迭代法的后序遍历,通过将前序遍历代码改为中右左,再翻转得到

  • 中序遍历,访问+处理两个逻辑分为,cur指针来访问节点、栈来处理节点;栈中保存的都是经过访问但未处理的节点;cur指针为空、栈不为空时,说明访问到叶子节点的左右孩子了,需要弹出栈的最上元素,进行输出。

前序遍历(迭代法)

class Solution {
public:
   
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;            // 不是int类型,不然丢失父子节点的关系

        if (root == nullptr) return res;
        st.push(root);

        while (!st.empty()) {           // 每个循环处理一个节点
            TreeNode* cur = st.top();
            res.push_back(cur->val);
            st.pop();

            if (cur->right) st.push(cur->right);
            if (cur->left) st.push(cur->left);
        }
        return res;

    }
};

后序遍历(迭代法)

class Solution {
public:
   
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;

        if (root == nullptr) return res;
        st.push(root);

        while (!st.empty()) {          //中右左
            TreeNode* cur = st.top();
            res.push_back(cur->val);
            st.pop();

            if (cur->left) st.push(cur->left);           
            if (cur->right) st.push(cur->right);
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

中序遍历(迭代法)

class Solution {
public:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;   //stack里放已访问但是未处理的元素

        TreeNode* cur = root;
        while (cur != nullptr || !st.empty()) {  // 既无需要访问的元素,也无待处理的元素,就结束了
            if (cur != nullptr) {
                st.push(cur);
                cur = cur->left;
            }else {
                cur = st.top();
                st.pop();
                res.push_back(cur->val);
                cur = cur->right;
            }
        }
        
        return res;
    }
};

总结

迭代法进行前中后遍历,前序由于访问和处理同时,相比于中序不需要指针去单独作访问操作。

统一风格的迭代法遍历

class Solution {
public:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;

        if (root != nullptr) st.push(root);
        
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            
            if (node != nullptr) {
                if (node->right) st.push(node->right);
                st.push(node);
                st.push(nullptr);
                if (node->left) st.push(node->left);
            }else {
                node = st.top();
                st.pop();
                res.push_back(node->val);
            }
        }       
        
        return res;
    }
};
  • 尽管访问和处理逻辑顺序不同,不用cur指针来访问,使用NULL作为标记位,每当st.top()NULL时,下一个元素为要处理的元素
  • 要处理的元素和要访问的元素放在同一个栈中,用标记位区分
  • 入栈顺序和迭代法的前序遍历一样,反着来
  • 先入根节点node,再入NULL;这样出来时,NULL的下一个top()元素就是要处理的元素