【数据结构】二叉爆炸

news2025/4/6 22:26:02

【数据结构】二叉爆炸

按照惯例整点抽象的,贴上这篇博客的名字由来:

image-20240413233154850

言归正传,本篇博客介绍二叉树的构造方式、前中后序遍历、层序遍历以及代码随想录中二叉树章节的相关题目:

代码随想录 (programmercarl.com)

一、啥是二叉树

1.二叉树是什么?

百度百科上的解释是这样的:

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个节点 。

太长不看,简单来说,二叉树就是这个:

image-20240414004106540

单亲爸爸带俩娃(划掉),一个根节点带着俩孩子。左边的孩子是“左子树”,右边的孩子是“右子树”。

下面是一些二叉树相关的定义,下面关于二叉树的算法题中可能会用到:

①节点:包含一个数据元素及若干指向子树分支的信息。

②节点的度:一个节点拥有子树的数目称为节点的度。

③叶子节点:也称为终端节点,没有子树的节点或者度为零的节点。

④分支节点:也称为非终端节点,度不为零的节点称为非终端节点。

⑤树的度:树中所有节点的度的最大值。

⑥节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层。

⑦树的深度:也称为树的高度,树中所有节点的层次最大值称为树的深度。

⑧有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树。

⑨无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树。

⑩森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成。

2.二叉树有啥用?

  1. 数据存储和检索:二叉搜索树(BST)是一种常见的数据结构,用于存储和检索数据。在BST中,每个节点的左子树中的所有节点都小于该节点,右子树中的所有节点都大于该节点,这使得查找、插入和删除操作的平均时间复杂度为O(log n),其中n是树中节点的数量。
  2. 数据库索引:许多数据库系统使用二叉树或其变种(如B树、B+树)来加速数据检索。这些树结构允许在大量数据中快速找到所需的记录。
  3. 文件系统:许多操作系统使用树状结构来组织文件和目录。文件系统中的每个目录都可以视为一个节点,其中包含文件和其他子目录。
  4. 图形用户界面(GUI):许多GUI工具包使用树形结构来组织用户界面元素。例如,菜单和文件资源管理器通常使用树形结构来表示层级结构。

3.特殊的二叉树!

满二叉树

所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上的二叉树。

这张图上的树就是一棵满二叉树。

image-20240414004106540

完全二叉树

在满二叉树中,从最后一个结点开始,连续去掉任意个结点得到的二叉树。即最后一层所有节点靠左的二叉树。

如图所示:

image-20240414005958254

二、咋遍历二叉树?

1. 深度优先搜索(前,中,后序遍历)

从根节点开始,沿着树的深度尽可能远的搜索树的分支。当达到树的最深处时,再回溯到上一级节点继续搜索,直到遍历完整棵树。

  1. 前序遍历(Preorder Traversal):以根节点、左子树、右子树的顺序遍历二叉树。
  2. 中序遍历(Inorder Traversal):以左子树、根节点、右子树的顺序遍历二叉树。
  3. 后序遍历(Postorder Traversal):以左子树、右子树、根节点的顺序遍历二叉树。

接下来我们就看看前中后序遍历的代码实现,此处均以leetcode上题目为模板。

前序遍历

image-20240414140805767

递归遍历:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

    public void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        preorder(root.left, result);
        preorder(root.right, result);
    }
}

迭代法遍历:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
    }
}
中序遍历

image-20240414140825676

递归遍历:

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        inorder(root.left, list);
        list.add(root.val);             // 注意这一句
        inorder(root.right, list);
    }
}

迭代法遍历:

class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
    Stack<TreeNode> st = new Stack<>();
    if (root != null) st.push(root);
    while (!st.empty()) {
        TreeNode node = st.peek();
        if (node != null) {
            st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
            if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
            st.push(node);                          // 添加中节点
            st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。

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

image-20240414140838854

递归遍历:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        postorder(root, res);
        return res;
    }

    void postorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        postorder(root.left, list);
        postorder(root.right, list);
        list.add(root.val);             // 注意这一句
    }
}

迭代法遍历:

class Solution {
   public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)         
                               
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
   }
}
如何从中序和后序倒推出二叉树

先别急着看层序遍历,既然二叉树可以用这几种方式遍历,那么一定可以从这几种方式中倒推出二叉树的构造!

来看看这道题目:

image-20240414133320316

首先复习一下中序遍历与后序遍历的遍历方式:

中序:左中右

后序:左右中

如果光看中序遍历数组,无法找到中间节点,光看后序遍历的数组,无法分割左右孩子,于是将两

种遍历方式结合起来,先从后序中找到中间节点的值,再将中序数组的左右孩子分开。

思路大概是:

第一步:如果数组大小为零的话,说明是空节点了。

第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中

序数组)

第五步:切割后序数组,切成后序左数组和后序右数组

第六步:递归处理左区间和右区间

接下来出场的是递归三部曲:

  1. 首先确定递归函数返回值和参数:返回treenode,参数为两个数组及其区间。

  2. 而后确定递归终止条件:当后序数组为空时,证明已经没有新的中间节点了,则返回空。

  3. 最后确定单层递归逻辑:

在本题的单层递归中,我们需要做的事情稍显复杂:

  1. 首先,我们需要从后序数组中找到最后一个值,即当前子树的根节点。
  2. 而后,在中序数组中找到该节点,并保存其索引mid。
  3. 以mid分割中序数组,找到左子树和右子树。
  4. 分割后序数组,找到中序数组中左子树与右子树对应的左子树和右子树区间。
  5. 将计算好的值传入下层递归中。
  6. 最后返回根节点。

AC代码如下:

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(postorder.length == 0 || inorder.length == 0)
            return null;
        return Build(inorder, 0, inorder.length, postorder, 0, postorder.length);
    }
    public TreeNode Build(int[] inorder, int inStart, int inEnd, int[] postorder, int posStart, int posEnd){
        if(posStart == posEnd){
            return null;
        }
        //后序遍历的最后一个值,即为当前子树的根节点
        int rootVal = postorder[posEnd - 1];
        TreeNode root = new TreeNode(rootVal);
        int mid;
        //从中序遍历数组中找到root结点
        for (mid = inStart; mid < inEnd; mid++){
            if(inorder[mid] == rootVal){
                break;
            }
        }

        //分割中序数组
        int lInStart = inStart; 
        int lInEnd = mid;
        int rInStart = mid + 1;
        int rInEnd = inEnd;

        //分割后序数组
        int lPosStart = posStart;
        int lPosEnd = posStart + (mid - inStart);
        int rPosStart = lPosEnd;
        int rPosEnd = posEnd - 1;
        
        //传入下层递归
        root.left = Build(inorder, lInStart, lInEnd,  postorder, lPosStart, lPosEnd);
        root.right = Build(inorder, rInStart, rInEnd, postorder, rPosStart, rPosEnd);

        return root;
    }  
}

2. 广度优先搜索(层序遍历)

从根节点开始,沿着树的宽度遍历树的节点。首先访问根节点,然后依次访问与根节点相邻的节点,直到遍历完整层的节点后再继续向下一层移动。

借用队列Queue来实现层序遍历,将每一层的内容放入队列中,在下一次遍历时,对队列中从头到

尾的每个元素进行检查,如果存在左右子节点,则入队,依次进行下去即可完成层序遍历。

例:对于如下二叉树,某一次的出队入队过程如下:

image-20240414140709491

示例代码如下:

class Solution {
    public List<List<Integer>> ans = new LinkedList<>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null){
            return ans;
        }
        Queue<TreeNode> que = new LinkedList<>();
        que.add(root);
        while(!que.isEmpty()){
            int len = que.size();
            List<Integer> ls = new LinkedList<>();
            
            while(len > 0){
                TreeNode temp = que.poll();
                ls.add(temp.val);
                if(temp.left != null){
                    que.add(temp.left);
                }
                if(temp.right != null){
                    que.add(temp.right);
                }
                len--;
            }
            ans.add(ls);
        }
        return ans;
    }
}

学完层序遍历可以一口气A完以下10题:

  • 102.二叉树的层序遍历
  • 107.二叉树的层次遍历II
  • 199.二叉树的右视图
  • 637.二叉树的层平均值
  • 429.N叉树的层序遍历
  • 515.在每个树行中找最大值
  • 116.填充每个节点的下一个右侧节点指针
  • 117.填充每个节点的下一个右侧节点指针II
  • 104.二叉树的最大深度
  • 111.二叉树的最小深度

我们选取一道典型的题目来看看:

image-20240414141141829

这个题和116. 填充每个节点的下一个右侧节点指针两道题,用同一套代码直接解决了…

层序遍历真好用(喜)

用队列来进行层序遍历,再用一个list维护每一层的代码,在遍历完某一层后链接该层的next指针即可。

值得一提的是链接时用的while代码块,单独掏出来看看:

while(ls.size() > 1){
    Node pf = ls.removeLast();
    Node pb = ls.getLast();
    pb.next = pf;
}

首先把最后的节点保存,然后在get到倒数第二个节点,因为下一次遍历仍然会用到倒数第二个节

点,故而不能remove掉,再进行连接,结束条件是list中只剩下一个节点。

AC代码如下:

class Solution {
    public Node connect(Node root) {
        if(root == null){
            return root;
        }
        Queue<Node> que = new LinkedList<>();
        List<Node> ls = new LinkedList<>();
        que.add(root);
        while(!que.isEmpty()){
            int len = que.size();
            ls.clear();
            while(len > 0){
                Node tmp = que.poll();
                ls.add(tmp);
                if(tmp.left != null) que.add(tmp.left);
                if(tmp.right != null) que.add(tmp.right);
                len--;
            }
            while(ls.size() > 1){
                Node pf = ls.removeLast();
                Node pb = ls.getLast();
                pb.next = pf;
            }
        }
        return root;
    }
}

这个代码使用了List进行辅助,导致时间和空间都变得复杂了,唯一的好处是好理解,接下来登场的是优化代码~

public Node connect(Node root) {
    if(root == null){
        return root;
    }
    Queue<Node> que = new LinkedList<>();
    que.add(root);
    while (!que.isEmpty()) {
        int len = que.size();
        //前一个节点
        Node pre = null;
        while(len > 0){
            //出队
            Node node = que.poll();
            //如果pre为空就表示node节点是这一行的第一个,
            //没有前一个节点指向他,否则就让前一个节点指向他
            if (pre != null) {
                pre.next = node;
            }
            //然后再让当前节点成为前一个节点
            pre = node;
            //左右子节点如果不为空就入队
            if (node.left != null) que.add(node.left);
            if (node.right != null) que.add(node.right);
            len--;
        }
    }
    return root;
}

将内层的循环拿出来看:

定义一个pre节点,用于保存每次的节点,依次向后遍历,将每次出队的节点保存为node,若首次

插入,则将第一个node定义为pre,然后再进行后面的操作。

int len = que.size();
//前一个节点
Node pre = null;
while(len > 0){
    //出队
    Node node = que.poll();
    //如果pre为空就表示node节点是这一行的第一个,
    //没有前一个节点指向他,否则就让前一个节点指向他
    if (pre != null) {
        pre.next = node;
    }
    //再让当前节点成为前一个节点
    pre = node;
    if (node.left != null) que.add(node.left);
    if (node.right != null) que.add(node.right);
    len--;
}

三、结语

本篇博客介绍了树的基础知识以及树的各种遍历方式的应用,在接下来的学习中,还可能会接触到较为复杂的树,如二叉搜索树,平衡二叉树等。

本篇博客代码以及部分解析参考:

,则将第一个node定义为pre,然后再进行后面的操作。

int len = que.size();
//前一个节点
Node pre = null;
while(len > 0){
    //出队
    Node node = que.poll();
    //如果pre为空就表示node节点是这一行的第一个,
    //没有前一个节点指向他,否则就让前一个节点指向他
    if (pre != null) {
        pre.next = node;
    }
    //再让当前节点成为前一个节点
    pre = node;
    if (node.left != null) que.add(node.left);
    if (node.right != null) que.add(node.right);
    len--;
}

三、结语

本篇博客介绍了树的基础知识以及树的各种遍历方式的应用,在接下来的学习中,还可能会接触到较为复杂的树,如二叉搜索树,平衡二叉树等。

本篇博客代码以及部分解析参考:

代码随想录 (programmercarl.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1593583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【150套】基于SSM框架的Java毕业设计开发实战项目(附源码+演示视频+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f9e1;今天给大家分享150的Java毕业设计&#xff0c;基于ssm框架&#xff0c;这些项目都经过精心挑选&#xff0c;涵盖了不同的实战主题和用例&#xff0c;可做毕业设计和课程…

服务器docker应用一览

文章目录 一、需求概况二、业务流程三、运行效果四、实现过程1. 基础前提2. 源码放送3.核心代码4. 项目打包5.部署步骤 一、需求概况 现有某云主机服务器&#xff0c;用来做项目演示用&#xff0c;上面运行了docker应用&#xff0c;现希望有一总览页面&#xff0c;用来展示部署…

fastjson 序列化问题

问题: 使用fastjson 的 对同一个JSONObject对象 多次引用后, 通过 JSON.toJSONString() 方法进行json序列化时出现只有第一次的可以成功序列化未json string 字符串, 后面的对象都为引用地址; 示例: public static void main(String[] args) {JSONObject jsonObject new JSON…

【C语言__结构体__复习篇3】

目录 前言 一、结构体基础知识 1.1 结构体的语法形式 1.2 创建结构体变量 1.3 结构体变量的初始化 1.4 点(.)操作符和箭头(->)操作符 二、匿名结构体 三、结构体自引用 四、结构体内存对齐 4.1 内存对齐的规则 4.2 出现结构体内存对齐的原因 4.3 修改默认对齐数 五、结…

【YOLOv9】完胜V8的SOTA模型Yolov9(论文阅读笔记)

官方论文地址&#xff1a; 论文地址点击即可跳转 官方代码地址&#xff1a; GitCode - 开发者的代码家园 官方代码地址点击即可跳转 1 总述 当输入数据经过各层的特征提取和变换的时候&#xff0c;都会丢失一定的信息。针对这一问题&#xff1a; 论文中提出的可编程梯度信息…

高颜值的B端界面,如果漂亮也有错,就让它错下去吧。

分享一波漂亮的B端界面&#xff0c;内行看门道&#xff0c;外行看热闹&#xff0c;不喜勿喷昂。

防止狗上沙发,写一个浏览器实时识别目标检测功能

家里有一条狗&#x1f436;&#xff0c;很喜欢乘人不备睡沙发&#x1f6cb;️&#xff0c;恰好最近刚搬家 狗迎来了掉毛期 不想让沙发上很多毛。所以希望能识别到狗&#xff0c;然后播放“gun 下去”的音频&#x1f4e3;。 需求分析 需要一个摄像头&#x1f4f7; 利用 chrome…

反激电源RC吸收电路设计

一、什么是RC吸收&#xff1f; RC吸收是指在电路设计中&#xff0c;尤其是在开关电源、功率电子设备以及电力电子系统中&#xff0c;使用电阻与电容串联组成的电路结构&#xff0c;用于吸收和衰减电路中由于开关元件&#xff08;如MOSFET、IGBT等&#xff09;的快速切换所产生的…

Blender2.83 下载地址及安装教程

Blender是一款开源的3D计算机图形软件&#xff0c;广泛应用于动画制作、游戏开发、建模、渲染等领域。它提供了一套强大的工具和功能&#xff0c;让用户能够进行三维建模、动画制作和视觉效果的创作。 Blender支持多种文件格式的导入和导出&#xff0c;使用户能够与其他软件进…

从汇编指令看函数调用堆栈的详细过程

我们以下述代码为例来说明函数调用堆栈的详细过程 #include<iostream>int sum(int a,int b) {int temp0;tempab;return temp; }int main() {int a10;int b20;int retsum(a,b);std::cout<<"ret:"<<ret<<std::endl;return 0; } 接下来&#…

粒子群优化算法PSO与鹈鹕优化算法(POA)求解无人机三维路径规划(MATLAB代码)

一、无人机路径规划模型介绍 二、算法介绍 close all clear clc dbstop if all error warning (off) global model model CreateModel(); % 创建模型 FF1; [Xmin,Xmax,dim,fobj] fun_info(F);%获取函数信息 pop100;%种群大小(可以自己修改) maxgen100;%最大迭代次数(可以自己…

电商数据采集的网页抓取数据、淘宝、天猫、京东等平台的电商数据抓取|电商数据API接口网页爬虫、采集网站数据

电商数据采集的网页抓取数据、淘宝、天猫、京东等平台的电商数据抓取&#xff0c;网页爬虫、采集网站数据、网页数据采集软件、python爬虫、HTM网页提取、APP数据抓包、APP数据采集、一站式网站采集技术、BI数据的数据分析、数据标注等成为大数据发展中的热门技术关键词。那么电…

以太网数据量大小字符串生成方法(可变单位)

0 前言 当我们想显示以太网数据量大小时&#xff0c;往往有个头疼的单位需要处理&#xff0c;单位取小了不一目了然&#xff0c;单位取大了精度太低。本例设计一个函数&#xff0c;将根据以太网数据量大小自动生成单位可变的字符串&#xff08;KB、MB、GB、TB、PB&#xff09;…

CSS之固定定位、相对定位、绝对定位

一、相对定位 相对元素自身所在的原来的位置进行定位&#xff0c;可以设置 left&#xff0c;right&#xff0c;top&#xff0c;bottom四个属性。 效果&#xff1a;在进行相对定位以后&#xff0c;元素原来所在的位置被保留了&#xff0c;既保留占位&#xff0c;其他元素的位置…

软件全周期检查单

1、立项阶段 2、计划阶段 3、需求阶段 4、设计阶段 5、编码集成阶段 6、测试阶段 7、交付阶段 8、结项阶段 软件全套资料包获取进主页或本文末个人名片直接获取。

Bezier曲线的绘制 matlab

式中&#xff1a; 称为基函数。 。 因为n表示次数&#xff0c;点数为n1&#xff0c;显然i表示第i个控制点。 显然在Matlab中可以同矩阵的形式来计算C(u)。 关键代码为&#xff1a; clc clear % 假设控制点P取值为&#xff1a; P [4,7;13,12;19,4;25,12;30,3]; % 因此&a…

5 个让日常编码更简单的 Python 库

今天我们一起来研究一些非常有用的第三方模块&#xff0c;可以使得我们的日常编码变得更加简单方便 sh https://github.com/amoffat/sh 如果曾经在 Python 中使用过 subprocess 库&#xff0c;那么我们很有可能对它感到失望&#xff0c;它不是最直观的库&#xff0c;可能还有些…

C语言中局部变量和全局变量是否可以重名?为什么?

可以重名 在C语言中, 局部变量指的是定义在函数内的变量, 全局变量指的是定义在函数外的变量 他们在程序中的使用方法是不同的, 当重名时, 局部变量在其所在的作用域内具有更高的优先级, 会覆盖或者说隐藏同名的全局变量 具体来说: 局部变量的生命周期只在函数内部,如果出了…

mars3d.MaterialType.Image2修改配置面状:图片2的speed数值实现动画效果说明

摘要&#xff1a; mars3d.MaterialType.Image2修改配置面状&#xff1a;图片2的speed数值实现动画效果说明 前提&#xff1a; 1.在示例中&#xff0c;尝试给mars3d.MaterialType.Image2材质的图片加上speed参数&#xff0c;实现动画效果&#xff0c;但是没有看到流动效果说明…

酷漫熊,最好用的漫画工具,资源全,支持下载,全部免费!

hi&#xff0c;大家好我是技术苟&#xff0c;每周准时上线为你带来实用黑科技&#xff01;由于公众号改版&#xff0c;现在的公众号消息已经不再按照时间顺序排送了。因此小伙伴们就很容易错过精彩内容。喜欢黑科技的小伙伴&#xff0c;可以将黑科技百科公众号设为标星&#xf…