数据结构之树与二叉树——算法与数据结构入门笔记(五)

news2024/11/27 0:37:24

CSDNlogoPost

本文是算法与数据结构的学习笔记第五篇,将持续更新,欢迎小伙伴们阅读学习。有不懂的或错误的地方,欢迎交流

引言

前面章节介绍的都是线性存储的数据结构,包括数组、链表、栈、队列。本节带大家学习一种非线性存储的数据结构,即树(tree)。

不管是在面试时,还是日常开发过程中,树都是一种曝光率极高的数据结构。可以说树是数据结构最为承上启下的部分,其可以转化为线性表(通过二叉树的线索化),也是学习图的基础。本文将介绍树的基本概念、常见类型和应用、二叉树以及 C 语言实现,帮助大家深入理解树的本质和用途。

树的基本概念

树的定义

树是 n(n>=0) 个结点的有限集。当 n=0 时,称为空树。在任意一棵非空树中应满足:

  1. 有且仅有一个特定的称为根的结点。
  2. 当 n>1 时,其余节点可分为 m(m>0) 个互不相交的有限集 T1,T2,…,Tm,其中每个集合 Ti(1<=i<=m) 本身又是一棵树,并且称为根的子树。

显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树同时也是一种分层结构,具有以下两个特点:

  1. 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
  2. 树中所有结点可以有零个或多个后继。

因此 n 个结点的树中有 n-1 条边。

基本术语

下面结合图示来说明一下树的一些基本术语和概念。

在这里插入图片描述

结点:和链表类似,树存储结构中也将存储的各个元素称为结点。例如在上图中,元素 A 就是一个结点。对于树中某些特殊位置的结点,还可以进行更细致的划分,比如:
父结点双亲结点)、孩子结点兄弟结点:以上图中的结点 A、B、C、D 为例,A 是 B、C、D 结点的父结点(也称为双亲结点),而 B、C、D 都是 A 结点的孩子结点(也称“子结点”)。对于 B、C、D 来说,它们都有相同的父结点,所以它们互为兄弟结点;
树根结点(简称根结点):特指树中没有双亲(父亲)的结点,一棵树有且仅有一个根结点。上图中,结点 A 就是整棵树的根结点;
祖先子孙:根 A 到结点 K 的唯一路径上的任意结点,称为结点 K 的祖先。如结点 B 是结点 K 的祖先,而结点 K 是结点 B 的子孙。

子树:仍以上图的树为例,A 是整棵树的根结点。但如果单看结点 B、E、F、K、L 组成的部分来说,它们也组成了一棵树,结点 B 是这棵树的根结点。通常,我们将一棵树中几个结点构成的“小树”称为这棵树的“子树”。
知道了子树的概念后,树也可以这样定义:树是由根结点和若干棵子树构成的。例如,上图这棵树就是由根结点 A 和分别以 B、C、D 为根节点的子树构成。
注意:单个结点也可以看作是一棵树,该结点即为根结点。例如上图中,结点 K、L、F 各自就可以看作是一棵树,只不过树中只有一个根节点而已。

结点的度:一个结点拥有子树的个数,就称为该结点的度(Degree)。例如上图中,根结点 A 有3个子树,它们的根节点分别是 B、C、D,因此结点 A 的度为3。
树的度:一棵树中,最大的节点的度称为树的度。如上图:所有结点中最大的度为3,所以整棵树的度就是3。
叶节点终端节点:度为0的节点称为叶节点; 如上图:I、J、F、K、L、M 等节点为叶节点。
分支节点非终端节点:度不为0的节点; 如上图:A、B、C、D 等节点为分支节点。

结点的层次:结点的层次从一棵树的树根开始定义,树根所在层为第一层,根的孩子结点所在的层为第二层,依次类推。对于上图这棵树来说,A 结点在第一层,B、C、D 为第二层,E、F、G、H、I、J 在第三层,K、L、M 在第四层。
树中结点层次的最大值,称为这棵树的深度或者高度。例如上图这棵树的深度为 4。
如果两个结点的父结点不同,但它们父结点的层次相同,那么这两个结点互为堂兄弟。例如上图中,结点 G 和 E、F、H、I、J 的父结点都在第二层,所以它们互为堂兄弟。

有序树无序树:树中结点的各子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。假设图为有序树,若将子结点位置互换,则变成一棵不同的树。
在有序树中,结点最左边的子树称为 “第一个孩子”,最右边的称为 “最后一个孩子”。拿上图这棵树来说,如果它是一棵有序树,那么以结点 B 为根结点的子树为整棵树的第一个孩子,以结点 D 为根结点的子树为整棵树的最后一个孩子。

路径路径长度:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
注意:由于树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一层的两个结点之间不存在路径。

森林:森林是 m(m≥0) 棵互不相交的树的集合。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给 m 棵独立的树加上一个结点,并把这 m 棵树作为该结点的子树,则森林就变成了树。

树的性质

树具有如下最基本的性质:

  1. 树中的结点数等于所有结点的度数加1;
  2. 度为 m m m 的树中第 i i i 层上至多有 m i − 1 m^{i-1} mi1 个结点( i ≥ 1 i\ge1 i1);
  3. 高度为 h h h m m m 叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/(m-1) (mh1)/(m1) 个结点;
  4. 具有 n n n 个结点的 m m m 叉树的最小高度为 l o g m ( n ( m − 1 ) + 1 ) log_m(n(m-1)+1) logm(n(m1)+1)

树的其它表示方法

上图中使用的是树状表示法,最基本的逻辑结构表示法,使用一棵树倒置表示,非常直观。除了这种方法之外,还有其它的方式可以表示一棵树:

请添加图片描述
上图左侧是文氏图标表示法:是使用集合以及集合包含关系描述树结构(集合之间绝不能相交,即任意两个圆圈不能有交集)。

上图右侧使用的是凹入表示法,最长条为根结点,相同长度的表示在同一层次。例如 B、C、D 长度相同,都为 A 的子结点,E 和 F 长度相同,为 B 的子结点,K 和 L 长度相同,为 E 的子结点,依此类推。

还可以使用括号表示法:将树的根结点写在括号的左边,除根结点之外的其余结点写在括号中,并用逗号分隔。例如上面的树用括号表示为 ( A , ( B ( E ( K , L ) , F ) , C ( G ) , D ( H ( M ) , I , J ) ) ) (A , ( B ( E ( K , L ) , F ) , C ( G ) , D ( H ( M ) , I , J ) ) ) (A,(B(E(K,L),F),C(G),D(H(M),I,J)))

树的存储结构

说到存储结构,自然就会想到我们前面讲过的顺序存储和链式存储两种结构。

顺序存储结构:树中某个结点的孩子可以有多个,若将树中所有结点存储到数组中,结点的存储位置无法直接反应其逻辑关系,因此简单的顺序存储结构是不能满足树的实现要求的。

链式存储结构:链式存储结构的特点,完全可以实现对树的存储结构的表示。

表示方式:实际中树有很多种表示方式, 如:双亲表示法孩子表示法孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

对于树这样的层级结构,我们观察后发现,任意—棵树,它的结点的第一个孩子如果存在就是唯一的,它的右只弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟

结点结构如下表所示:

datafirstchildrightsib

其中 data 是数据域;firstchild 为指针域,存储该结点的第—个孩子结点的存储地址;rightsib是指针域,存储该结点的右兄弟结点的存储地址。

结构定义代码如下:

/* 树的孩子兄弟表示法结构定义 */
typedef struct CSNode
{
	TElemType data;				 // 结点中的数据域
    struct CSNode *firstchild1,*rightsib;    // 指向其第一个孩子结点和下一个兄弟结点       
}CSNode,*CSTree;

对于下图左边的树来说,这种方法实现的示意图如右下图所示:

在这里插入图片描述
其实这个表示法的最大好处是它把一棵复杂的树变成了一棵二叉树,这样就可以充分利用二叉树的特性和算法来处理这棵树了。嗯?有人问,二叉树是什么?哈哈,别急,这正是接下来要重点讲的内容。

常见类型的树

二叉树

二叉树是一种最基本的树型数据结构。简单地理解,度不超过2的有序树就是二叉树。

例如,下图左侧就是一棵二叉树,而右侧则不是。

请添加图片描述
二叉树还可以继续分类,衍生出完全二叉树、满二叉树等:

满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树,如下图所示:

请添加图片描述

完全二叉树

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。满二叉树是完全二叉树的一种特殊情况。

请添加图片描述

堆也是完全二叉树的一种特殊情况,针对的是节点数据的大小对比。堆中每个节点的值都必须大于等于(或者小于等于)其子树中的每个节点的值。大于等于每个子树节点的值称为大顶堆,反正称为小顶堆,如下图所示。后面将有文章专门分析堆。
请添加图片描述

二叉搜索树

二叉搜索树是一种特殊的二叉树,又称为二叉查找树、二叉排序树等等,它实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于根结点的数据域,右子树上所有结点的数据域均大于根结点的数据域。

在这里插入图片描述
二叉排序树意味着二叉树中的数据是排好序的,顺序为左结点<根节点<右结点,这表明二叉排序树的中序遍历结果是有序的(二叉树四种遍历方式[前序遍历、中序遍历、后序遍历、层序遍历]将在下面给出)。

二叉搜索树的特点:

  1. 每个节点包含一个值,每个节点至多有两个子树。
  2. 每个节点左子树节点的值都小于自身的值,每个节点右子树节点的值都大于自身的值。
  3. 二叉搜索树的查询时间复杂度是 O ( log ⁡ N ) O(\log N) O(logN),但是随着不断的插入、删除节点,二叉树的树高可能会不断变大,当一个二叉搜索树所有节点都只有左子树或者都只有右子树时,其查找性能就退化成线性的了。

平衡二叉树

平衡二叉树又被称为 AVL 树,它在符合二叉搜索树的条件下,还满足“高度平衡”,即任何节点的两个子树的高度最大差为1。

在这里插入图片描述
平衡二叉树的产生是为了解决二叉搜索树在插入时发生线性排列的现象。平衡二叉树在插入或删除数据时,采用旋转的调整方式,使得二叉树在插入数据后保持平衡。平衡二叉树的查询时间复杂度最好情况和最坏情况都维持在 O ( log ⁡ N ) O(\log N) O(logN)。但是频繁旋转会使插入和删除牺牲掉 O ( log ⁡ N ) O(\log N) O(logN) 左右的时间,不过相对二叉搜索树来说,时间上稳定了很多。

红黑树

平衡二叉树(AVL)为了追求高度平衡,需要通过平衡处理使得左右子树的高度差必须小于等于1。高度平衡带来的好处是能够提供更高的搜索效率,其最坏的查找时间复杂度都是 O ( log ⁡ N ) O(\log N) O(logN)。但是由于需要维持这份高度平衡,所付出的代价就是当对树种结点进行插入和删除时,需要经过多次旋转实现复衡。这导致 AVL 的插入和删除效率并不高。

为了解决这样的问题,红黑树被提出了。红黑树通过将结点进行红黑着色,使得原本高度平衡的树结构被稍微打乱,平衡程度降低。红黑树不追求完全平衡,只要求达到部分平衡。这是一种折中的方案,大大提高了结点删除和插入的效率,更加实用。

在这里插入图片描述
红黑树VS平衡二叉树

在这里插入图片描述
除了上面所提及的树结构,还有许多广泛应用在数据库、磁盘存储等场景下的树结构。比如B树B+树等。这里就先不介绍了诶。

树的应用

  1. 文件系统:文件系统通常使用树的结构来组织文件和目录。树形结构的特性使得文件系统可以方便地进行文件的查找、插入和删除操作。
  2. 数据库索引:数据库索引是一种数据结构,用于加快数据库中数据的访问速度。常见的数据库索引结构,如B树和B+树,利用树的特性实现高效的数据检索。
  3. 表达式解析:在编译器和解释器中,树结构常用于表示和解析数学表达式。表达式树(Expression Tree)可以将复杂的数学表达式转化为树的形式,便于计算和优化。
  4. 图形算法:许多图形算法,如最短路径算法和最小生成树算法,都可以使用树的结构进行表示和求解。树的特性可以帮助解决图形问题中的路径搜索和优化。

二叉树

二叉树的性质

  1. 在二叉树的第 i i i 层上至多有 2 i − 1 2^{i-1} 2i1 个结点 ( i ≥ 1 ) (i\ge1) (i1)

  2. 深度为 k k k 的二叉树至多有 2 k − 1 2^k-1 2k1 个结点 ( k ≥ 1 ) (k\ge1) (k1)

  3. 对任何一棵二叉树 T,如果度为0,其叶结点个数为 n 0 n_0 n0,度为2的分支结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0=n_2 + 1 n0n2+1

  4. 具有 n n n 个结点的完全二叉树的深度为 ⌊ log ⁡ 2 n ⌋ + 1 \left\lfloor {{\log _2}n} \right\rfloor + 1 log2n+1 ⌊ x ⌋ \left\lfloor x \right\rfloor x表示不大于 x x x 的最大整数)。

  5. 如果对一棵有 n n n 个结点的完全二叉树(其深度为 ⌊ log ⁡ 2 n ⌋ + 1 \left\lfloor {{\log _2}n} \right\rfloor + 1 log2n+1)的结点按层序编号(从第1层到第 ⌊ log ⁡ 2 n ⌋ + 1 \left\lfloor {{\log _2}n} \right\rfloor + 1 log2n+1 层,每层从左到右),则对于任意结点 i i i 1 ≤ i ≤ n 1\le i \le n 1in) 有:
    (1). 如果 i = 1 i=1 i=1,则结点 i i i 是二叉树的根,无双亲;如果 i > 1 i>1 i>1,则其双亲是结点 ⌊ i / 2 ⌋ \left\lfloor i/2 \right\rfloor i/2
    (2). 如果 2 i > n 2i>n 2i>n,则结点 i i i 无左孩子(结点 i i i 为叶子结点);否则其左孩子是结点 2 i 2i 2i
    (3). 如果 2 i < n 2i<n 2i<n,则结点 i i i无右孩子;否则其右孩子是结点 2 i + 1 2i+1 2i+1

二叉树的存储结构

二叉树的顺序存储结构
前面我们已经谈到了树的存储结构,并且谈到顺序存储对树这种—对多的关系结构实现起来是比较困难的。但是二叉树是—种特殊的树,由于它的特殊性,使得用顺序存储结构也可以实现。
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树。因为不是完全二叉树会有空间的浪费。

二叉链表
既然顺序存储适用性不强,我们就要考虑链式存储结构。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。结点结构图如下表所示:

lchilddatarchild

其中 data 是数据域;lchild 和 rchild 都是指针域,分别存放指向左孩子和右孩子的指针。

以下是我们的二叉链表的结点结构定义代码:

/* 二叉树的二叉链表结点结构定义*/
typedef struct BiTNode			// 结点结构
{
	TElemType data;				 // 结点中的数据域
    struct BiTNode *lchild,*rchild;    // 左右孩子指针       
}BiTNode,*BiTree;

结构示意图如下图所示:

在这里插入图片描述

二叉树的遍历

二叉树的遍历(traversing binary tree)是二叉树的一种重要操作。二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。

这里有两个关键词:访问和次序。访问其实是要根据实际的需要来确定具体做什么,比如对每个结点进行相关计算,输出打印等,它算作是—个抽象操作。在这里我们可以简单地假定访问就是输出结点的数据信息。

二叉树的遍历次序不同干线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。

先序遍历

在这里插入图片描述
算法讲解

  • 若二叉树为空,则空操作返回,否则遍历顺序:根结点->左子树->右子树。
  • 动态图解:和上面的动态图一样,先序遍历就像一个小人从根结点开始,围绕二叉树的外圈开始跑(遇到缝隙就钻进去),按照跑的顺序,依次输出序列。

代码演示

void PreOrderTraversal(BiTree BT)
{
    if( BT != NULL ) 
    {
        printf(“%d\n”, BT->Data);        //对节点的数据进行打印          
        PreOrderTraversal(BT->Left);     //访问左子树
        PreOrderTraversal(BT->Right);    //访问右子树
    }
}

中序遍历

在这里插入图片描述
算法讲解

  • 若二叉树为空,则空操作返回,否则遍历顺序:左子树->根结点->右子树。
  • 动态图解:中序遍历就像投影仪一样,将二叉树从最左侧到最右侧依次投影到同一水平线上面,得到的从左到右的相关序列就是二叉树的中序遍历。

代码演示

void InOrderTraversal(BiTree BT)
{
    if(BT != NULL)
    {
        InOrderTraversal(BT->Left);
        printf("%d\n", BT->Data);
        InOrderTraversal(BT->Right);
    }
}

后序遍历

在这里插入图片描述
算法讲解

  • 若二叉树为空,则空操作返回,否则遍历顺序:左子树->右子树->根结点。
  • 动态图解:后序遍历就像剪葡萄,只能一个个剪,不能让超过1个的葡萄一起掉下来,那就错了。例如上图中的 B,剪去 B 后面的 D、E、H、I、J 都会掉下来(达咩),而 H 剪去只会掉下 H,规律就是这个规律。

代码演示

void PostOrderTraversal(BiTree BT)
{
    if (BT != NULL)
    {
        PostOrderTraversal(BT->Left);
        PostOrderTraversal(BT->Right);
        printf("%d\n", BT->Data);
    }
}

层次遍历

层次遍历就是从根节点开始,一层一层,从上到下,每层从左到右,依次取值。

代码演示

void LevelOrder(BiTree T){
	InitQueue(Q);				//初始化辅助队列
	BiTree p;
	EnQueue(Q,T);				//将根结点入队
	while(!IsEmpty(Q))
	{							//队列不空则循环
		DeQueue(Q,p);			//队头结点出队
		visit(p);				//访问出队结点
		if(p->1child!=NULL)
			EnQueue(Q,p->lchild);//左子树不空,则左子树根结点入队
		if(p->rchild!=NULL)
			EnQueue(Q,p->rchild);//右子树不空,则右子树根结点入队
	}
}

C语言

以下是使用C语言实现二叉树(包括创建树、插入结点、删除结点、遍历树、计算树的深度/高度和大小等基础操作)的示例代码:

#include <stdio.h>
#include <stdlib.h>

// 定义二叉树节点
typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

// 创建新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

// 插入节点(递归实现)
Node* insertNode(Node* root, int data) {
    if (root == NULL) {
        return createNode(data);
    } else {
        if (data <= root->data) {
            root->left = insertNode(root->left, data);
        } else {
            root->right = insertNode(root->right, data);
        }
        return root;
    }
}

// 删除节点
Node* deleteNode(Node* root, int data) {
    if (root == NULL) {
        return root;
    } else if (data < root->data) {
        root->left = deleteNode(root->left, data);
    } else if (data > root->data) {
        root->right = deleteNode(root->right, data);
    } else {
        if (root->left == NULL && root->right == NULL) {
            free(root);
            root = NULL;
        } else if (root->left == NULL) {
            Node* temp = root;
            root = root->right;
            free(temp);
        } else if (root->right == NULL) {
            Node* temp = root;
            root = root->left;
            free(temp);
        } else {
            Node* minRight = findMin(root->right);
            root->data = minRight->data;
            root->right = deleteNode(root->right, minRight->data);
        }
    }
    return root;
}

// 查找最小节点
Node* findMin(Node* root) {
    while (root->left != NULL) {
        root = root->left;
    }
    return root;
}

// 计算树的深度/高度
int calculateHeight(Node* root) {
    if (root == NULL) {
        return 0;
    } else {
        int leftHeight = calculateHeight(root->left);
        int rightHeight = calculateHeight(root->right);
        return (leftHeight > rightHeight) ? (leftHeight + 1) : (rightHeight + 1);
    }
}

// 计算树的大小(节点数)
int calculateSize(Node* root) {
    if (root == NULL) {
        return 0;
    } else {
        int leftSize = calculateSize(root->left);
        int rightSize = calculateSize(root->right);
        return leftSize + rightSize + 1;
    }
}

// 先序遍历(根-左-右)
void preorderTraversal(Node* root) {
    if (root != NULL) {
        printf("%d ", root->data);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
    }
}

// 中序遍历(左-根-右)
void inorderTraversal(Node* root) {
    if (root != NULL) {
        inorderTraversal(root->left);
        printf("%d ", root->data);
        inorderTraversal(root->right);
    }
}

// 后序遍历(左-右-根)
void postorderTraversal(Node* root) {
    if (root != NULL) {
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        printf("%d ", root->data);
    }
}

int main() {
    Node* root = NULL;

    // 插入节点
    root = insertNode(root, 4);
    root = insertNode(root, 2);
    root = insertNode(root, 6);
    root = insertNode(root, 1);
    root = insertNode(root, 3);
    root = insertNode(root, 5);
    root = insertNode(root, 7);

    // 先序遍历
    printf("Preorder traversal: ");
    preorderTraversal(root);
    printf("\n");

    // 中序遍历
    printf("Inorder traversal: ");
    inorderTraversal(root);
    printf("\n");

    // 后序遍历
    printf("Postorder traversal: ");
    postorderTraversal(root);
    printf("\n");

    // 删除节点
    root = deleteNode(root, 3);

    // 先序遍历(删除节点后)
    printf("Preorder traversal (after deletion): ");
    preorderTraversal(root);
    printf("\n");

    // 计算树的深度/高度
    int height = calculateHeight(root);
    printf("Tree height: %d\n", height);

    // 计算树的大小(节点数)
    int size = calculateSize(root);
    printf("Tree size: %d\n", size);

    return 0;
}

结论

树作为一种重要的数据结构,在计算机科学中有广泛的应用。通过理解树的基本概念、常见类型和应用场景,我们可以更好地理解和运用树结构解决实际问题。通过进一步的学习和实践,我们可以不断深入和拓展树的应用领域,提升算法和数据结构的能力。

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

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

相关文章

【MySQL】- 05 sql 语句练习题

sql 语句练习题 一 测试数据的准备二、数据查询1、查询"01"课程比"02"课程成绩高的学生的信息及课程分数2、查询"01"课程比"02"课程成绩低的学生的信息及课程分数3、查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4、…

MySQL数据库基础 06

第六章 多表查询 1. 一个案例引发的多表连接1.1 案例说明1.2 笛卡尔积&#xff08;或交叉连接&#xff09;的理解1.3 案例分析与问题解决 2. 多表查询分类讲解分类1&#xff1a;等值连接 vs 非等值连接等值连接非等值连接 分类2&#xff1a;自连接 vs 非自连接分类3&#xff1a…

动态网站开发02:Java Web概述

文章目录 一、 XML基础&#xff08;一&#xff09;XML概述1、XML2、XML与HTML的比较 &#xff08;二&#xff09;XML语法1、XML文档的声明2、XML元素的定义3、XML属性的定义4、XML注释的定义5、XML文件示例 &#xff08;三&#xff09;DTD约束1、什么是XML约束2、什么是DTD约束…

MM32F3273G8P火龙果开发板MindSDK开发教程5 - Gcc编译环境的配置

MM32F3273G8P火龙果开发板MindSDK开发教程5 - Gcc编译环境的配置 1、准备工作 用了几天Keil后&#xff0c;实在用不习惯&#xff0c;只好回到macos系统中来搭建gcc的编译环境。但是百问网火龙果开发板自带的DAP-Link在pyocd中根本识别不到&#xff0c;所以烧录还需要重新购置…

MySQL数据库基础 07

第七章 单行函数 1. 函数的理解1.1 什么是函数1.2 不同DBMS函数的差异1.3 MySQL的内置函数及分类 2. 数值函数2.1 基本函数2.2 角度与弧度互换函数2.3 三角函数2.4 指数与对数2.5 进制间的转换 3. 字符串函数4. 日期和时间函数4.1 获取日期、时间 4.2 日期与时间戳的转换 4.3 获…

一个关于宏定义的问题,我和ChatGPT、NewBing、Google Bard、文心一言 居然全军覆没?

文章目录 一、问题重述二、AI 解题2.1 ChatGPT2.2 NewBing2.3 Google Bard2.4 文心一言2.5 小结 一、问题重述 今天在问答模块回答了一道问题&#xff0c;要睡觉的时候&#xff0c;又去看了一眼&#xff0c;发现回答错了。 问题描述&#xff1a;下面的z的值是多少。 #define…

【Redis】Redis持久化机制RDB与AOF

目录 一、RDB 1、概念 2、RDB文件保存 3、执行RDB 4、触发RDB 5、fork原理 6、RDB的缺点 二、AOF 1、概念 2、开启AOF 3、触发AOF 4、触发重写AOF 三、区别 一、RDB 1、概念 RDB全称为Redis Database Backup File&#xff08;Redis数据备份文件&#xff09;&…

提示工程师指南3-Prompt工程-高级提示

高阶Prompting 到这一步&#xff0c;应该很明显&#xff0c;改进提示有助于在不同任务上获得更好的结果。这就是Prompt工程背后的整个理念。 虽然之前的例子很有趣&#xff0c;但在我们深入了解更高级的概念之前&#xff0c;让我们先正式地介绍一些概念。 文章目录 高阶Promp…

中国机器元宇宙手术机器人市场迎来爆发期,思哲睿能否借势上市?

手术机器人作为一种能够辅助医生进行精准、微创的外科手术的高端医疗设备&#xff0c;近年来受到了国内外医疗界的广泛关注和重视。随着我国人口老龄化、医疗需求增加、医疗技术进步等因素的推动&#xff0c;手术机器人市场规模呈现快速增长态势。 在这样一个充满机遇和挑战的市…

docker部署prometheus+grafana视图监控

效果 一、grafana可视化平台部署 docker run -d \--namegrafana \--restartalways \-p 3000:3000 \grafana/grafanagrafana我也是部署在170.110服务器上&#xff0c;192.168.170.110:3000访问grafana 默认账号密码都是admin 二、部署exportor采集信息 针对各类数据库平台系统…

ASP.NET Core MVC 从入门到精通之Html辅助标签补充及模型校验基础

随着技术的发展&#xff0c;ASP.NET Core MVC也推出了好长时间&#xff0c;经过不断的版本更新迭代&#xff0c;已经越来越完善&#xff0c;本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容&#xff0c;适用于初学者&#xff0c;在校毕业生&#xff0c…

7. user-Agent破解反爬机制

文章目录 1. 为什么要设置反爬机制2. 服务器如何区分浏览器访问和爬虫访问3. 反爬虫机制4. User-Agent是什么5. 如何查询网页的User-Agent6. user-agent信息解析7. 爬虫程序user-agent和浏览器user-agent的区别8. 代码查看爬虫程序的user-agent9. 在代码中加入请求头信息 1. 为…

wkhtmltopdf踩坑记录

1. 不支持writing-mode。 需求是文字纵向排列&#xff0c;内容从左到右&#xff0c;本来用的是writing-mode: tb-rl;&#xff0c;插件转pdf后发现失效。 解决方法&#xff1a; 让每一列文字单独用一个div容器包裹&#xff0c;对它的宽度进行限制&#xff0c;控制每一行只能出现…

MySQL 搭建数据库表

创建MySQL数据表需要以下信息&#xff1a; 表名表字段名定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法&#xff1a; CREATE TABLE table_name (column_name column_type); 复制 以下例子中我们将在 RUNOOB 数据库中创建数据表runoob_tbl&#xff1a; CREATE …

2023第十六届“认证杯”数学建模网络挑战赛第一阶段比赛经历分享

个人名片&#xff1a; &#x1f405;作者简介&#xff1a;一名大二在校生&#xff0c;热爱生活&#xff0c;爱好敲码&#xff01; \ &#x1f485;个人主页 &#x1f947;&#xff1a;holy-wangle ➡系列内容&#xff1a; &#x1f5bc;️ tkinter前端窗口界面创建与优化 &…

【C++】特殊类

目录 前言 一、设计一个类&#xff0c;不能被拷贝 C98方法 C11方法 二、设计一个类&#xff0c;只能在堆上创建对象 方法一 方法二 三、设计一个类&#xff0c;只能在栈上创建对象 方法一 方法二 四、设计一个类&#xff0c;不能被继承 C98方式 C11方法 五、设计…

darknet yolo标注、训练详细说明

文章目录 1、标注数据1.1、标注1.2、生成训练列表文件train.txt1.3、转换数据标注格式 2、训练数据整理2.1、修改train.txt路径2.2、修改yolov3.cfg2.3、obj.name和obj.data2.4、训练脚本文件trian.sh2.5、测试脚本文件test.sh 3、训练 本文对应的脚本文件和程序下载链接 darke…

chatgpt赋能python:Python如何保存成文件

Python如何保存成文件 Python是一种十分强大和流行的编程语言&#xff0c;它的灵活性和易于使用使得它成为了一个应用广泛的语言。在Python中&#xff0c;我们可以使用多种方式将编写的代码保存成文件&#xff0c;从而能够实现对需要的内容的复用和分享。本文将会介绍Python中…

利用谷歌DevTool解决web网页内存泄漏问题

目录 web网页内存泄漏 主要的内存泄漏来源 利用谷歌DevTool定位内存泄漏问题 性能Performance 主要功能 Performance insights性能数据分析 Memory内存 三种模式 相关概念 解决内存泄漏问题 第一步 &#xff1a;是否内存泄漏&#xff1a;js堆直增不降&#xff1b;降…

001Mybatis常用的网站及工具

MyBatis中文网https://mybatis.net.cn/ MyBatis Dynamic SQL – MyBatis Dynamic SQLhttps://mybatis.org/mybatis-dynamic-sql MyBatis GitHubMyBatis has 37 repositories available. Follow their code on GitHub.https://github.com/mybatis/ mybatis-spring-boot-auto…