【代码随想录】第8章 二叉树(上)

简介: 【代码随想录】第8章 二叉树

第8章 二叉树


1. 二叉树理论基础


二叉树种类


(1)满二叉树


(2)完全二叉树


(3)二叉搜索树


或称二叉查找树


中序遍历是递增序列



(4)平衡二叉搜索树



二叉树的存储方式


可以链式存储,也可以顺序存储


链式存储用指针,顺序存储用数组


顺序存储的元素在内存是连续分布的,链式存储则是通过指针把分布在散落在各个地址的结点串联在一起




用数组来存储二叉树如何遍历的呢?


如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。


二叉树的遍历


深度优先遍历:都有递归和迭代两种方法


  • 前序遍历:中左右


  • 中序遍历:左中右


  • 后序遍历:左右中



广度优先遍历


  • 层次遍历(迭代法)


二叉树的定义


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) {}
}


2. 二叉树的遍历


144. 二叉树的前序遍历


94. 二叉树的中序遍历


145. 二叉树的后序遍历


二叉树的递归遍历


递归三要素:


  1. 确定递归函数的参数和返回值:void preorder(TreeNode *root, vector<int>& res)


  1. 确定终止条件:if(cur == nullptr) return;


  1. 确定单层递归的逻辑


时间复杂度:O(n)


空间复杂度:O(n)


class Solution {
public:
    void preorder(TreeNode *root, vector<int> &res) {
        if (root == nullptr) return;
        res.push_back(root->val);    //中
        preorder(root->left, res);   //左
        preorder(root->right, res);  //右
    }
    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> res;
        preorder(root, res);
        return res;
    }
};


实现不同的遍历顺序通过修改单层递归的逻辑实现


二叉树的迭代遍历


通过栈实现


时间复杂度:O(n)


空间复杂度:O(n)


(1)前序遍历


class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        if(root == nullptr)  return res;
    s.push(root);  //压入根结点
        while (!s.empty()){  //入栈是 根右左
            TreeNode* node = s.top();  s.pop();
            res.push_back(node->val);                 //中
            if (node->right) stk.push(node->right);   //右
            if (node->left)  stk.push(node->left);    //左
        }
        return res;
    }
};


(2)中序遍历



class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while (cur != nullptr || !s.empty()) {
            if (cur != nullptr) {               // 指针来访问节点,访问到最底层
                s.push(cur);                    // 将访问的节点放进栈
                cur = cur->left;                // 左
            } 
            else {
                cur = s.top();   s.pop();       // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                res.push_back(cur->val);        // 中
                cur = cur->right;               // 右
            }
        }
        return res;
    }
};


(3)后序遍历


跟前序的很像,中序是三个当中差距最大的


class Solution {
public:
    //后续迭代最难,有些结点反复进出栈
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        if (root == nullptr)   return res;
        s.push(root);
        while(!s.empty()){
            auto node = s.top();   s.pop();
            res.push_back(node->val);             //中
            //这两步相对于前序  顺序换了
            if(node->left)  s.push(node->left);    //左
            if(node->right) s.push(node->right);   //右  
        }
        reverse(res.begin(), res.end()); // 将结果反转之后就是左右中的顺序了
        return res;
    }
};


二叉树的层序遍历


102. 二叉树的层序遍历


通过队列实现


class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector <vector <int>> res;
        if (root == nullptr)  return res;    //排除空二叉树情况
        queue <TreeNode*> q;
        q.push(root);    //根结点入队
        while (!q.empty()) {
            int currentLevelSize = q.size();   //记录当前层的结点个数
            res.push_back(vector <int>());
            for (int i = 1; i <= currentLevelSize; ++i) {
                auto node = q.front();             //获取队头元素
                q.pop();                           //删除队头元素
                res.back().push_back(node->val);   //往ret的最后一个容器中压元素
                if (node->left)  q.push(node->left);  
                if (node->right) q.push(node->right);
            }
        }
        return res;
    }
};


107. 二叉树的层序遍历 II【中等】



思路:把层序遍历的结果reverse一下再输出


reverse(res.begin(), res.end());


完整代码不记录


199. 二叉树的右视图【中等】



思路:层次遍历存储每层最后一个元素


class Solution {
public:
  vector<int> rightSideView(TreeNode* root) {
    vector<int> res;
    if (root == nullptr) return res;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
      int currentSize = q.size();
      for (int i = 1; i <= currentSize; ++i) {
        auto node = q.front();
        q.pop();
        if (i == currentSize)  res.push_back(node->val);
        if (node->left)  q.push(node->left);
        if (node->right) q.push(node->right);
      }
    }
    return res;
  }
};


637. 二叉树的层平均值【简单】



class Solution {
public:
  vector<double> averageOfLevels(TreeNode* root) {
    vector<double> res;
    if (root == nullptr) return res;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
      int currentSize = q.size();
      double num = 0;    //注意是double
      for (int i = 1; i <= currentSize; ++i) {
        auto node = q.front();
        q.pop();
        num += node->val;
        if (node->left) q.push(node->left);
        if (node->right) q.push(node->right);
      }
            res.push_back(num / currentSize);
    }
    return res;
  }
};


429. N 叉树的层序遍历【中等】



/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;
    Node() {}
    Node(int _val) {
        val = _val;
    }
    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/
class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>> res;
        if(root == nullptr) return res;
        queue<Node*> q;
        q.push(root);
        while(!q.empty()){
            res.push_back(vector<int> ());
            int currentSize = q.size();
            for(int i = 1; i <= currentSize; ++i){
                auto node = q.front();
                q.pop();
                res.back().push_back(node->val);
                for(auto i : node->children){
                    q.push(i);
                }
            }
        }
        return res;
    }
};


515. 在每个树行中找最大值【中等】



class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        vector<int> res;
        if(root == nullptr) return res;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            int currentSize = q.size();
            int nowMax = INT_MIN;
            for(int i = 1; i <= currentSize; ++i){
                auto node = q.front();
                q.pop();
                nowMax = max(node->val, nowMax);
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            res.push_back(nowMax);
        }
        return res;
    }
};


116. 填充每个节点的下一个右侧节点指针【中等】



/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;
    Node() : val(0), left(NULL), right(NULL), next(NULL) {}
    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
  Node* connect(Node* root) {
    if (root == nullptr) return root;
    queue<Node*>q;
    q.push(root);
    while (!q.empty()) {
      int current_level_size = q.size();
      for (int i = 1; i <= current_level_size; ++i) {
        auto node = q.front();
        q.pop();
        if (i < current_level_size) node->next = q.front();   //实现next指向
        else node->next = NULL;
        if (node->left) q.push(node->left);
        if (node->right) q.push(node->right);
      }
    }
        return root;
  }
};


117. 填充每个节点的下一个右侧节点指针 II【中等】


与116题做法一模一样,这里写了代码随想录的解法;116题写的是我自己习惯的解法。


class Solution {
public:
    Node* connect(Node* root) {   
        if(root == nullptr) return root;
        queue<Node*> q;
        q.push(root);
        while (!que.empty()) {
            int currentSize = que.size();
            Node* nodePre;
            Node* node;
            for (int i = 1; i <= currentSize; i++) {
                if (i == 0) {
                    nodePre = q.front(); // 取出一层的头结点
                    q.pop();
                    node = nodePre;
                    continue;
                } 
                node = q.front();
                q.pop();
                nodePre->next = node;    // 本层前一个节点next指向本节点
                nodePre = nodePre->next;
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
            nodePre->next = NULL;    // 本层最后一个节点指向NULL
        }
        return root;
    }
};


104. 二叉树的最大深度【简单】



class Solution {
public:
    int maxDepth(TreeNode* root) {
        int res = 0;
        if(root == nullptr) return res;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            int currentSize = q.size();
            for(int i = 1; i <= currentSize; ++i){
                auto node = q.front();
                q.pop();
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            res++;
        }
        return res;
    }
};


111. 二叉树的最小深度【简单】



class Solution {
public:
    int minDepth(TreeNode* root) {
        int res = 0;
        if (root == nullptr) return res;
        queue <TreeNode*> q;
        q.push(root);    //根结点入队
        while (!q.empty()) {
            int currentLevelSize = q.size();   //记录当前层的结点个数
            res++;
            for (int i = 1; i <= currentLevelSize; i++) {  //队列弹出当前层所有元素   并压入下一层所有元素
                auto node = q.front();
                q.pop();
                if (node->left == nullptr && node->right == nullptr) {
                    return res;
                }
                if (node->left)  q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        return res;
    }
};


3. 翻转二叉树


226. 翻转二叉树【简单】


相同题目:剑指 Offer 27. 二叉树的镜像



迭代:


广度优先遍历,即层次遍历


class Solution {
public:
  TreeNode* invertTree(TreeNode* root) {
    if (root == nullptr) return root;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
      auto node = q.front();              
      q.pop();
            swap(node->left, node->right);
      if (node->left) q.push(node->left); 
      if (node->right) q.push(node->right);
    }
    return root;
  }
};


深度优先遍历就是把queue换成stack


递归:


递归三部曲:


  1. 确定递归函数的参数和返回值:TreeNode* invertTree(TreeNode* root)


  1. 确定终止条件:if(root == nullptr) return NULL


  1. 确定单层递归的逻辑


class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == nullptr) return NULL;
        swap(root->left, root->right);
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};


上面是递归的前序遍历


递归的中序遍历这里是不行的


4. 对称二叉树


101. 对称二叉树【简单】


相同题目:剑指 Offer 28. 对称的二叉树



迭代:


class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root == nullptr) return true;
        queue<TreeNode*> q;
        q.push(root->left);
        q.push(root->right);
        while(!q.empty()){
            auto leftNode = q.front(); q.pop();
            auto rightNode = q.front(); q.pop();
            if(leftNode == nullptr && rightNode == nullptr) continue;   //少了报错
            if(leftNode == nullptr && rightNode != nullptr) return false;
            if(leftNode != nullptr && rightNode == nullptr) return false;
            if(leftNode->val != rightNode->val) return false;
            q.push(leftNode->left);
            q.push(rightNode->right);
            q.push(leftNode->right);
            q.push(rightNode->left);
        }
        return true;
    }
};


同样的queue改成stack也可以,其他原封不动


递归:


递归三部曲:


  1. 确定递归函数的参数和返回值:bool compare(TreeNode* left, TreeNode* right)


  1. 确定终止条件


  1. 确定单层递归逻辑


class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right){  //1.确定递归函数的参数
        //2.确定终止条件
        if(left == nullptr && right == nullptr) return true;
        else if(left == nullptr && right != nullptr) return false;
        else if(left != nullptr && right == nullptr) return false;
        else if(left->val != right->val) return false;
        //3.返回值
        return compare(left->left, right->right) && compare(left->right, right->left);
    }
    bool isSymmetric(TreeNode* root) {
        if(root == nullptr) return true;
        return compare(root->left, root->right);
    }
};


100. 相同的树【简单】



递归:


class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == nullptr && q == nullptr) return true;
        else if(p == nullptr && q != nullptr) return false;
        else if(p != nullptr && q == nullptr) return false;
        else if(p->val != q->val) return false;
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};


迭代:


class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == nullptr && q == nullptr) return true;
        if(p == nullptr || q == nullptr) return false;
        queue<TreeNode*> que;
        que.push(p);
        que.push(q);
        while(!que.empty()){
            auto pNode = que.front(); que.pop();
            auto qNode = que.front(); que.pop();
            if(pNode->val != qNode->val)  return false;
            auto pLeft = pNode->left, pRight = pNode->right, qLeft = qNode->left, qRight = qNode->right;
            if(pLeft == nullptr && qLeft != nullptr) return false;
            else if(pLeft != nullptr && qLeft == nullptr) return false;
            else if(pLeft != nullptr && qLeft != nullptr){
                que.push(pLeft);
                que.push(qLeft);
            }
            if(pRight == nullptr && qRight != nullptr) return false;
            else if(pRight != nullptr && qRight == nullptr) return false;
            else if(pRight != nullptr || qRight != nullptr) {
                que.push(pRight);
                que.push(qRight);
            }
        }
        return true;
    }
};



目录
相关文章
|
1天前
|
存储 机器学习/深度学习 人工智能
打破硬件壁垒!煎饺App:强悍AI语音工具,为何是豆包AI手机平替?
直接上干货!3000 字以上长文,细节拉满,把核心功能、使用技巧和实测结论全给大家摆明白,读完你就知道这款 “安卓机通用 AI 语音工具"——煎饺App它为何能打破硬件壁垒?它接下来,咱们就深度拆解煎饺 App—— 先给大家扒清楚它的使用逻辑,附上“操作演示”和“🚀快速上手不踩坑 : 4 条核心操作干货(必看)”,跟着走零基础也能快速上手;后续再用真实实测数据,正面硬刚煎饺 App的语音助手口令效果——创建京东「牛奶自动下单神器」口令 ,从修改口令、识别准确率到场景实用性,逐一测试不掺水,最后,再和豆包 AI 手机语音助手的普通版——豆包App对比测试下,简单地谈谈煎饺App的能力边界在哪?
|
3天前
|
云安全 监控 安全
|
8天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1050 5
|
10天前
|
机器学习/深度学习 人工智能 数据可视化
1秒生图!6B参数如何“以小博大”生成超真实图像?
Z-Image是6B参数开源图像生成模型,仅需16GB显存即可生成媲美百亿级模型的超真实图像,支持中英双语文本渲染与智能编辑,登顶Hugging Face趋势榜,首日下载破50万。
706 42
|
14天前
|
人工智能 Java API
Java 正式进入 Agentic AI 时代:Spring AI Alibaba 1.1 发布背后的技术演进
Spring AI Alibaba 1.1 正式发布,提供极简方式构建企业级AI智能体。基于ReactAgent核心,支持多智能体协作、上下文工程与生产级管控,助力开发者快速打造可靠、可扩展的智能应用。
1140 41
|
14天前
|
人工智能 前端开发 算法
大厂CIO独家分享:AI如何重塑开发者未来十年
在 AI 时代,若你还在紧盯代码量、执着于全栈工程师的招聘,或者仅凭技术贡献率来评判价值,执着于业务提效的比例而忽略产研价值,你很可能已经被所谓的“常识”困住了脚步。
871 71
大厂CIO独家分享:AI如何重塑开发者未来十年
|
10天前
|
存储 自然语言处理 测试技术
一行代码,让 Elasticsearch 集群瞬间雪崩——5000W 数据压测下的性能避坑全攻略
本文深入剖析 Elasticsearch 中模糊查询的三大陷阱及性能优化方案。通过5000 万级数据量下做了高压测试,用真实数据复刻事故现场,助力开发者规避“查询雪崩”,为您的业务保驾护航。
526 31
|
17天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
975 59
Meta SAM3开源:让图像分割,听懂你的话
|
2天前
|
机器学习/深度学习 传感器 自动驾驶
具身智能核心突破:物理模拟器与世界模型协同技术拆解
本文系统综述了物理模拟器与世界模型在具身智能发展中的协同作用,提出五级智能机器人分类体系(IR-L0至IR-L4),分析其在运动、操作与交互中的进展,并对比主流仿真平台与世界模型架构,探讨其在自动驾驶与关节机器人中的应用及未来挑战。
164 113