1. 二叉树基础理论

二叉树种类:

满二叉树、完全二叉树(仅底层节点未填满,未填满的话节点必须在左边)

二叉搜索树(值:左>中>右)、平衡二叉搜索树(左右子树深度差不超过1)

二叉树存储方式

链式存储&顺序存储

链式存储图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

顺序存储图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二叉树的遍历方式:

  • 深度优先遍历

    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历

    • 层次遍历(迭代法)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二叉树的定义

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

2. 题目打卡

DFS:

144.二叉树的前序遍历
145.二叉树的后序遍历
94.二叉树的中序遍历
递归法:

递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!

  1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

    class Solution {//前序遍历
    public:
        void traversal(TreeNode* cur, vector<int>& vec) {
            if (cur == NULL) return;
            vec.push_back(cur->val);    // 中
            traversal(cur->left, vec);  // 左
            traversal(cur->right, vec); // 右
        }
        vector<int> preorderTraversal(TreeNode* root) {
            vector<int> result;
            traversal(root, result);
            return result;
        }
    };
    
迭代法:
  1. 前序遍历:是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
    为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。

    class Solution {
    public:
        vector<int> preorderTraversal(TreeNode* root) {
            stack<TreeNode*> st;
            vector<int> result;
            if (root == NULL) return result;
            st.push(root);
            while (!st.empty()) {
                TreeNode* node = st.top();                       // 中
                st.pop();
                result.push_back(node->val);
                if (node->right) st.push(node->right);           // 右(空节点不入栈)
                if (node->left) st.push(node->left);             // 左(空节点不入栈)
            }
            return result;
        }
    };
    
  2. 中序遍历:为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作:
    处理:将元素放进result数组中
    访问:遍历节点
    分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
    那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了
    处理顺序和访问顺序是不一致的。

    那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

    class Solution {
    public:
        vector<int> inorderTraversal(TreeNode* root) {
            vector<int> result;
            stack<TreeNode*> st;
            TreeNode* cur = root;
            while (cur != NULL || !st.empty()) {
                if (cur != NULL) { // 指针来访问节点,访问到最底层
                    st.push(cur); // 将访问的节点放进栈
                    cur = cur->left;                // 左
                } else {
                    cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                    st.pop();
                    result.push_back(cur->val);     // 中
                    cur = cur->right;               // 右
                }
            }
            return result;
        }
    };
    
  3. 后序遍历:再来看后序遍历,先序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。

统一迭代法

就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法可以叫做空指针标记法

class Solution {//中序遍历
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)

                st.push(node);                          // 添加中节点
                st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }
};

BFS

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec;
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};
102.二叉树的层序遍历
107.二叉树的层次遍历
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐