当前位置: 首页 > article >正文

初阶数据结构--链式二叉树

二叉树(链式结构)

前面的文章首先介绍了树的相关概念,阐述了树的存储结构是分为顺序结构和链式结构。其中顺序结构存储的方式叫做堆,并且对堆这个数据结构进行了模拟实现,并进行了相关拓展,接下来会针对链式结构的存储方式的树进行介绍。

1. 链式结构的二叉树

用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。

1.1 链式二叉树的结构

其结构如下:

//定义二叉树链式结构
//二叉树结点的结构
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;BTNode* left;BTNode* right;
}BTNode;
  • 这里还是利用typedef对二叉树结构体进行重定义,定义为新的类型BTNode,对于结构体中用于存储数据的变量data的类型int再使用typedef定义,便于后期的更改和维护。

1.2 创建一个链式二叉树

//创建一个二叉树节点并初始化
BTNode* buyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}//开辟成功,初始化newnode->data = x;newnode->left = newnode->right = NULL;//返回新节点return newnode;
}//手动创建一个二叉树
void createTree()
{//创建数据BTNode* node1 = buyNode(1);BTNode* node2 = buyNode(2);BTNode* node3 = buyNode(3);BTNode* node4 = buyNode(4);BTNode* node5 = buyNode(5);BTNode* node6 = buyNode(6);//连接数据node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;
}

经过以上代码即可调整出如下图的二叉树。

总结:

  • 回顾二叉树的概念,二叉树分为空树和非空二叉树
    • 非空二叉树由根结点、根结点的左子树、根结点的右子树组成的
  • 根结点的左子树和右子树分别又是由子树结点、子树结点的的左子树、子树结点的右子树组成的,因此二叉树定义是递归式的,后序链式二叉树的操作中基本都是按照该概念实现的。

2. 二叉树的前中后序遍历

2.1 遍历规则

按照规则,二叉树的遍历有:前序 / 中序 / 后序的递归结构遍历:

  1. 前序遍历 (Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
    访问顺序为:根结点、左子树、右子树
  2. 中序遍历 (Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)
    访问顺序为:左子树、根结点、右子树
  3. 后序遍历 (Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后
    访问顺序为:左子树、右子树、根结点

2.2 详解遍历

2.2.1 前序遍历
2.2.1.1 代码示例
//实现二叉树前序遍历
void PreOrder(BTNode* root)
{if (root == NULL){return;}printf("%d", root->data);//左右两个子树作为根节点继续遍历//根据:根左右,所以先从左节点开始PreOrder(root->left);//左子树遍历完成,遍历右子树PreOrder(root->right);
}
2.2.1.2 代码解释

这里使用递归的思想,因为为前序遍历是根左右的思想,所以先打印根结点的数据;再按顺序依次将每个左结点作为新的根结点传给PreOrder函数,并每次将新的根结点数据进行打印,然后进行递归循环执行,直到遍历到二叉树最后一层的最左侧叶子结点,打印此数据并回退至其父结点;之后再检测此父节点有无右结点,若有则打印,没有则继续回退至此父结点的父结点;不断回退打印直到根结点的左子树遍历完成,之后再以相同的根左右的思路继续遍历右子树。

最终遍历一遍之后,就可以得到先序遍历的数据。

2.2.1.3 前序遍历的本质
  • 根左右

  • 先打印后遍历

  • 对于前序遍历的本质可以理解为,一个小人开始绕着整棵树的外围转一圈,经过的结点顺序就是前序遍历的顺序。

2.2.2 中序遍历
2.2.2.1 代码示例
//实现二叉树中序遍历
void InOrder(BTNode* root)
{if (root == NULL){return;}//不断递归找到左子树最左下角的数据InOrder(root->left);//找到之后打印数据printf("%d ", root->data);//左子树遍历完成,继续遍历右子树InOrder(root->right);
}
2.2.2.2 代码解释

这里仍然使用函数递归的思想,根据中序遍历左根右的思想,首先递归遍历二叉树的左子树,直到遍历至左子树中最后一层的最左侧叶子结点,开始打印此数据,并回退至最左侧叶子结点的父节点并打印,之后再检测此父结点有无右结点,若有则打印,没有则继续回退至此父结点的父结点;不断回退打印直到根结点的左子树遍历完成,此时再打印根结点数据,然后继续以相同的左根右的思路遍历右子树。

最终遍历完所有数据之后,就可以得到中序遍历的数据。

2.2.2.3 中序遍历的本质
  • 左根右
  • 打印放遍历中间
  • 中序遍历可以想象成,按树画好的左右位置直接投影下来即可

在这里插入图片描述

2.2.3 后序遍历
2.2.3.1 代码示例
//实现二叉树后序遍历--左右根
void PostOrder(BTNode* root)
{if (root == NULL){return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}
2.2.3.2 代码解释

这里仍然借用递归的思想,根据后序遍历左右根的思想,首先由根结点遍历左子树找到左子树中最后一层的最左侧叶子结点,开始打印此数据,并回退至最左侧叶子结点的父节点,进入其父结点的函数栈帧,执行代码PostOrder(root->right);,检测此父结点是否有有右结点,若有则打印,再返回回其父结点并打印;若没有这直接回退至其父节点并打印父节点中的数据,之后不断循环递归直到回到根结点,再根据上述思路,诋毁遍历根结点的右子树。

最终遍历完所有数据之后,就可以得到后序遍历的数据。

2.2.3.3 后序遍历的本质
  • 左右根
  • 先遍历,后打印
  • 后序遍历就像剪葡萄,把一串葡萄剪成一颗一颗的,按照之前先序遍历的路径,围着树的外围绕一圈,如果发现一剪刀就可以剪下来的葡萄,就把他剪下来,这样就组成后序遍历了

在这里插入图片描述

3. 二叉树中结点个数以及高度的实现(递归)

3.1 求二叉树中结点个数

3.1.1 代码示例
// ⼆叉树结点个数 
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
3.1.2 代码解释

在面对求解二叉树总结点的问题时,可以将问题拆解成子问题:

  1. 若根结点为空则直接返回,结点个数为0
  2. 若根结点不为空,则先计算此时的结点本身,再继续遍历根结点的左右子树

二叉树结点总个数 = 根结点左子树个数 + 根结点右子树个数 + 1(根结点本身)

3.1.3 递归详解(return的讲解)

这里思考本质就把递归只放在一个随机结点处,此时这个结点接收到了其左子树右子树返回给他的结点个数,此节点需要先**+1(加上自己本身的结点数),再将新的结点个数传递给其父结点**。

所以针对根节点的总结点数,直接递归遍历其根结点左子树右子树的结点数再**+1**相加即可。

**补充:**这里相当于每一个结点都会创建一个函数栈帧,每一个函数栈帧都会在返回值+1,记录结点数。

3.2 求二叉树中叶子结点个数

3.2.1 代码示例
// ⼆叉树叶⼦结点个数 
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}//确认叶子结点,是叶子结点返回1if (root->left == NULL && root->right == NULL){return 1;}//总叶子结点个数 = 左子树叶子结点个数 + 右子树叶子结点个数return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
3.2.2 代码解释

子问题拆解:

  1. 若根结点为空,则叶子结点个数为0
  2. 若结点的左指针和右指针均为空,则该节点为叶子结点返回1
  3. 除上述两种情况外,说明该树存在子树,继续向下遍历

二叉树叶子结点个数 = 左子树的叶子结点个数 + 右子树的叶子结点个数

3.2.3 递归详解(return的讲解)

同样这里从递归的本质进行思考,针对普通情况任何一点的叶子结点,都需要分别不断向下遍历,直到符合子问题2,找到叶子结点则返回1数据的调整实在函数回退的时候实现的),并开始回退至其父节点,再执行+号后面的代码,检测此父节点的右子树是不是叶子结点,若是则继续返回1,再回退至其父节点若不是则直接回退。不断回退递归直到回到初始节点,此时此节点的左右子树的叶子结点相加即可。

所以针对根节点的叶子结点数,直接递归遍历其根结点的左子树右子树的叶子结点再相加即可。

3.3 求二叉树中第k层结点个数

3.3.1 代码示例
// ⼆叉树第k层结点个数 
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}//判断结点是不是第k层,如果是则返回1if (k == 1){return 1;}//第k层结点个数 = 左子树的第k层结点个数 + 右子树的第k层结点个数return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
3.3.2 代码解释

子问题拆解:

  1. 若根节点为空,则二叉树第k层结点为0
  2. 设置标志位k用于表示是树的第几层,如果该节点的标志位层数为k,则返回1
  3. 若均不是以上两种情况表示,还不是第k层的结点,继续向下遍历

相对于根结点的第k层结点的个数 = 相对于以其左孩子为根的第k-1层结点的个数 + 相对于以其右孩子为根的第k-1层结点的个数

3.3.3 本质理解(return详解)

对任意一个二叉树的第k层结点数量,都是由根节点的左子树和右子树出发,先由左子树出发直到符合子问题2,找到位于第k层的结点,并返回1(数据的调整实在函数回退的时候实现的),并开始回退至其父节点,再执行+号后面的代码,检测此父节点的是否有右孩子结点,因为此时的k-1与+号前的函数统一,所以不需要再判断是不是第k层,直接判断此层有无数据即可。之后就不断回退递归直到回到根结点,此时该二叉树的第k层结点数量将左右子树的第k层结点数量相加即可

对于结点第K层结点的个数由左子树的第k-1层结点个数右子树的第k-1层结点个数相加即可。

3.4 求二叉树深度/高度

3.4.1 代码示例
//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root)
{if (root == NULL){return 0;}int leftDep = BinaryTreeDepth(root->left);int rightDep = BinaryTreeDepth(root->right);//返回最终比较大的高度return leftDep > rightDep ? leftDep + 1 : rightDep + 1;
}
3.4.2 代码解释

子问题拆解:

  1. 若为空,则深度为0
  2. 若不为空,则先对标志位 + 1

树的最大深度 = 左右子树中深度较大的值 + 1(根结点也代表一个深度)

3.4.3 本质理解(return详解)

对于求任意一个二叉树深度,都是由根结点的左右子树出发,先从左子树出发直到遍历至最左侧叶子结点,为左子树标志位leftDep + 1之后,回退至其父结点,再检测此父结点是否有右孩子结点若有则为右子树标志位rightDep + 1,再回退至父结点;若没有则直接回退至父结点。经过递归回退不断对leftDep和rightDep进行迭代,最后选择大的再加上根结点本身作为二叉树深度返回

3.4 求二叉树深度/高度

3.4.1 代码示例
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}//判断是否找到xif (root->data == x){//找到直接返回数据所在的结点return root;}//递归遍历左子树BTNode* leftFind =  BinaryTreeFind(root->left, x);if (leftFind){return leftFind;}//递归遍历右子树BTNode* rightFind =  BinaryTreeFind(root->right, x);if (rightFind){return rightFind;}return NULL;
}
3.4.2 代码解释

子问题拆解:

  1. 判断根结点是否是目标结点,若是则返回此结点
  2. 若不是,先在左子树寻找,再在右子树寻找

3.5 二叉树的销毁

3.5.1 代码示例
// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL){return;}//先释放左子树和右子树BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));//释放结点free(*root);*root == NULL;
}
3.5.2 代码解释
  • 这里函数的参数传的是二级指针,因为销毁二叉树要操作的参数是结点的地址,然而结点的类型是结构体,所以结构体的地址用二级指针表示。
  • 对于求任意一个二叉树的销毁,都是由根结点的左右子树出发,先从左子树出发直到遍历至最左侧叶子结点,销毁此节点后,回退至其父结点,再检测此父结点是否有右孩子结点若有则销毁此结点,再回退至父结点;若没有则直接回退至父结点。经过递归回退直到遍历至根节点释放掉即可实现二叉树的销毁。

3.6 总结

  • 以上递归思路都是先遍历左子树在遍历右子树,采用的主要是左根右的中序遍历思想。
  • 上述代码中对于左子树和右子树的遍历,并不是一次性将根结点的左子树遍历完成再遍历右子树,而是针对每一个节点都是先遍历左子树再遍历右子树。(中序遍历的重要思想注意!!注意!!

4. 层序遍历

4.1 概念介绍

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树过进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第-一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历

实现层序遍历需要额外借助数据结构:队列

在这里插入图片描述

4.2 代码示例

//Queue.h
//定义队列结构
//通过修改重定义,将队列中的数据data改为结点
//typedef int QDataType;
typedef struct BinaryTreeNode* QDataType;
//typedef struct BTNode* QDataType;
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;typedef struct Queue
{QueueNode* phead;QueueNode* ptail;int size;//保存队列有效数据个数
}Queue;//Tree.c
//层序遍历
//借助数据结构---队列
void LevelOrder(BTNode* root)
{//创建队列,并初始化Queue q;QueueInit(&q);//先将根结点放入队列QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,并打印BTNode* front = QueueFront(&q);printf("%d ", front->data);//打印后出队列QueuePop(&q);//队头节点的左右孩子入队列if (front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}//队列为空,销毁队列QueueDestroy(&q);
}

4.3 逻辑详解

  • 要想实现层序遍历是没有办法通过想前面实现前中后序遍历那样通过递归实现,这里借助队列这一数据结构实现层序遍历。

  • 用队列实现层序遍历详解:

    • 首先根结点(1)进队列,再出队列,同时检查根结点有无左右孩子结点,若有则入队列。如上图,根结点有左右孩子结点,均入队列(2 3)。

    • 之后左孩子结点(2)出队列,出队列之后检查根结点的左孩子结点是否有左右孩子结点,若有则也入队列(4 5),如上图,故有左右孩子结点,并入队列

    • 之后根结点的右孩子节点(3)出队列,同时执行上述逻辑,继续检查是否有左右孩子节点,但是上图中只有左孩子结点(6),故左孩子节点再入队列。

    • ······

    • 不断循环,直到队列为空,此时取出的数据,组成的就是层序遍历

    • **总结:**本质就是从根结点开始,每出一个结点就将其存在的左右孩子结点加入队列,并不断循环(借助队列先进先出的性质,上一层数据出队列的时候带入下一层的数据),即可实现层序遍历。
      在这里插入图片描述

  • 补充:由于每一个进入队列的数据都是一个结点,然后结点的数据结构类型是结构体类型struct BinaryTreeNode*,所以需要再队列中的typedef int QDataType;改为typedef struct BinaryTreeNode* QDataType;,这里就体现了前期使用重定义的方式定义队列,增加了后期代码的可维护性。

5. 判断二叉树是否为完全二叉树

5.1 代码示例

//判断二叉树是否为完全二叉树
//---队列
bool BinaryTreeComplete(BTNode* root)
{//创建队列,并初始化Queue q;QueueInit(&q);//将根节点插入队列QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,并出队列BTNode* front = QueueFront(&q);QueuePop(&q);// 检测到NULL则跳出循环,停止出队列if (front == NULL){break;}QueuePush(&q, front->left);QueuePush(&q, front->right);}//检查队列是否为空while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//队列中还有数据,说明不是完全二叉树if (front != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);//是完全二叉树return true;
}

5.2 逻辑详解

这里的基本逻辑和上面的层序遍历基本一样,均是借用队列这一数据结构,通过先进先出,一层一层检查结点,看结点是不是按照完全二叉树的顺序

  • 首先把根结点入队列,然后开始从队头出数据
  • 出队头数据,若其有左右孩子结点,也将其依次如队列
  • ······
  • 不断循环,直到取队头数据为NULL时,停止入队列
  • 此时只需要检查队列中的剩余数据,若均为NULL则是完全二叉树,如果还存在非空数据,则不是完全二叉树

在这里插入图片描述

6. 二叉树的插入和删除

​ 由于二叉树的定义比较广泛,其结构并不固定,比较多样化,所以模拟实现普通二叉树的增加和删除是没有意义的。

文章中部分图片来源于博主:2021dragon和一位前辈yuxi(抱歉个人主页已经找不到了,有好心人可以在评论区提醒一下)

最后谢谢大家看到这里,感谢!!

相关文章:

初阶数据结构--链式二叉树

二叉树(链式结构) 前面的文章首先介绍了树的相关概念,阐述了树的存储结构是分为顺序结构和链式结构。其中顺序结构存储的方式叫做堆,并且对堆这个数据结构进行了模拟实现,并进行了相关拓展,接下来会针对链…...

Tree Shaking(摇树优化)详解

Tree Shaking(摇树优化)详解 Tree Shaking 是现代 JavaScript 打包工具(如 Webpack、Rollup、Vite等)中的一项重要优化技术,它的名字形象地比喻为"摇动一棵树,让没用的叶子掉下来"。 核心概念 …...

SpringAI版本更新:向量数据库不可用的解决方案!

Spring AI 前两天(4.10 日)更新了 1.0.0-M7 版本后,原来的 SimpleVectorStore 内存级别的向量数据库就不能用了,Spring AI 将其全部源码删除了。 此时我们就需要一种成本更低的解决方案来解决这个问题,如何解决呢&…...

BladeX单点登录与若依框架集成实现

1. 概述 本文档详细介绍了将BladeX认证系统与若依(RuoYi)框架集成的完整实现过程。集成采用OAuth2.0授权码流程,使用户能够通过BladeX账号直接登录若依系统,实现无缝单点登录体验。 2. 系统架构 2.1 总体架构 #mermaid-svg-YxdmBwBtzGqZHMme {font-fa…...

JVM 内存调优

内存调优 内存泄漏(Memory Leak)和内存溢出(Memory Overflow)是两种常见的内存管理问题,它们都可能导致程序执行不正常或系统性能下降,但它们的原因和表现有所不同。 内存泄漏 内存泄漏(Memo…...

Shell脚本提交Spark任务简单案例

一、IDEA打包SparkETL模块,上传值HDFS的/tqdt/job目录 二、创建ods_ETL.sh脚本 mkdir -p /var/tq/sh/dwd vim /var/tq/sh/dwd/ods_ETL.sh chmod 754 /var/tq/sh/dwd/ods——ETL.sh #脚本内容如下 #!/bin/bash cur_date$(date %Y-%m-%d) /opt/bigdata/spark-3.3.2/b…...

国标GB28181视频平台EasyCVR视频汇聚系统,打造别墅居民区智能监控体系

一、现状背景 随着国家经济的快速增长,生活水平逐渐提高,私人别墅在城市、乡镇和农村的普及率也在逐年增加。然而,由于别墅区业主经济条件较好,各类不法事件也日益增多,主要集中在以下几个方面: 1&#x…...

BGP分解实验·23——BGP选路原则之路由器标识

在选路原则需要用到Router-ID做选路决策时,其对等体Router-ID较小的路由将被优选;其中,当路由被反射时,包含起源器ID属性时,该属性将代替router-id做比较。 实验拓扑如下: 实验通过调整路由器R1和R2的rout…...

机器学习(5)——支持向量机

1. 支持向量机(SVM)是什么? 支持向量机(SVM,Support Vector Machine)是一种监督学习算法,广泛应用于分类和回归问题,尤其适用于高维数据的分类。其核心思想是寻找最优分类超平面&am…...

访问不到服务器上启动的llamafactory-cli webui

采用SSH端口转发有效,在Windows上面进行访问 在服务器上启动 llamafactory-cli webui 后,访问方式需根据服务器类型和网络环境选择以下方案: 一、本地服务器(物理机/虚拟机) 1. 直接访问 若服务器与操作设备处于同一…...

【玩泰山派】MISC(杂项)- 使用vscode远程连接泰山派进行开发

文章目录 前言流程1、安装、启动sshd2、配置一下允许root登录3、vscode中配置1、安装remote插件2、登录 **注意** 前言 有时候要在开发板中写一写代码,直接在终端中使用vim这种工具有时候也不是很方便。这里准备使用vscode去通过ssh远程连接泰山派去操作&#xff0…...

量子纠缠物理本质、技术实现、应用场景及前沿研究

以下是关于 量子纠缠(Quantum Entanglement) 的深度解析,涵盖物理本质、技术实现、应用场景及前沿研究,以技术视角展开: 一、量子纠缠的物理本质 1. 核心定义 量子纠缠是多个量子系统(如粒子)间的一种关联状态,表现为: 非局域性:纠缠态粒子无论相距多远,测量其中一…...

Spring Boot中接入DeepSeek的流式输出

第一步&#xff0c;添加依赖&#xff1a; <!-- WebFlux 响应式支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 第二步&#xff0c;配置We…...

同步/异步日志系统

同步/异步日志系统 项目演示基础测试性能测试测试环境&#xff1a;同步日志器单线程同步日志器多线程异步日志器单线程异步日志器多线程 工具类&#xff08;util.hpp&#xff09;日志等级level.hpp 日志消息message.hpp 日志消息格式化formatter.hpp 日志消息落地sink.hpp 日志…...

typescript html input无法输入解决办法

input里加上这个&#xff1a; onkeydown:(e: KeyboardEvent) > {e.stopPropagation();...

游戏引擎学习第224天

回顾游戏运行并指出一个明显的图像问题。 回顾一下之前那个算法 我们今天要做一点预加载的处理。上周刚完成了游戏序章部分的所有剪辑内容。在运行这一部分时&#xff0c;如果观察得足够仔细&#xff0c;就会注意到一个问题。虽然因为视频流压缩质量较低&#xff0c;很难清楚…...

SAP-ABAP:SAP HANA高可用与灾备——存储镜像与系统复制的核心技术

SAP HANA作为企业关键业务的核心数据库&#xff0c;其高可用性&#xff08;High Availability, HA&#xff09;与灾备&#xff08;Disaster Recovery, DR&#xff09;能力直接影响业务连续性。HANA通过存储镜像、系统复制及集群集成三大核心技术&#xff0c;实现秒级故障切换与…...

工厂能耗系统智能化解决方案 —— 安科瑞企业能源管控平台

安科瑞顾强 政策背景与“双碳”战略驱动 2025年《政府工作报告》明确提出“单位国内生产总值能耗降低3%左右”的目标&#xff0c;要求通过产业结构升级&#xff08;如高耗能行业技术革新或转型&#xff09;、能源结构优化&#xff08;提高非化石能源占比&#xff09;及数字化…...

【pytorch图像视觉】lesson17深度视觉应用(上)构建自己的深度视觉项目

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、 数据1、认识经典数据1.1入门数据&#xff1a;MNIST、其他数字与字母识别&#xff08;1&#xff09;数据下载&#xff08;2&#xff09;查看数据的特征和标…...

java中的Future的设计模式 手写一个简易的Future

案例 例如&#xff1a;今天是小妹的生日&#xff0c;需要一个蛋糕有点仪式感&#xff0c;于是去蛋糕店预定&#xff0c;预定完之后&#xff0c;店老板说蛋糕做好了&#xff0c;到时电话通知你&#xff0c;不可能在这傻傻的等着吧&#xff0c;还有其他事情要做啊&#xff0c;于…...

USB(TYPE-C)转串口(TTL)模块设计讲解

目录 一 、引言 二、方案设计 三、USB TYPE-C介绍 1、TYPE-C接口定义 1、24P全引脚描述 2、Type C 接口 VBUS/GND 作用 3、Type C 接口 D/D- 作用 1、数据传输&#xff1a; 2、设备识别&#xff1a; 3、充电协议协商&#xff1a; 4、Type C 接口 CC1/CC2 作用 1、主从设备区…...

JavaScript | ajax实现原理

在早期&#xff0c;web应用&#xff0c;更多采用mvc框架&#xff0c;通过后端输出整个页面的内容&#xff0c;然后再用浏览器进行渲染&#xff0c;这样效率不高&#xff0c;对于事件绑定来说比较麻烦&#xff0c;于是提出了ajax&#xff0c;其最大的特点就是能实现局部更新。通…...

PyTorch张量操作指南:cat、stack、split与chunk的实战拆解

本文深入探讨PyTorch中用于调整张量结构的四个核心函数——torch.cat、torch.stack、torch.split和torch.chunk。通过实际应用场景分析和代码演示&#xff0c;帮助读者掌握它们的功能差异及适用条件&#xff0c;提升模型开发的灵活性与效率。 在深度学习实践中&#xff0c;张量…...

YOLO涨点技巧之分层扩展路径聚合网络 (HEPAN)

一、应用场景与问题背景 1.1 无人机图像检测挑战 https://ai-studio-static-online.cdn.bcebos.com/3d4f7e8c4d8d4d2d8a4c8e4b4e8c4d8d ​场景特点:无人机航拍视角下的小目标检测(如行人、车辆、农作物病害等)​核心难点: 目标尺寸小(<3232像素)复杂背景干扰(如城市…...

SQLite、MySQL、SQL Server、Oracle 和 PostgreSQL 五种数据库的区别

以下是 SQLite、MySQL、SQL Server、Oracle 和 PostgreSQL 五种主流关系型数据库管理系统(RDBMS)的区别,从多个维度进行对比: 1. 架构与部署 SQLite(Structured Query Language Lite‌): 嵌入式数据库,无服务器架构。数据库存储在一个单一的磁盘文件中。部署简单,适合轻量…...

git在分支上会退到某个指定的commit

1、在idea上先备份好分支&#xff08;基于现有分支new branch&#xff09; 2、在gitlab管理端删除现有分支 3、在idea中大卡terminal&#xff0c;执行 git log 查看commit log ,找到要会退到的commit唯一码&#xff0c;然后执行git reset 唯一码 4、查看本地代码状态 git st…...

玩机进阶教程----MTK芯片设备刷机导致的死砖修复实例解析 连电脑毫无反应 非硬件问题

在高通芯片机型中,我们可以通过短接主板测试点来激活高通芯片特有的9008底层端口来刷写救砖固件。但通常MTK芯片类的设备联机电脑即可触发深刷模式。但有些例外的情况会导致链接电脑毫无反应。遇到类似故障的友友可以参阅此博文尝试解决。 通过博文了解 1💝💝💝-----实…...

MIPI协议介绍

MIPI协议介绍 mipi 协议分为 CSI 和DSI,两者的区别在于 CSI用于接收sensor数据流 DSI用于连接显示屏 csi分类 csi 分为 csi2 和 csi3 csi2根据物理层分为 c-phy 和 d-phy, csi-3采用的是m-phy 一般采用csi2 c-phy 和 d-phy的区别 d-phy的时钟线和数据线是分开的,2根线一对…...

MySQL 中 `${}` 和 `#{}` 占位符详解及面试高频考点

文章目录 一、概述二、#{} 和 ${} 的核心区别1. 底层机制代码示例 2. 核心区别总结 三、为什么表名只能用 ${}&#xff1f;1. 预编译机制的限制2. 动态表名的实现 四、安全性注意事项1. ${} 的风险场景2. 安全实践 五、面试高频考点1. 基础原理类问题**问题 1**&#xff1a;**问…...

AI应用开发平台 和 通用自动化工作流工具 的详细对比,涵盖定义、核心功能、典型工具、适用场景及优缺点分析

以下是 AI应用开发平台 和 通用自动化工作流工具 的详细对比&#xff0c;涵盖定义、核心功能、典型工具、适用场景及优缺点分析&#xff1a; 1. AI应用开发平台 vs 通用自动化工作流工具 (1) 定义与目标 类型AI应用开发平台通用自动化工作流工具定义用于快速构建、训练、部署…...