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

《数据结构初阶》【链式二叉树】

《数据结构初阶》【链式二叉树】

  • 前言:
  • ---------------树---------------
    • 什么是树?
    • 📌爱心❤小贴士:树与非树?
    • 树的基本术语有哪些?
      • 关于节点的一些定义:
      • 关于树的一些定义:
      • 关于森林的定义:
    • 树的实现方式有哪些?
  • ---------------二叉树---------------
    • 什么是二叉树?
    • 二叉树与普通树的本质区别是什么?
    • 二叉树的五大基本形态是什么?
    • 特殊的二叉树有哪些?
    • 树和二叉树的性质有哪些?
    • 树和二叉树的区别是什么?
    • 二叉树怎么进行遍历?
    • 二叉树的实现方式有哪些?我们要选择哪种方式进行实现?
  • 使用二叉链实现二叉树
    • 头文件
    • 实现文件
    • 测试文件
    • 运行成功样例

在这里插入图片描述

往期《数据结构初阶》回顾:
【时间复杂度 + 空间复杂度】
【顺序表 + 单链表 + 双向链表】
【顺序表/链表 精选15道OJ练习】
【顺序栈 + 链式队列 + 循环队列】

前言:

嗨~(✿◠‿◠) 友友们!好久不见啦,不知道这段时间大家在数据结构的知识海洋里遨游得怎么样?🏊
最近天气越来越暖和,阳光也愈发灿烂啦~🌼
🚩 今天咱们就要继续探索新的数据结构—— 二叉树 啦! 🌳

“学不会?不存在的!汪星人都能懂的二叉树教程🐶,你也一定行!”
相信当你沉浸其中,读完这篇博客后,一定会对二叉树有全新的认识哦~,也一定能收获超多知识干货!✨

---------------树---------------

什么是树?

树(Tree):是一种非线性的数据结构,它是由 n n n ( n ≥ 0 ) (n≥0) n0个有限节点组成一个具有层次关系的集合。

树的递归定义:树是由一个根节点和若干棵子树构成的。

一棵树中至少有一个节点,这个节点就是根节点。

n > 0 n>0 n>0 时,其余节点可分为 m m m ( m ≥ 0 ) (m≥0) m0个互不相交的有限集合 T 1 T_1 T1 T 2 T_2 T2、…、 T m T_m Tm,其中每个集合本身又是一棵树,并且称为根的子树。

  • 例如:在一个表示公司组织架构的树中,公司的最高领导是根节点,其下的各个部门负责人是根节点的子树的根,每个部门又可以有自己的下属员工,形成更下一层的子树。

总结:从上面的定义中,我们可以知道树这种数据结构天然的和递归有关联。

在这里插入图片描述

📌爱心❤小贴士:树与非树?

在这里插入图片描述

注意树形结构中,子树之间不能有交集,否则就不是树形结构

树的基本术语有哪些?

在这里插入图片描述

关于节点的一些定义:

节点:树中的每个元素称为节点。

  • 如上图:A、B、C、D、E、F、G、H、I、J、K、L、M、N、P 、Q均为节点。

根节点:树中没有父节点的节点,也是树的起始点。

  • 如上图:A 是根节点。

子节点与父节点:一个节点的下一层连接的节点称为其子节点,该节点则是其子节点的父节点

  • 如上图:B、C、D、E、F、G 是 A 的子节点,A 是 B、C、D、E、F、G 的父节点

兄弟节点:具有相同父节点的节点互为兄弟节点。

  • 如上图:K、L、M互为兄弟节点

堂兄弟节点:父节点互为兄弟节点的节点互为堂兄弟节点。

  • 如上图:H 和 K 是堂兄弟节点,因为 H 的父节点 D 与 K 的父节点 F 是兄弟节点。

叶子节点:没有子节点的节点,也称为终端节点。

  • 如上图:B、C、H、I、K、L、M、N、P、Q 是叶子节点。

内部节点:除叶子节点外的其他节点,即至少有一个子节点的节点。

  • 如上图:A、D、E、F、G、J 均为节点。

节点的祖先:从根节点到该节点所经路径上的所有节点。

  • 如上图:对于节点 Q ,其祖先为 A、E、J

节点的子孙:以某节点为根的子树中的所有节点。

  • 如上图:对于节点 E ,其子孙为 I、J、P 、Q

在这里插入图片描述

关于树的一些定义:

节点的度:一个节点拥有的子节点数量。

  • 如上图:A 的度为 6,D 的度为 1 等

树的度:树中节点度的最大值。

  • 如上图:树的度为 6(A 节点的度)

路径:从一个节点到另一个节点所经过的节点序列。

  • 如上图:从 A 到 Q 的路径为 A、E、J、Q

路径长度:路径上的边的数量。

  • 如上图:从 A 到 Q 的路径长度为 3

树的深度(高度):树中节点的最大层数。

  • 如上图:树的深度(高度)为 4

:节点的层次,根节点在第 1 层,其子节点在第 2 层,以此类推。

  • 如上图:A 在第 1 层,B、C、D、E、F、G 在第 2 层,H、I、J、K、L、M、N 在第 3 层,P、Q 在第 4 层。

关于森林的定义:

森林:由零棵或多棵互不相交的树组成的集合。

树的实现方式有哪些?

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存结点和结点之间 的关系。

实际中树有很多种表示方式如:双亲表示法孩子表示法孩子双亲表示法以及孩子兄弟表示法 等。

我们这里就简单的了解其中最常用的 孩子兄弟表示法

typedef int TDataType;/*** @brief 树节点结构体定义(孩子-兄弟表示法)* @details 采用左孩子右兄弟表示法,将多叉树转换为二叉树形式存储*          每个节点包含数据域和两个指针域*/
typedef struct TreeNode
{TDataType data;              // 节点中的数据域struct TreeNode* pfirstChild;   // 指向第一个孩子结点struct TreeNode* pNextBrother;  // 指向其下一个兄弟结点
}TNode;

在这里插入图片描述

---------------二叉树---------------

什么是二叉树?

二叉树(Binary Tree): 是一种特殊的树形结构,其特点是每个节点最多有两个子节点,分别称为左子节点右子节点

  • 二叉树的每个节点最多有两个子节点,并且子节点有左右之分,顺序不能颠倒
  • 二叉树是一个有限节点的集合,该集合可以为空,或由一个根节点和两棵互不相交的子树组成,这两棵子树分别称为左子树右子树

在这里插入图片描述

二叉树与普通树的本质区别是什么?

特征二叉树普通树
最大分支数每个节点≤2个子节点任意数量子节点
子节点顺序严格区分左/右通常无序
空树定义允许空树(NULL至少包含根节点

二叉树的关键特点

  1. 每个节点至多有两个子节点:即节点的度最大为 2
  2. 子节点有明确的左右之分:左子树和右子树是不同的,即使只有一个子节点也必须区分左右
  3. 递归结构:二叉树的左右子树本身也都是二叉树

二叉树的五大基本形态是什么?

空树:没有任何节点。

只有根节点:根节点的左右子树均为空。

根节点只有左子树:右子树为空。

根节点只有右子树:左子树为空。

根节点同时有左右子树:左右子树均非空。

在这里插入图片描述

注意对于任意的二叉树都是由上面的这五种情况复合而成的

特殊的二叉树有哪些?

满二叉树(Full Binary Tree):所有层的节点数都达到最大值,则这个二叉树就是满二叉树。

完全二叉树(Complete Binary Tree):除了最后一层外,每一层的节点数都达到最大值,且最后一层的节点从左到右连续排列。

二叉搜索树(Binary Search Tree, BST):左子树的所有节点值小于根节点,右子树的所有节点值大于根节点。

平衡二叉树(Balanced Binary Tree):每个节点的左右子树高度差不超过 1(如: AVL 树、红黑树、B树)

在这里插入图片描述

:满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树。

所以满二叉树是特殊的完全二叉树

树和二叉树的性质有哪些?

树的性质:

连通性:树是一个连通的无向图,任意两个节点之间都有且仅有一条路径相连。

  • 树是边数最少的连通图,加一条边会形成环,减一条边会变成非连通图

无环性:树中不存在回路或环,从根节点到任何其他节点都有唯一的路径。

层次关系:树具有明显的层次结构,根节点位于最高层,其子孙节点分布在不同的下层。

子树性质:树中的每个节点都可以看作是一棵子树的根,该子树包含了以该节点为根的所有后代节点。

节点与边的关系:对于一棵有 n n n个节点的树,其边的数量为 n − 1 n - 1 n1

节点和度的关系:总结点数 = 所有节点度数之和 + 1

  • 树中所有节点的度数之和 == 树总边数边数

二叉树的性质:

二叉树遍历的性质

  1. 已知前序+中序,或后序+中序可以唯一确定一棵二叉树

  2. 但前序+后序不能唯一确定二叉树(除非是满二叉树)


层节点数与层数的关系:在满二叉树中,满二叉树的第 k k k层的节点数为 2 k − 1 2^{k-1} 2k1

  • 扩展到二叉树:一棵非空二叉树的第 k k k层上最多有 2 k − 1 2^{k-1} 2k1个结点

总节点数与高度的关系
在满二叉树中,高度为 h h h的满二叉树的总节点数为 2 h − 1 2^{h}-1 2h1

  • 扩展到二叉树:一棵高度为 h h h的二叉树最多有 2 h − 1 2^{h}-1 2h1个结点

在满二叉树中,具有 n n n个结点的满二叉树的高度为 log ⁡ 2 ( n + 1 ) \log_2(n + 1) log2(n+1)


完全二叉树顺序特性

对于具有 n n n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为 i i i的结点有:

  1. 双亲结点(父结点)
    • i = 0,该结点为根结点,无双亲结点
    • i > 0,结点 i 的双亲结点序号为 (i-1)/2(向下取整)
  2. 左孩子结点
    • 2i + 1 >= n,则该结点无左孩子
    • 2i + 1 < n,则左孩子序号为 2i + 1
  3. 右孩子结点
    • 2i + 2 >= n,则该结点无右孩子
    • 2i + 2 < n,则右孩子序号为 2i + 2

总节点数与高度的关系:具有 n n n个节点的完全二叉树的高度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log_2{n}\rfloor + 1 log2n+1

在这里插入图片描述

在这里插入图片描述

对于任意非空二叉树叶节点数 = 度为2的节点数 + 1

在这里插入图片描述

树和二叉树的区别是什么?

特性普通树二叉树
节点最大度数无限制最多2
子树顺序一般不区分严格区分左右
空树有些定义不允许明确允许
存储结构通常使用 孩子兄弟表示法常用 左右指针法(二叉链)
遍历方式无中序遍历有中序遍历

二叉树怎么进行遍历?

二叉树的遍历:二叉树的遍历是指按照一定的规则和顺序,访问二叉树中的每个节点,且每个节点仅被访问一次。

根据节点访问顺序的不同,二叉树的遍历主要分为以下四类:

遍历方式访问顺序典型应用场景
前序遍历 → 左子树 → 右子树复制树结构、前缀表达式(波兰式)
中序遍历左子树 → → 右子树二叉搜索树的有序输出(升序/降序)
后序遍历左子树 → 右子树 → 删除树结构、后缀表达式(逆波兰式)
层序遍历按层次从上到下、从左到右计算树的高度、广度优先搜索(BFS)

下面是一棵二叉树,大家可以自测一下自己是否已经掌握了二叉树的前/中/后序遍历:

在这里插入图片描述

答案

前序:【1 2 3 # # # 4 5 # # 6 # #

中序:【# 3 # 2 # 1 # 5 # 4 # 6 #

后序:【# # 3 # 2 # # 5 # # 6 4 1

二叉树的实现方式有哪些?我们要选择哪种方式进行实现?

和之前我们学习的其他的数据结构一样,二叉树常见实现方式有顺序存储链式存储

那我们就先来看看树的顺序存储

二叉树的顺序存储结构是用一组连续的存储单元依次自上而下、自左至右存储二叉树的节点元素。

其中普通的二叉树是不适合使用顺序存储的,也就是说不适合使用数组来存储二叉树的节点,这是因为对于一般二叉树,需要按照完全二叉树的形式来存储,可能会浪费一些存储空间。

而像完全二叉树就比较适合使用顺序结构存储。

在这里插入图片描述

其实我们通常会把“使用动态数组实现的完全二叉树”这种结构称为

注意:这里的堆和内存四区中的堆是两回事,一个是一种数据结构,一个是操作系统中管理内存的一块区域分段。

:使用动态数组实现的完全二叉树

//任务2:定义堆的结构体(存储类型)
typedef int HPDataType;
typedef struct Heap
{//核心思路:堆是完全二叉树所以使用数组实现比较好//1.记录数组的容量 ---> 一个int变量//2.记录当前数组中元素的数量 --> 一个int变量//3.使用数组存储堆中的节点 ---> 定义一个动态数组int capacity;int size;HPDataType* a;}HP;

预告:由于堆这种数据结构有很多可讲的点,这篇博客中我们只是简单的了解一下,下篇博客再详细的介绍它。


接下来我们再看一看树的链式存储

二叉树的链式存储结构,是借助链表来呈现二叉树形态,以链表的链接关系来体现节点间逻辑关联。

一般而言,链表中的每个节点包含三个域:数据域用于存储节点数据,左指针域指向该节点左孩子所在的链表节点,右指针域则指向右孩子所在的链表节点 。

链式结构又分为:二叉链三叉链,在数据结构初阶阶段,我们先学习使用二叉链来实现二叉树。

在这里插入图片描述

1. 使用二叉链实现二叉树:

typedef int BTDataType;typedef struct BinaryTreeNode
{//1.存储二叉树节点存储的值//2.存储节点的左孩子指针//3.存储节点的右孩子指针BTDataType data;struct BinaryTreeNode* left; //注意:这里还是要注意不要忘记使用structstruct BinaryTreeNode* right;
}BTNode;

2. 使用三叉链实现二叉树

typedef int BTDataType;typedef struct BinaryTreeNode{BTDataType data;            // 当前结点值域struct BinTreeNode* parent; // 指向当前结点的双亲struct BinTreeNode* left;   // 指向当前结点左孩子struct BinTreeNode* right;  // 指向当前结点右孩子
}BTNode;

综上所述

  • 顺序存储(数组):用数组按层次顺序存储节点,适合 完全二叉树

  • 链式存储(链表):用节点(含数据域与左右指针域)以链式结构相连存储节点,适配 任意二叉树

使用二叉链实现二叉树

头文件

------------------------------BinaryTree.h------------------------------#pragma once//任务1:声明需要使用头文件
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Queue.h"//任务2:定义二叉树的存储结构体
typedef int BTDataType;typedef struct BinaryTreeNode
{//1.存储二叉树节点存储的值//2.存储节点的左孩子指针//3.存储节点的右孩子指针BTDataType data;struct BinaryTreeNode* left; //注意:这里还是要注意不要忘记使用structstruct BinaryTreeNode* right;
}BTNode;//任务3:声明二叉树的接口函数//注意:初阶数据结构阶段的学习我们只会去实现一些简单的接口
//例如:二叉树的创建暂时并不涉及,初学阶段我们直接使用手动创建的二叉树来测试我们完成的简单的接口/*------------------------------ 递归实现 ------------------------------*/
//0.二叉树的节点的创建 + 初始化//1.二叉树的前序遍历
//2.二叉树的中序遍历
//3.二叉树的后序遍历//4.二叉树的求树中节点的个数
//5.二叉树的求树中叶子节点的个数
//6.二叉树的求树的高度//7.二叉树的求树中第K层节点的个数
//8.二叉树的寻找树中某个值
//9.二叉树的销毁
/*------------------------------ 非递归实现 ------------------------------*/
//10.二叉树的层序遍历 (需要使用数据结构:队列)
//11.二叉树的判断是否为完全二叉树 (需要使用数据结构:队列)BTNode* BTCreatNode(int x);void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);int BTSize(BTNode* root);
int BTLeafSize(BTNode* root);
int BTHeight(BTNode* root);int BTLevelKSize(BTNode* root, int k);
BTNode* BTFind(BTNode* root, int x);
void BTDestroy(BTNode* root);void LevelOrder(BTNode* root);
bool BTComplete(BTNode* root);
--------------------------------Queue.h--------------------------------
#pragma once//任务1:声明需要使用的头文件
//任务2:定义队列的结构体
//任务3:声明队列的接口函数//任务1:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>//任务2:
typedef struct BinaryTreeNode* QDateType; //使用typedf重新定义队列中的变量的数据类型,方便后续进行修改//队列节点的结构体
typedef struct QueueNode
{//1.队列节点中存储的数据 ---> 一个int变量//2.队列节点中存储下一个节点的位置 ---> 一个指针QDateType val;struct QueueNode* next; //注意这里next的类型可千万不要使用QNode哦,因为这时你还没有定义QNode,也不要使用QueueNode进行代替
}QNode;//队列的结构体(使用带头尾指针的结构)
typedef struct Queue
{//1.记录当前队列中元素的个数 ---> 一个int变量//2.指向头部的指针 ---> (用于:头删/取队头的元素)//3.指向尾部的指针 ---> (用于:尾插/取队尾的元素)int size;QNode* queHead;QNode* queTail;
}Que;//任务3:
//1.队列的初始化
//2.队列的销毁//3.队列的入队
//4.队列的出队//5.队列的取队头元素
//6.队列的取队尾元素//7.队列的判空
//8.队列的求队列中元素的数量void QueInit(Que* pque);
void QueDestroy(Que* pque);void QuePush(Que* pque, QDateType x);
void QuePop(Que* pque);QDateType QueFront(Que* pque);
QDateType QueBack(Que* pque);bool QueEmpty(Que* pque);
int QueSize(Que* pque);

实现文件

------------------------------BinaryTree.c------------------------------#define _CRT_SECURE_NO_WARNINGS 1#include "BinaryTree.h"//0.实现:“二叉树的节点的创建 + 初始化”操作
/****************************************************************** @brief 动态创建二叉树节点* @param x 节点要存储的数据值* @return 成功返回节点指针,失败返回NULL* @note 使用malloc动态分配内存*       典型错误处理:*       1. 内存分配失败时打印错误信息*       2. 新节点的左右指针初始化为NULL*****************************************************************/
BTNode* BTCreatNode(int x)
{/*--------第一阶段:动态申请节点的内存空间--------*///1.使用malloc进行动态的内存分配BTNode* node = (BTNode*)malloc(sizeof(BTNode));//2.检查内存是否开辟成功if (node == NULL){perror("malloc fail");return NULL;}/*--------第二阶段:初始化节点的数据--------*/node->data = x;node->left = NULL;node->right = NULL;/*--------第二阶段:返回开辟的二叉树节点的内存地址--------*/return node;
}//1.实现:“二叉树的前序遍历”操作
/****************************************************************** @brief 二叉树前序遍历(递归实现)* @param root 当前遍历的子树根节点* @note 遍历顺序:根节点->左子树->右子树*       输出格式:非空节点输出数据,空节点输出"N "*       示例输出:1 2 3 N N N 4 5 N 6 N N 6 N N*****************************************************************/void PrevOrder(BTNode* root)
{//1.递归结束的出口if (root == NULL){printf("N "); //也可以不写,只是为了方便初学者能更好的理解前序遍历而添加的return;}//2.按照:根节点 ---> 左子树 ---> 右子树 的顺序进行递归遍历printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}//2.实现:“二叉树的中序遍历”操作
/****************************************************************** @brief 二叉树中序遍历(递归实现)* @param root 当前遍历的子树根节点* @note 遍历顺序:左子树->根节点->右子树*       输出格式同前序遍历*       示例输出:N 3 N 2 N 1 N 5 N 6 N 4 N 6 N*****************************************************************/void InOrder(BTNode* root)
{//1.递归结束的出口if (root == NULL){printf("N ");return;}//按照:左子树 ---> 根节点 ---> 右子树 的顺序进行递归遍历InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}//3.实现:“二叉树的后序遍历”操作
/****************************************************************** @brief 二叉树后序遍历(递归实现)* @param root 当前遍历的子树根节点* @note 遍历顺序:左子树->右子树->根节点*       输出格式同前序遍历*       示例输出:N N 3 N 2 N N 6 5 N N 6 4 1*****************************************************************/void PostOrder(BTNode* root)
{//1.递归结束的出口if (root == NULL){printf("N ");return;}//2.按照:左子树 ---> 右子树  ---> 根节点 的顺序进行递归遍历PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}//4.实现:“二叉树的求树中节点的个数”操作
/****************************************************************** @brief 计算二叉树节点总数(递归)* @param root 当前子树根节点* @return 以root为根的树的节点总数* @note 计算公式:*       空树:0*       非空树:左子树节点数 + 右子树节点数 + 1(当前节点)*****************************************************************/int BTSize(BTNode* root)
{//1.处理:空树 + 空节点 的情况(同样是递归结束的条件)if (root == NULL) return 0;else{return BTSize(root->left) + BTSize(root->right) + 1;}//跟简洁的写法://return root == NULL ? 0 : BTSize(root->left) + BTSize(root->right) + 1;
}//5.实现:“二叉树的求树中叶子节点的个数”操作
/****************************************************************** @brief 计算二叉树叶子节点数(递归)* @param root 当前子树根节点* @return 以root为根的树的叶子节点数* @note 叶子节点定义:无左右子树的节点*       计算公式:*       空树:0*       叶子节点:1*       非叶子节点:左子树叶子数 + 右子树叶子数*****************************************************************/int BTLeafSize(BTNode* root)
{//1.处理:空树 + 一度节点 的情况 (同样是递归结束的条件)if (root == NULL) return 0;//2.使用if判断如果:当前遍历到的节点是叶子节点 (同样是递归结束的条件)if (root->left == NULL && root->right == NULL) return 1;else{return BTLeafSize(root->left) + BTLeafSize(root->right);}}//6.实现:“二叉树的求树的高度”操作
/****************************************************************** @brief 计算二叉树高度/深度(递归)* @param root 当前子树根节点* @return 以root为根的树的高度* @note 高度定义:*       空树高度为0*       非空树高度为max(左子树高, 右子树高) + 1*       优化:避免重复计算左右子树高度*****************************************************************/int BTHeight(BTNode* root)
{//1.处理:空树 +if (root == NULL) return 0;//2.计算非空树的高度//2.1:递归计算左子树和右子树的高度  --->  进行优化(这里先将结果计算出来,避免进行重复的计算)int leftHeight = BTHeight(root->left);int rightHeight = BTHeight(root->right);//2.2:返回非空树的高度return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}//7.实现:“二叉树的求树中第K层节点的个数”操作
/****************************************************************** @brief 计算二叉树第k层节点数(递归)* @param root 当前子树根节点* @param k 目标层数(从1开始计数)* @return 第k层节点数* @note 计算公式:*       空树或k<1:0*       k==1:1(当前节点)*       其他:左子树k-1层节点数 + 右子树k-1层节点数*****************************************************************/int BTLevelKSize(BTNode* root, int k)
{//1. 处理:空树 + 一度节点 情况if (root == NULL) return 0;//2.使用if判断:如果当前K值为1if (k == 1)  return 1;//3.递归计算左右子树的第K层return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
}//8.实现:“二叉树的寻找树中的某个节点的位置”操作
/****************************************************************** @brief 在二叉树中查找值为x的节点(递归)* @param root 当前子树根节点* @param x 要查找的值* @return 找到的节点指针,未找到返回NULL* @note 查找顺序:*       1. 检查当前节点*       2. 递归查找左子树*       3. 若左子树未找到,递归查找右子树*       注意:如果树中存在多个x,返回最先找到的节点*****************************************************************/
BTNode* BTFind(BTNode* root, int x)
{//1.处理:“空树 + 遍历到空节点”的情况if (root == NULL) return NULL;//2.使用if判断:如果遍历到的节点的值是xif (root->data == x) return root;//3.进行递归查找//3.1:先在左子树中进行递归查找BTNode* ret = BTFind(root->left, x);if (ret) return ret;//3.2:再在右子树中进行递归操作ret = BTFind(root->right, x);if (ret) return ret;//4.返回空指针标志未找到return NULL;//可以将“在右子树寻找 + 未找到”的情况进行合并简化//原因:在右子树中没有找到:(代表整棵树中真的没有你想找的这个值)//return BTFind(root->right, x);
}//9.实现:“二叉树的销毁”操作
/*** @brief 销毁二叉树(后序遍历方式)* @param root 二叉树根节点指针* @note 递归释放所有节点内存*       采用后序遍历顺序:先销毁左子树,再销毁右子树,最后释放当前节点*/
void BTDestroy(BTNode* root)
{//1.处理:“空树 + 遍历到空节点”的情况if (root == NULL)  return;//2.使用后序遍历的方式进行递归的销毁BTDestroy(root->left);BTDestroy(root->right);free(root);
}//10.实现:“二叉树的层序遍历”操作
/*** @brief 二叉树层序遍历(广度优先遍历)* @param root 二叉树根节点指针* @note 使用队列辅助实现:*       1. 根节点入队*       2. 循环取出队首元素并处理*       3. 将其左右子节点入队*       时间复杂度:O(n),空间复杂度:O(n)*/
void LevelOrder(BTNode* root)
{//1.创建队列 + 初始化队列Que que;QueInit(&que);//2.将二叉树的根节点入队if (root){QuePush(&que, root);}//3.遍历队列来实现树的层序遍历while (!QueEmpty(&que)){//3.1:将队头元素:“记录 + 出队 + 输出”BTNode* front = QueFront(&que);QuePop(&que);printf("%d ", front->data);//3.2:将队头的二叉树的节点的左孩子和右孩子添加到队列中if (front->left) QuePush(&que, front->left); //注意:这里是(front->left而不是root->left)if (front->right) QuePush(&que, front->right);}//4.销毁队列QueDestroy(&que);
}//11.实现:“二叉树的判断其是否是一棵完全二叉树”操作
/*** @brief 判断是否是完全二叉树* @param root 二叉树根节点指针* @return true-是完全二叉树,false-不是完全二叉树* @note 完全二叉树判定条件:*       1. 层序遍历遇到第一个NULL节点后,后续不应再有非NULL节点*       2. 使用队列辅助实现,NULL节点也入队*       时间复杂度:O(n),空间复杂度:O(n)*/
bool BTComplete(BTNode* root)
{//1.创建队列 + 初始化队列Que que;QueInit(&que);//2.将二叉树的根节点入队if (root) QuePush(&que, root);//3.按照层序遍历:“找到层序遍历的第一个空节点的位置”while (!QueEmpty(&que)){//3.1:将队头元素:“记录 + 出队 + 判断”BTNode* front = QueFront(&que);QuePop(&que);if (front == NULL) break;//3.2:将队头的二叉树的节点的左孩子和右孩子添加到队列中 QuePush(&que, front->left); //注意:这里是无论子节点是否为空都将其入队QuePush(&que, front->right);}//4.按照层序遍历:“检查剩余节点是否有非空节点”while (!QueEmpty(&que)){//3.1:将队头元素:“记录 + 出队 + 判断”BTNode* front = QueFront(&que);QuePop(&que);if (front != NULL){QueDestroy(&que);return false;}}QueDestroy(&que);return true;
}
------------------------------Queue.c------------------------------
#include "Queue.h"//1.实现:“队列的初始化”操作
void QueInit(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列结构体中“指向队头/队尾的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//2.实现:“队列的销毁”操作
void QueDestroy(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列中的所有的节点都释放掉//1)创建临时指针代替queHead进行遍历队列的节点QNode* tmp = pque->queHead;//2)使用tmp遍历所有的节点while (tmp != NULL){//3)创建后继指针存储当前遍历到的节点的下一位置QNode* next = tmp->next;//4)释放当前遍历到的节点free(tmp);//5)更新tmp指针tmp = next;}//将队列结构体中“指向队列头部/尾部的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//3.实现:“队列的入队”操作
void QuePush(Que* pque, QDateType x)
{assert(pque);//思路:使用链表的尾插法实现入队的操作//1)先创建出来一个队列的节点QNode* newNode = (QNode*)malloc(sizeof(QNode));//1.1:判断新开辟的空间是否开辟成功哦if (newNode == NULL){perror("malloc fail");return;}//2)为这个节点进行赋值newNode->val = x;//3)将这个节点链接到队列链表的后面//3.1:将新开辟的节点的next指针域置空newNode->next = NULL;//3.2:将新开辟的节点链接到队列链表的尾部//3.3.1:如果当前的队列链表中没有节点if (pque->queTail == NULL){pque->queHead = pque->queTail = newNode;}//3.3.2:如果当前的队列链表中有节点else{pque->queTail->next = newNode;pque->queTail = newNode;}//4)队列中的元素的数量增加pque->size++;
}//4.实现:“队列的出队”操作
void QuePop(Que* pque)
{assert(pque);assert(pque->size > 0);//注意:当我们:1.删除某个数据结构中的元素时 2.从某个数据结构中取出元素的时//---> 需要判断当前的数据结构中是否还有元素//思路:使用链表的头删法实现出队操作//1)如果当前队列中只有一个元素if (pque->queHead->next == NULL){//1.1:将这一个节点删除掉free(pque->queHead);//1.2:将指向队头和队尾的指针都置为空pque->queHead = NULL;pque->queTail = NULL;}//2)如果队列中的元素有多个else{//2.1:创建一个临时的指针来存放queHead指针的下一个位置QNode* next = pque->queHead->next;//2.2:释放掉queHead指针指向的节点free(pque->queHead);//2.3:将queHead指针移动到下一个节点的位置pque->queHead = next;}//3)队列中的元素的数量减少pque->size--;
}//5.实现:“队列的取出队头元素”操作
QDateType QueFront(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queHead->val;
}//6.实现:“队列的取出队尾元素”操作
QDateType QueBack(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queTail->val;
}//7.实现:“队列的判空”
bool QueEmpty(Que* pque)
{assert(pque);return pque->size == 0; //队列中没有元素返回真;队列中有元素返回假
}//8.实现:“队列的求出队列中元素的个数”操作
int QueSize(Que* pque)
{assert(pque);return pque->size;
}

测试文件

------------------------------Test.c------------------------------
#include "BinaryTree.h"int main()
{/****************************************************************** @brief 创建固定结构的二叉树(示例)* @return 返回构建好的二叉树根节点指针* @note 构建的二叉树结构:*         1*        / \*       2   4*      /   / \*     3   5   6*          \*           6* 注意:实际应用中应该从输入构建树,这里为演示硬编码*****************************************************************//*--------第一阶段:创建二叉树的节点--------*/BTNode* node1 = BTCreatNode(1);BTNode* node2 = BTCreatNode(2);BTNode* node3 = BTCreatNode(3);BTNode* node4 = BTCreatNode(4);BTNode* node5 = BTCreatNode(5);BTNode* node6 = BTCreatNode(6);BTNode* node7 = BTCreatNode(6);/*--------第二阶段:构建二叉树的结构--------*/node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;node5->right = node7;/*--------第三阶段:测试二叉树的接口函数--------*///1.测试遍历算法 printf("前序遍历: ");PrevOrder(node1);		// 预期输出:1 2 3 N N N 4 5 N 6 N N 6 N Nprintf("\n");printf("中序遍历: ");InOrder(node1);		    // 预期输出:N 3 N 2 N 1 N 5 N 6 N 4 N 6 Nprintf("\n");printf("后序遍历: ");PostOrder(node1);       // 预期输出:N N 3 N 2 N N N 6 5 N N 6 4 1printf("\n");//2.测试树属性计算printf("节点总数: %d\n", BTSize(node1));       // 预期:7printf("叶子节点数: %d\n", BTLeafSize(node1)); // 预期:3(节点3、6、6)printf("树高度: %d\n", BTHeight(node1));       // 预期:4printf("第3层节点数: %d\n", BTLevelKSize(node1, 3)); // 预期:3(节点3、5、6)printf("第4层节点数: %d\n", BTLevelKSize(node1, 4)); // 预期:1(节点6)//3.测试查找功能 int target = 6;BTNode* found = BTFind(node1, target);if (found) 	printf("找到节点%d\n", target);else 	printf("未找到节点%d\n", target);//4.测试层序遍历printf("层序遍历: ");LevelOrder(node1);  // 预期输出:1 2 4 3 5 6 6printf("\n");//5.测试判断是否为完全二叉树if (BTComplete(node1))printf("该二叉树是完全二叉树\n");elseprintf("该二叉树不是完全二叉树\n");//6.测试二叉树销毁BTDestroy(node1);printf("二叉树已销毁\n");return 0;
}

运行成功样例

在这里插入图片描述

在这里插入图片描述

相关文章:

《数据结构初阶》【链式二叉树】

《数据结构初阶》【链式二叉树】 前言&#xff1a;---------------树---------------什么是树&#xff1f;&#x1f4cc;爱心❤小贴士&#xff1a;树与非树&#xff1f;树的基本术语有哪些&#xff1f;关于节点的一些定义&#xff1a;关于树的一些定义&#xff1a;关于森林的定…...

Oracle免费认证来袭

1、Oracle Cloud Infrastructure 2025 Foundations Associate” &#x1f517; 考证地址&#xff1a;https://mylearn.oracle.com/ou/exam-unproctored/oracle-cloud-infrastructure-2025-foundations-associate-1z0-1085-25/148056/241954 2、Oracle Cloud Infrastructure 2…...

Vim 编辑器常用快捷键速查表

Vim 编辑器常用快捷键速查表 Vim 快捷键大全 **1. 基础操作****2. 光标移动****3. 编辑文本****4. 查找替换****5. 分屏操作****6. 可视化模式** **附&#xff1a;Vim 模式切换流程图** 1. 基础操作 快捷键功能说明i进入插入模式&#xff08;光标前&#xff09;a进入插入模式&…...

从父类到子类:C++ 继承的奇妙旅程(1)

前言&#xff1a; 在前文&#xff0c;小编讲述了C模板的进阶内容&#xff0c;下面我们就要结束C初阶的旅行&#xff0c;开始进入C进阶容的旅c程&#xff0c;今天旅程的第一站就是C三大特性之一——继承的旅程&#xff0c;各位扶好扶手&#xff0c;开始我们今天的C继承的奇妙旅程…...

HTML9:页面结构分析

页面结构分析 元素名描述header标题头部区域的内容&#xff08;用于页面或页面中的一块区域&#xff09;footer标记脚部区域的内容&#xff08;用于整个页面或页面的一块区域&#xff09;sectionWeb页面的一块独立区域article独立的文章内容aside相关的内容或应用&#xff08;…...

LabVIEW超声波液位计检定

在工业生产、运输和存储等环节&#xff0c;液位计的应用十分广泛&#xff0c;其中超声波液位计作为非接触式液位测量设备备受青睐。然而&#xff0c;传统立式水槽式液位计检定装置存在受建筑高度影响、量程范围受限、流程耗时长等问题&#xff0c;无法满足大量程超声波液位计的…...

maven 安装 本地 jar

命令&#xff1a; mvn install:install-file -DgroupIdnet.pingfang.application -DartifactIdjna -Dversion5.1.0 -Dpackagingjar -DfileD:\maven\repository1\jna\5.1.0\jna-5.1.0.jarmvn&#xff1a;这是Maven的执行命令。 install:install-file&#xff1a;这是Maven插件目…...

leetcode 141. Linked List Cycle

题目描述&#xff1a; 代码&#xff1a; 用哈希表也可以解决&#xff0c;但真正考察的是用快慢指针法。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/ class Soluti…...

【Python】通过`Editable Install`模式详解,解决Python开发总是import出错的问题

摘要 田辛老师在很久以前&#xff0c;写过一篇关于Python的模块、包之间的内部关系的博客&#xff0c;叫做【Python】__init__.py 文件详解。 虽然我觉得这篇文章已经足够了&#xff0c; 但是还是有很多朋友碰到开发的过程中import包报错的问题。 今天&#xff0c; 田辛老师想…...

C 语言网络编程问题:E1696 无法打开 源 文件 “sys/socket.h“

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>在 C 语言网络编程中&#xff0c;上述代码报如下错误 E1696 无法打开 源 文件 "sys/socket.h"E1696 无法打开 源 文件 "netinet/in.h" E1696 无法打开 源 文件…...

操作指南*

任务1: 环境搭建 1.1 创建Spring Boot项目 操作步骤&#xff1a; 使用IDEA创建项目&#xff1a; 打开IDEA → File → New → Project选择 Spring Initializr → 设置项目信息&#xff08;Group、Artifact、Java版本&#xff09;选择依赖&#xff1a;Spring Web、MySQL Drive…...

VRM Add-on for Blender 学习笔记

VRM Add-on for Blender 使用教程-CSDN博客 VRM Add-on for Blender 是 Blender 的一个官方插件&#xff0c;主要用于 导入和导出 VRM 格式的 3D 模型。VRM&#xff08;Virtual Reality Model&#xff09;是一种开放标准的 3D 人形角色模型格式&#xff0c;起源于日本&#xf…...

C++ 完美转发

C 完美转发逐步详解 1. 问题背景与核心目标 在 C 模板编程中&#xff0c;若直接将参数传递给其他函数&#xff0c;参数的 值类别&#xff08;左值/右值&#xff09;和 类型信息&#xff08;如 const&#xff09;可能会丢失。例如&#xff1a; template<typename T> voi…...

学习记录:DAY23

项目开发与学习记录&#xff1a;字段注入优化 前言 我总有一种什么大的要来了的危机感。还是尽快把项目做起来吧&#xff0c;现在全在弄底层的框架。这是一个两天的blog&#xff0c;前一天bug没修好&#xff0c;气到连blog都没写。 日程 5月7日 晚上7点&#xff1a;本来想玩…...

Linux 信号(下篇)

Linux 信号-CSDN博客&#xff08;上篇&#xff09; 前言&#xff1a;在我上一篇博客写到了信号产生的三种条件分别是键盘组合键、kill命令、系统调用接口&#xff1b; 接下来我要把信号产生剩余的两个条件介绍完毕并理解信号的保存&#xff0c;和信号从产生到保存到处理整个过…...

hadoop中的序列化和反序列化(1)

1. 什么是序列化和反序列化 序列化&#xff08;Serialization&#xff09; 是将对象的状态信息转换为可以存储或传输的格式的过程。序列化后的对象可以保存到文件中&#xff0c;或者通过网络传输。 反序列化&#xff08;Deserialization&#xff09; 是序列化的逆过程&#x…...

linux查java进程CPU高的原因

问题&#xff1a;linux查java进程CPU高的原因 解决&#xff1a;用jdk带的工具分析 被查的java最好也使用jdk启动 systemctl启动的注意要去掉PrivateTmptrue /opt/jdk1.8.0_441/bin/jps -l top -Hp 8156 printf "%x" 8533 /opt/jdk1.8.0_441/bin/jstack 8156 |…...

鸿蒙开发——3.ArkTS声明式开发:构建第一个ArkTS应用

鸿蒙开发——3.ArkTS声明式开发:构建第一个ArkTS应用 一、创建ArkTS工程二、ArkTS工程目录结构&#xff08;Stage模型&#xff09;三、构建第一个页面四、构建第二个页面五、实现页面之间的跳转六、模拟器运行 一、创建ArkTS工程 1、若首次打开DevEco Studio&#xff0c;请点击…...

vue3+ts的watch全解!

vue3中的watch只能监听以下四种数据&#xff1a; 1.ref定义的数据 2.reactive定义的数据 3.函数返回一个值&#xff08;getter函数&#xff09; 4.一个包含上述内容的数组 通常我们在使用watch的时候&#xff0c;通常会遇到以下几种情况&#xff1a; 情况一&#xff1a; …...

yarn的概述

1.Yarn的定义 2.Yarn的三大组件 3.Yarn的调度策略 1. YARN的定义 YARN&#xff08;Yet Another Resource Negotiator&#xff09; 是Hadoop生态系统中的一个资源管理框架&#xff0c;用于管理和调度集群中的计算资源。它允许多个应用程序在同一个集群上高效地运行&#xff0c;…...

C++初阶-string类4

目录 1.String operations 1.1string::c_str 1.2string::data 1.3string::copy 1.4string::find 1.5string::rfind 1.6string::find_first_of 1.7string::find_last_of 1.8string::find_first_not_of和string::find_last_not_of find_first_not_of 功能 典型用途 f…...

HarmonyOS NEXT深度解析:自研框架ArkUI-X的技术革命与跨平台实践

HarmonyOS NEXT&#xff5e;深度解析&#xff1a;自研框架ArkUI-X的技术革命与跨平台实践 引言&#xff1a;ArkUI-X的诞生背景与战略意义 在HarmonyOS NEXT全面摒弃AOSP代码的历史性转折点上&#xff0c;华为推出的ArkUI-X框架标志着国产操作系统研发进入深水区。根据华为202…...

CUDA:out of memory的解决方法(实测有效)

一、问题概述 1.问题分析 CUDA out of memory问题通常发生在深度学习训练过程中&#xff0c;当GPU的显存不足以容纳模型、输入数据以及中间计算结果时就会触发。这个问题可能由几个因素引起&#xff1a; 模型和数据规模&#xff1a;深度学习模型尤其是大型模…...

canal mysqltomysql增加同步的库操作

例如增加库 online 1、停止canal.adapter 服务。 ./bin/stop.sh2、备份数据库online&#xff0c;导入目标mysql 备份 mysqldump -h 127.0.0.1 -P 3307 --single-transaction -uroot -p -B online > online.sql导入 mysql -h 127.0.0.1 -P 3308 -uroot -p < onl…...

【AI】模型与权重的基本概念

在 ModelScope 平台上&#xff0c;「模型」和「权重」的定义与工程实践紧密结合&#xff0c;理解它们的区别需要从实际的文件结构和加载逻辑入手。以下是一个典型 ModelScope 模型仓库的组成及其概念解析&#xff1a; 1. ModelScope 模型仓库的典型结构 以 deepseek-ai/deepse…...

k8s 中 deployment 管理的多个 pod 构成集群吗

在 Kubernetes (k8s) 中&#xff0c;通过 Deployment 创建的多个 Pod 本身并不构成一个“集群”&#xff0c;而是属于同一个 工作负载&#xff08;Workload&#xff09; 的多个副本实例。它们的角色是 无状态服务副本&#xff0c;而非独立的集群节点。以下是详细解释&#xff1…...

「动态规划」线性DP:股票问题合集 / LeetCode 121|122|123|188 (C++)

目录 概述 Question1 思路 算法过程 Code 复杂度 Question2 思路 解题过程 Code 复杂度 Question3 思路 解题过程 Code 复杂度 Question4 思路 解题过程 Code 复杂度 总结 概述 我们已经了解过了线性DP&#xff1a; 「动态规划」线性DP&#xff1a;最长…...

【Python os模块完全指南】从基础到高效文件操作

目录 &#x1f31f; 前言&#x1f9e9; 技术背景与价值&#x1f6a7; 当前技术痛点&#x1f6e0;️ 解决方案概述&#x1f465; 目标读者说明 &#x1f4da; 一、技术原理剖析&#x1f3a8; 核心概念图解&#x1f4a1; 核心作用讲解&#x1f511; 关键技术模块说明⚖️ 技术选型…...

Ubuntu 安装 Keepalived、LVS

Keepalived Keepalived 是什么&#xff08;高可用&#xff09; Keepalived 是一个用于实现 高可用 性&#xff08;High Availability, HA&#xff09;的服务&#xff0c;是一款基于 VRRP 协议的高可用软件&#xff0c;常用于主备切换和虚拟IP漂移&#xff0c;在服务故障时自动…...

记录一个rabbitmq因为linux主机名服务无法启动的问题

https://g.co/gemini/share/fb5a55644f6f 过程因为主机名为数字导致之间无法进行网络访问&#xff0c;导致无法开启。修改主机名解决这一问题&#xff0c;debian在系统安装时会指定一个用户名&#xff0c;一般为IP地址的第一块&#xff0c;数字导致了无法访问。 #使用命令查看…...

打造个人知识库,wsl+ollama部署deepseek与vscode集成

目前大模型应用如火如荼,各大LLM如Deepseek也都提供了在线的助手服务,结合mcp-server还可以进一步拓展到本地的工具能力。 但对于一些和本地业务和数据强相关的资料,在线的大模型训练数据集一般并不能涵盖,特别还有一些敏感或对安全要求很高的数据,使用在线大模型并不现实…...

Spring 项目无法连接 MySQL:Nacos 配置误区排查与解决

在开发过程中&#xff0c;我们使用 Nacos 来管理 Spring Boot 项目的配置&#xff0c;其中包括数据库连接配置。然而&#xff0c;在实际操作中&#xff0c;由于一些概念的混淆&#xff0c;我们遇到了一些连接问题。本文将分享我的故障排查过程&#xff0c;帮助大家避免类似的错…...

P值、置信度与置信区间的关系:统计推断的三大支柱

目录 引言一、P值是什么&#xff1f;——假设检验的“证据强度”1.1 定义1.2 判断标准&#xff1a;显著性水平 α \alpha α&#xff08;阿尔法&#xff09;1.3 示例说明 二、置信区间与置信度&#xff1a;参数估计的“不确定性范围”2.1 置信区间的定义2.2 置信度的含义 三、显…...

探索智能仓颉:Cangjie Magic开发体验

探索智能仓颉:Cangjie Magic 的开发体验与技术革新 在大型语言模型(LLM)驱动的智能体开发领域,2025年3月开源的 Cangjie Magic 以其独特的原生仓颉语言基因和三大核心技术突破,为开发者提供了一种全新的开发范式。本文将从技术架构、实际应用、开发体验及未来潜力等角度,…...

$在R语言中的作用

在 R 语言中&#xff0c;$ 是一个非常重要的操作符&#xff0c;主要用于访问对象的成员或组件。它的用途非常广泛&#xff0c;不仅限于数据框&#xff08;data frame&#xff09;&#xff0c;还可以用于列表&#xff08;list&#xff09;、环境&#xff08;environment&#xf…...

【Pandas】pandas DataFrame rolling

Pandas2.2 DataFrame Function application, GroupBy & window 方法描述DataFrame.apply(func[, axis, raw, …])用于沿 DataFrame 的轴&#xff08;行或列&#xff09;应用一个函数DataFrame.map(func[, na_action])用于对 DataFrame 的每个元素应用一个函数DataFrame.a…...

新疆地区主要灾害链总结

新疆地处亚欧大陆腹地,拥有高山(如天山、昆仑山)、盆地(如塔里木盆地、准噶尔盆地)、沙漠(如塔克拉玛干沙漠)、绿洲、内陆河流和冰川等复杂多样的地貌单元。其气候极端,干旱少雨是常态,但山区夏季暴雨集中、冬季积雪深厚,地质构造活跃,地震风险高。这些特点共同决定…...

在 Vue 2 中使用 qrcode 库生成二维码

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…...

在 Ubuntu 系统中,挂起(Suspend)和休眠(Hibernate)

在 Ubuntu 系统中&#xff0c;挂起&#xff08;Suspend&#xff09;和休眠&#xff08;Hibernate&#xff09;是两种常见的电源管理模式。以下是相关命令及说明&#xff1a; --- ### **1. 挂起&#xff08;Suspend&#xff09;** 挂起会将当前系统状态保存到内存中&#xff0…...

什么是声明式UI什么是命令式UI?鸿蒙ArkTS为什么是声明式UI-优雅草卓伊凡

什么是声明式UI什么是命令式UI&#xff1f;鸿蒙ArkTS为什么是声明式UI-优雅草卓伊凡 一、UI编程范式的根本分野 在软件开发领域&#xff0c;用户界面(UI)构建方式经历了三次重大范式转换。作为优雅草科技CTO&#xff0c;卓伊凡在多个操作系统开发实践中发现&#xff0c;UI框架…...

nRF Connect SDK system off模式介绍

目录 概述 1. 软硬件环境 1.1 软件开发环境 1.2 硬件环境 2 System Off 模式 2.1 模式介绍 2.2 注意事项 3 功能实现 3.1 框架结构介绍 3.2 代码介绍 4 功能验证 4.1 编译和下载代码 4.2 测试 4.3 使能CONFIG_APP_USE_RETAINED_MEM的测试 5 main.c的源代码文件…...

node.js 实战——餐厅静态主页编写(express+node+ejs+bootstrap)

ejs页面 <!DOCTYPE html> <html> <head><title><% title %></title><link relstylesheet href/stylesheets/style.css/><link relstylesheet href/stylesheets/font-awesome.css/><link relstylesheet href/stylesheets/f…...

晶体布局布线

1Clock时钟电路 时钟电路就是类似像时钟一样准确运动的震荡电路&#xff0c;任何工作都是依照时间顺序&#xff0c;那么产生这个时间的电路就是时钟电路&#xff0c;时钟电路一般是由晶体振荡器、晶振、控制芯片以及匹配电容组成 2.时钟电路布局 晶体电路布局需要优先考虑&…...

数据结构--树

一、树的概念 树是由n(n≥0)个节点组成的有限集合&#xff0c;它满足以下条件&#xff1a; 1. 当n0时&#xff0c;称为空树 2. 当n>0时&#xff0c;有且仅有一个特定的节点称为根节点(root) 3. 其余节点可分为m(m≥0)个互不相交的有限集合&#xff0c;每个集合本身又是一…...

5月7号.

flex布局: 表单标签: 表单标签-表单项:...

Spark 之 YarnCoarseGrainedExecutorBackend

YarnCoarseGrainedExecutorBackend executor ID , 在日志里也有体现。 25/05/06 12:41:58 INFO YarnCoarseGrainedExecutorBackend: Successfully registered with driver 25/05...

Webug4.0靶场通关笔记19- 第24关邮箱轰炸

目录 第24关 邮箱轰炸 1.配置环境 2.打开靶场 3.源码分析 4.邮箱轰炸 &#xff08;1&#xff09;注册界面bp抓包 &#xff08;2&#xff09;发送到intruder &#xff08;3&#xff09;配置position &#xff08;4&#xff09;配置payload &#xff08;5&#xff09;开…...

机器学习实战:6种数据集划分方法详解与代码实现

在机器学习项目中&#xff0c;合理划分数据集是模型开发的关键第一步。本文将全面介绍6种常见数据格式的划分方法&#xff0c;并附完整Python代码示例&#xff0c;帮助初学者掌握这一核心技能。 一、数据集划分基础函数 1. 核心函数&#xff1a;train_test_split from sklea…...

PostgreSQL 查询历史最大进程数方法

PostgreSQL 查询历史最大进程数方法 PostgreSQL 提供了多种方式来查询数据库的历史最大进程数&#xff08;连接数&#xff09;。以下是几种有效的方法&#xff1a; 一、使用统计收集器数据 1. 查看当前统计信息 SELECT max_connections, (SELECT setting FROM pg_settings …...

第十二节:图像处理基础-图像平滑处理 (均值滤波、高斯滤波、中值滤波)

在数字图像处理中&#xff0c;图像平滑&#xff08;Image Smoothing&#xff09;是去除噪声、改善图像质量的关键技术之一。通过滤波算法&#xff0c;可以有效地抑制高频噪声&#xff0c;但同时可能牺牲部分图像细节。本文将以均值滤波、高斯滤波和中值滤波为核心&#xff0c;结…...