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

初阶数据结构:树---堆

目录

一、树的概念

二、树的构成

(一)、树的基本组成成分

(二)、树的实现方法

三、树的特殊结构------二叉树

(一)、二叉树的概念

(二)、二叉树的性质

(三)、特殊二叉树

1、满二叉树

2、完全二叉树

四、二叉树的结构------顺序存储------堆

(一)、二叉树的顺序存储

(二)、堆的概念

(三)、堆的实现

1、堆的构建

2、堆的插入

3、堆的删除

4、获取堆顶元素、堆的有效元素个数和堆的判空

(四)、堆排序

1、堆排序的概念

2、堆排序的实现

(五)、TOP-K问题


在数据结构的广阔领域中,树与堆犹如两颗璀璨的明星,散发着独特的魅力。它们不仅是计算机科学的基础,更是解决各种复杂问题的有力武器。树,以其独特的层次结构,模拟了现实世界中的众多场景,如文件系统的目录结构、公司的组织架构等,让我们能够清晰地梳理和管理复杂的数据关系。而堆,作为一种特殊的完全二叉树,凭借其高效的插入、删除和查找操作,在优先队列、排序算法等领域发挥着关键作用 。那树究竟是什么呢?

一、树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。逻辑结构如图所示:

树有一个特殊的结点,称为根结点,根节点没有前驱结点

任何一颗树都是由根和子树构成。

树中,子树是不相交的,除了根节点外,每个节点有且仅有一个父节点;

一颗有N个节点的树有N-1条边(根节点无前驱节点,故少一条);

除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继 因此,树是递归定义的。

二、树的构成

(一)、树的基本组成成分

让我们来了解一下一颗树的组成结构吧。

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林(也即我们后续需要学到的并查集);

(二)、树的实现方法

我们通过上述已经了解到树是递归式的,那我们如何实现上图所示的树呢?

看了上图后是不是一下子就想到了链表,链表是由一个一个的节点链接构成,上图所示的树也是由一个一个的节点构成,那我们能不能按实现链表的思想来实现树呢?

但问题是,链表是线性结构,每个节点只需要与一个节点链接即可,但树可不同,有的节点和一个节点链接,有的和几个节点链接,构成节点的结构体可不能随情况不同而变化。那我们能不能对链接不同个节点分别构建节点结构体呢?链接节点个数的情况可以是无限的,真这样做只能说吃力不讨好。这就不得不提到一个非常巧妙的方法--------左孩子右兄弟表示法。

先来看看它的概念:

树中的每个节点除了自身的数据域,还设置两个指针域,分别指向该节点的第一个孩子节点和它的右兄弟节点。节点的左指针指向其第一个孩子,右指针指向其紧邻的右兄弟。若节点没有孩子或右兄弟,则相应指针为 None 或 NULL 。

代码如下:

typedef struct TreeNode
{//存储数据int data;//存储左孩子指针struct TreeNode* left;//存储右兄弟地址struct TreeNode* right;
}TreeNode;

通过这个表示法,上图的树就变成了这样:

它将这样一棵普通树转化为一棵二叉树。这一转化意义非凡,因为二叉树的算法和操作相对成熟,我们可以借助二叉树的处理方式来解决普通树的问题,大大简化了操作难度。而且左孩子右兄弟表示法在空间利用上表现出色。它不再需要为每个节点预留大量不确定的子节点指针,节省了内存空间。在操作效率方面,基于二叉树的遍历算法,如前序遍历、中序遍历和后序遍历,都能直接应用到由左孩子右兄弟表示法转化而来的二叉树上,使得树的遍历、查找、插入和删除等操作都能以相对较低的时间复杂度完成。

对于当前的我们来说,我们对于树,只需要做到了解即可,我们的侧重点是树的一种特殊结构二叉树。

那什么是二叉树呢?重点在于二叉两个字。

三、树的特殊结构------二叉树

(一)、二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

1. 或者为空

2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

二叉树并不是每一个节点都与另外两个节点链接。二叉树只是不存在度大于2的节点。且二叉树是有左右之分的,它的次序不能颠倒,故二叉树是有序的。如图所示:

上图所示皆为二叉树,因此在对待二叉树时,一定不要忘记NULL,且对于任意的二叉树都是由以下几种情况复合而成的:

(二)、二叉树的性质

  1.  若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有(2^(i-1)) 个结点。
  2.  若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2^h-1)。
  3.  对任何一棵二叉树, 如果度为0其叶结点个数为 n, 度为2的分支结点个数为n-1。
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log(n+1)。(log(n+1)是以2为底,(n+1)为对数)。
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有:  
  • 1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点。
  • 2. 若2i+1=n否则无左孩子。
  • 3. 若2i+2=n否则无右孩子。

(三)、特殊二叉树

在二叉数中,有一些特殊的二叉树,分别是满二叉树和完全二叉树。同时,满二叉树也可以说是一种特殊的二叉树。

1、满二叉树

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是(2^k-1),则它就是满二叉树。

为什么说满二叉树的节点总数为(2^k-1)呢?

也即第k层节点个数为(2^(k-1)),构成了一个等比数列。使用等比数列求和公式可得(2^k-1)。

2、完全二叉树

完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 

完全二叉树其实就是前n-1层都是满的,最后一层要从左到右是连续的。

完全二叉树的节点范围为[2^(n-1),2^n-1],高度范围为[logN+1,log(N+1)]。

四、二叉树的结构------顺序存储------堆

(一)、二叉树的顺序存储

二叉树有两种结构,分别是顺序存储和链式存储。从字面上看,我们就可以知道,分别是在数组上和链表上来存储二叉树。

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面 如红黑树等会用到三叉链。

二叉树在数组上存储就构成了我们这次博客的主题------堆。

二叉树顺序存储的逻辑结构和物理结构是不一样的,我们需要通过数组去看到存储的二叉树是不容易的,因此,要想学好,还是要多画图。二叉树的顺序存储逻辑结构如下图所示:

二叉树顺序存储的物理结构如下图所示:

若想要学好堆,就不得不对父节点和子节点在顺序存储中的关系记忆深刻:

我们假设parent为父节点在数组中的下标,children为子节点在数组中的下标,则有:

  • parent=(children-1)/2;
  • leftchildren=parent*2+1;
  • rightchildren=parent*2+2;

注意:使用数组存储二叉树会不合适,因为会浪费很多空间。数组存储表示二叉树只适合完全二叉树。如图所示:

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

(二)、堆的概念

堆是一种特殊的完全二叉树,它满足以下两个重要特性:

  • 结构性:堆是一棵完全二叉树,这意味着除了最后一层外,每一层的节点都是满的,且最后一层的节点从左到右依次排列 。这种规整的结构使得堆可以高效地使用数组进行存储。在一个包含 10 个元素的堆中,其完全二叉树结构可以清晰地映射到数组中,根节点存储在数组的第一个位置,根节点的左子节点存储在数组的第二个位置,右子节点存储在第三个位置,以此类推。通过这种方式,利用数组下标就能快速定位节点的父子关系,大大提高了操作效率。
  • 有序性:堆分为大顶堆(大根堆或大堆)小顶堆(小根堆或小堆) 。在大顶堆中,每个节点的值都大于或等于其左右子节点的值,堆顶元素是堆中的最大值 。而在小顶堆中,每个节点的值都小于或等于其左右子节点的值,堆顶元素是堆中的最小值 。以一个存储学生成绩的大顶堆为例,堆顶元素就是所有学生中的最高成绩,通过这种有序性,在需要获取最大值或最小值时,能够快速定位到目标元素,节省查找时间。

堆的功能:

  • 堆的构建 void HeapCreate(Heap* hp, int* a, int n);此处堆的构建是对于一个存满数据的数组进行堆的构建,它是先申请空间,然后使用堆的插入函数将数组中的元素逐一插入来构建堆。
  •  堆的销毁 void HeapDestory(Heap* hp);
  •  堆的插入 void HeapPush(Heap* hp, HPDataType x);
  •  堆的删除 void HeapPop(Heap* hp);
  •  取堆顶的数据 HPDataType HeapTop(Heap* hp);
  • 堆的数据个数 int HeapSize(Heap* hp);
  • 堆的判空 int HeapEmpty(Heap* hp);

(三)、堆的实现

我们需要先定义一个结构体,结构体和顺序表一样,毕竟都是在数组上进行存储,代码如下:

typedef struct Heap
{int* data;//数组,方便扩容int size;//计数器int capicity;//容量大小
}Heap;

1、堆的构建

我们先来看如何对一个存满数据的数组进行堆的构建,也即建堆。代码如下:

//两数交换函数
void Swap(int* a, int* b)
{int c = *a;*a = *b;*b = c;
}
void HeapCreate(int* a, int n)
{//这里建的是大根堆,故父节点一定要比子节点大//循环遍历要建堆的数组的每一个数for (int i = 0; i < n; i++){int children = i;int parent = (children - 1) / 2;//当子节点都小于0时,父节点一定小于0,如果用父节点>=0,来做//循环结束条件,会导致越界访问或者缺失比较数据while (children > 0){//建的是大根堆if (a[parent] < a[children]){//父节点比子节点小,就互换,让大的来当父节点Swap(&a[parent], &a[children]);//循环的递近条件,让大的节点互换后与当前节点的父节点再度比较,直到小于children = parent;parent = (children - 1) / 2;}else{break;}}}
}
int main()
{int a[10] = { 1,0,5,6,3,2,4,9,8,7 };HeapCreate(a, 10);for (int i = 0; i < 10; i++){printf("%d ", a[i]);}return 0;
}

看图解释:

上述代码结果为:

教你一个简便的方法快速判断是否为堆:

开始时:

运用画图功能将其拼成一个二叉树,根据大根堆或者小根堆的概念进行判断,如下图就是一个大根堆。如果没有电脑这种方法就不可行吗?当然可行。我们可以直接看数组,运用堆的性质比较即可。

而另一种堆的构建,堆的初始化、堆的扩容,堆的销毁和顺序表一样。代码如下:

//堆的初始化
void HeapInte(Heap* hp)
{assert(hp);hp->data = (int*)malloc(sizeof(int) * 4);if (hp->data == NULL){perror("Inte::malloc");return;}hp->size = 0;hp->capicity = 4;
}
//动态空间扩容
void HeapCapicity(Heap* hp)
{assert(hp);if (hp->capicity == hp->size){hp->data = (int*)realloc(hp->data, hp->capicity * 2 * sizeof(int));if (hp->data == NULL){perror("Capicity::realloc");return;}hp->capicity *= 2;printf("扩容成功\n");}
}
//堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->data);hp->data = NULL;hp->capicity =hp->size=0;printf("销毁成功\n");
}

2、堆的插入

堆的插入类似于顺序表的尾删,只是再插入后还需通过向上调整建堆使得数组上的数据始终符合堆的性质。

什么是向上调整建堆呢?

向上调整建堆是一种构建堆的方法,通常用于在已有部分堆结构的基础上插入新元素时维护堆的性质,也可以用来从零开始构建堆。对于大顶堆,堆中每个节点的值都大于或等于其子节点的值;对于小顶堆,堆中每个节点的值都小于或等于其子节点的值。

向上调整的核心思想是:从当前节点开始,将其与父节点比较,如果不满足堆的性质(例如在大顶堆中当前节点的值大于父节点的值),则交换当前节点和父节点,然后继续向上比较,直到满足堆的性质或者到达根节点。

向上调整建堆的时间复杂度为O(N*logN)。

而上述我们直接对数组进行建堆操作,而不另开辟空间的代码中使用的就是向上调整建堆。代码如下:

void HeapPush(Heap* hp, int x)
{assert(hp);//检查空间是否充足,不足则扩容HeapCapicity(hp);//插入数据hp->data[hp->size] = x;//我们不能确定插入后是否符合,故采用向上调整法。int chile = hp->size;int parent = (chile - 1) / 2;//和之前直接对数组操作建堆一样while (chile > 0){if (hp->data[chile] > hp->data[parent]){Swap(&hp->data[chile], &hp->data[parent]);chile = parent;parent = (chile - 1) / 2;}else{break;}}hp->size++;
}

看到这,你是不是有一个疑惑,既然有向上调整建堆,那是不是还有向下调整建堆呢?

当然是有的,而且向下调整建堆比向上调整建堆效率更高。那向下调整建堆又该如何做呢?

向下调整建堆是一种高效的建堆算法,其核心思想是从最后一个非叶子节点开始,依次对每个节点进行向下调整操作,使得每个子树都满足堆的性质,最终整个树成为一个堆。

对于一个包含 n个元素的数组表示的完全二叉树,最后一个非叶子节点的索引是 (n-2)/2。也可以直接找最后一个元素的父节点,也即最后一个非叶子节点。从这个节点开始,依次向前遍历每个非叶子节点,对其进行向下调整操作,将以该节点为根的子树调整为堆。

向下调整操作是指:比较当前节点与其左右子节点的值,如果不满足堆的性质(对于大顶堆,当前节点的值应大于等于其子节点的值;对于小顶堆,当前节点的值应小于等于其子节点的值),则将当前节点与值最大(大顶堆)或最小(小顶堆)的子节点交换,然后继续对交换后的子节点进行向下调整,直到满足堆的性质或者到达叶子节点。

向下调整建堆的时间复杂度为O(N)。

以下图为例:

代码如下:

//n-1为最后一个元素的下标,(下标-1)/2得到父节点
for (int i = (n - 2) / 2; i >= 0; i--)
{int parent = i;//因为父节点的左右子节点始终在一起,故右子节点下标为左子节点下标加1//因此只需一个变量int child = parent * 2 + 1;//向下调整操作,左孩子下标不要超过数组元素个数并且右孩子下标也不要超过while (child<n){//因为是建大堆,故父节点要和子节点大的比较//如果是建小堆,则父节点要和子节点小的比较if ((child + 1) < n && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}

3、堆的删除

堆的删除如果只是删除堆底的话那就没什么好说的,只需要计数器size减一即可,且不会破坏堆的结构,但如果是删除堆顶元素呢?

如果删除堆顶元素,那么不但要将堆顶元素后的所有元素往前移一下,而且堆的结构也被破坏了,需要重新构建堆。这样的话过于麻烦。有没有什么更好的办法呢?

我们可以让堆顶元素和堆底元素互换,然后像删除堆底元素一样删除,最后使用向下调整法重构堆,以此来达到删除堆顶元素的操作,且效率很高。代码如下:

void HeapPop(Heap* hp)
{assert(hp);if (hp->size == 0){printf("无数据\n");return;}//先交换堆顶元素和堆底元素Swap(&hp->data[0], &hp->data[hp->size - 1]);hp->size--;int parent = 0;int leftchile = parent * 2 + 1;//向下调整建堆while ((leftchile+1)<hp->size && leftchile < hp->size){if (hp->data[leftchile] > hp->data[leftchile + 1]){if (hp->data[parent] < hp->data[leftchile]){Swap(&hp->data[parent],&hp->data[leftchile]);parent = leftchile;leftchile = leftchile * 2 + 1;}else{return;}}else{if (hp->data[parent] < hp->data[leftchile+1]){Swap(&hp->data[parent], &hp->data[leftchile+1]);parent = leftchile+1;leftchile = (leftchile+1) * 2 + 1;}else{return;}}}
}

4、获取堆顶元素、堆的有效元素个数和堆的判空

这三个功能的实现比较简单,因为堆就是存储在数组上的。因此代码如下:

int HeapTop(Heap* hp)
{assert(hp);return hp->data[0];
}
int HeapSize(Heap* hp)
{assert(hp);return hp->size;
}
int HeapEmpty(Heap* hp)
{assert(hp);return hp->size;
}

自此,一个堆就完成了。那么堆有什么用处呢?毕竟我们不会学一个没有用处的结构。我们或许都听过在排序中有一个堆排序。堆可以用来排序吗?

当然可以,而且它的速度还是比较快的。那我们如何去实现堆排序呢?

(四)、堆排序

1、堆排序的概念

堆排序(Heap Sort)是一种基于堆这种数据结构设计的高效排序算法,它利用堆的特性进行排序,时间复杂度为 O(N*longN),且是一种不稳定的排序算法。

堆排序的基本思想是利用堆的特性,先将待排序的数组构建成一个堆,然后不断地将堆顶元素(最大值或最小值)与堆的最后一个元素交换,再对剩余的元素重新调整为堆,重复这个过程直到整个数组有序。

它的排序步骤只有两步:

  1. 建堆:将待排序的数组构建成一个堆。可以使用向上调整建堆或向下调整建堆的方法,其中向下调整建堆的时间复杂度为O(N),更为常用。
  2. 排序:将堆顶元素与堆的最后一个元素交换,然后对剩余的元素重新调整为堆,重复这个过程直到整个数组有序。

注意:排升序建大堆,排降序建小堆。

2、堆排序的实现

代码如下:

建大堆排升序,使用向下调整建堆法。(这是指下图代码)

//堆排序
void Swap(int* a, int* b)
{int c = *a;*a = *b;*b = c;
}
void HeapSortdown(int* a, int n)
{//先建堆(向下调整建堆)//n-1为最后一个元素的下标,(下标-1)/2得到父节点for (int i = (n - 2) / 2; i >= 0; i--){int parent = i;//因为父节点的左右子节点始终在一起,故右子节点下标为左子节点下标加1//因此只需一个变量int child = parent * 2 + 1;//向下调整操作,左孩子下标不要超过数组元素个数并且右孩子下标也不要超过while (child<n){//因为是建大堆,故父节点要和子节点大的比较//如果是建小堆,则父节点要和子节点小的比较if ((child + 1) < n && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}}//向下调整for (int i = n-1; i >0; i--){Swap(&a[0], &a[i]);int parent = 0;int child = parent * 2 + 1;while ( child <i){if ((child + 1) < i && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}}
}

建小堆排降序,使用向上调整建堆法。(这是指下图代码)

void HeapSortup(int* a, int n)
{//先建堆(向上调整建堆,建小堆)for (int i = 1; i < n; i++){int chile = i;int parent = (chile - 1) / 2;while (chile > 0){if (a[chile] < a[parent]){Swap(&a[chile], &a[parent]);chile = parent;parent = (chile - 1) / 2;}else{break;}}}//向下调整for (int i = n-1; i>0;i--){Swap(&a[0], &a[i]);int parent = 0;int child = parent * 2 + 1;while (child <i){if ((child + 1) < i && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}}
}

同时,堆不仅仅只用于排序,它还涉及到一个问题------TOP-K问题。

(五)、TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决。

我们怎么利用堆来解决TOP-K问题呢?

我们知道大堆堆顶是最大的元素,小堆堆顶是最小元素,我们如果要求n个数的前50,那我们就建一个小堆,先存储n个数中的50个数,此时堆顶元素就是这50个元素中最小的,我们再遍历除这50个数外的所有数,让这些数都与堆顶元素比较,如果大于,则让大于的数和堆顶元素互换,并使用向下调整法保持堆为小堆状态,当遍历结束后,堆中的元素就为n个数中前50的数。

同理,求n个数中后50的数,只需建大堆重复上述步骤即可。这里代码就不给出了,大家可以自己尝试一下,如果逻辑依旧想不通,一定要画图,一定要画图,一定要画图,重要的事说三遍!!!

如果代码出现了问题,一定要灵活的使用调试功能!!!

相关文章:

初阶数据结构:树---堆

目录 一、树的概念 二、树的构成 &#xff08;一&#xff09;、树的基本组成成分 &#xff08;二&#xff09;、树的实现方法 三、树的特殊结构------二叉树 &#xff08;一&#xff09;、二叉树的概念 &#xff08;二&#xff09;、二叉树的性质 &#xff08;三&#…...

判断192.168.1.0/24网络中,当前在线的ip有哪些

需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 [rootopenEuler ~]# cat 1.sh #!/bin/bash for ip in $(seq 1 254); do ping -c 1 -W 1 "192.168.1.$ip" > /dev/null 2>&1 if [ $? …...

初始JavaEE篇 —— Spring Web MVC入门(上)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 RequestMappingg 注解介绍 Postman的介绍与使用 PostMapping 与 GetMapping 注解 构造并接收请求 接收简单参数 接收对象…...

STM32的HAL库开发-通用定时器输入捕获实验

一、通用定时器输入捕获部分框图介绍 1、捕获/比较通道的输入部分(通道1) 首先设置 TIM_CCMR1的CC1S[1:0]位&#xff0c;设置成01&#xff0c;那么IC1来自于TI1&#xff0c;也就是说连接到TI1FP1上边。设置成10&#xff0c;那个IC1来自于TI2&#xff0c;连接到TI2FP1上。设置成…...

nodejs:express + js-mdict 网页查询英汉词典,能播放.spx 声音

向 DeepSeek R1 提问&#xff1a; 我想写一个Web 前端网页&#xff0c;后台用 nodejs js-mdict , 实现在线查询英语单词&#xff0c;并能播放.spx 声音文件 1. 项目结构 首先&#xff0c;创建一个项目目录&#xff0c;结构如下&#xff1a; mydict-app/ ├── public/ │ …...

【蓝桥杯嵌入式】5_PWM

全部代码网盘自取 链接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码&#xff1a;3ii2 1、PWM占空比可调 以往届的赛题举例 将PA6、PA7分别设置为TIM16_CH1和TIM17_CH1 打开TIM16和TIM17&#xff0c;并设置PWM输出模式及其频率 设置占空比初…...

ESM-IF1:从AF2的预测结构中学习逆折叠

作者研究了从蛋白质骨干原子坐标预测蛋白质序列的问题。迄今为止&#xff0c;机器学习解决此问题的方法一直受限于可用的实验测定蛋白质结构的数量。作者使用AlphaFold2为1200万个蛋白质序列预测的结构&#xff0c;从而将训练数据扩充了近三个数量级。相比现有方法&#xff0c;…...

kafka服务端之控制器

文章目录 概述控制器的选举与故障恢复控制器的选举故障恢复 优雅关闭分区leader的选举 概述 在Kafka集群中会有一个或多个broker&#xff0c;其中有一个broker会被选举为控制器&#xff08;Kafka Controler&#xff09;&#xff0c;它负责管理整个集群中所有分区和副本的状态。…...

Redis双写一致性(数据库与redis数据一致性)

一 什么是双写一致性&#xff1f; 当修改了数据库&#xff08;MySQL&#xff09;中的数据&#xff0c;也要同时更新缓存&#xff08;redis&#xff09;中的数据&#xff0c;缓存中的数据要和数据库中的数据保持一致 双写一致性&#xff0c;根据业务对时间上的要求&#xff0c;…...

feign Api接口中注解问题:not annotated with HTTP method type (ex. GET, POST)

Bug Description 在调用Feign api时&#xff0c;出现如下异常&#xff1a; java.lang.IllegalStateException: Method PayFeignSentinelApi#getPayByOrderNo(String) not annotated with HTTPReproduciton Steps 1.启动nacos-pay-provider服务&#xff0c;并启动nacos-pay-c…...

开源2+1链动模式AI智能名片S2B2C商城小程序:突破流量与创意困境的新路径

摘要&#xff1a;本文深入剖析当前互联网行业中流量集中于巨头以及创意边际效应递减的困境&#xff0c;并探讨开源21链动模式AI智能名片S2B2C商城小程序在应对这些困境时所展现的独特优势与应用策略。通过对行业现状的分析以及该小程序功能特点的研究&#xff0c;旨在为企业在艰…...

python编程-内置函数compile(),exec(),complex(),eval()详解

1. compile() 函数 ‌用途‌&#xff1a;将一个字符串源代码编译为字节码对象&#xff0c;这样可以直接被Python解释器执行&#xff0c;或者通过exec()或eval()函数来执行。 ‌参数‌&#xff1a; source&#xff1a;一个字符串或AST&#xff08;抽象语法树&#xff09;对象&am…...

websocket自动重连封装

websocket自动重连封装 前端代码封装 import { ref, onUnmounted } from vue;interface WebSocketOptions {url: string;protocols?: string | string[];reconnectTimeout?: number; }class WebSocketService {private ws: WebSocket | null null;private callbacks: { [k…...

解锁C/C++:链表数据结构的奇幻之旅

目录 一、引言二、链表基础概念2.1 链表是什么2.2 链表的类型三、C 语言实现链表3.1 定义链表节点3.2 创建链表3.3 链表操作3.3.1 遍历链表3.3.2 插入节点3.3.3 删除节点3.3.4 查找节点3.4 完整示例代码四、C++ 实现链表4.1 定义链表节点类4.2 创建链表4.3 链表操作4.3.1 遍历链…...

x64、aarch64、arm与RISC-V64:详解四种处理器架构

x64、aarch64、arm与RISC-V64:详解四种处理器架构 x64架构aarch64架构ARM架构RISC-V64架构总结与展望在计算机科学领域,处理器架构是构建计算机系统的基石,它决定了计算机如何执行指令、管理内存和处理数据。x64、aarch64、arm与RISC-V64是当前主流的四种处理器架构,它们在…...

nuxt3中报错: `setInterval` should not be used on the server.

那是因为在后端渲染没有浏览器的执行环境&#xff0c;一些浏览器环境提供的对象和方法都无法使用&#xff0c;代码判断下就行。 if (import.meta.client) {setInterval(() > {}, 1000) }Import meta Nuxt API...

python编程-集合内置函数和filter(),集合常见操作

在Python中&#xff0c;列表、集合、字典是三种常用的数据结构&#xff0c;它们各自拥有一些内置函数&#xff0c;用于执行各种操作。 一、列表的常用内置函数 #‌1、append(obj)‌: 在列表末尾添加新的对象。list_a [1, 2, 3] list_a.append(4) print(list_a) # 输出: [1,…...

三极管的截止、放大、饱和区

三极管的几个区&#xff0c;都有什么用&#xff1a; 截止区&#xff1a;晶体管不导通&#xff0c;用于开关电路的“关”状态。 放大区&#xff1a;晶体管用于信号放大&#xff0c;集电极电流与基极电流成正比。 饱和区&#xff1a;晶体管完全导通&#xff0c;用于开关电路的“…...

python爬虫--简单登录

1&#xff0c;使用flask框架搭建一个简易网站 后端代码app.py from flask import Flask, render_template, request, redirect, url_for, sessionapp Flask(__name__) app.secret_key 123456789 # 用于加密会话数据# 模拟用户数据库 users {user1: {password: password1}…...

苹果公司宣布正式开源 Xcode 引擎 Swift Build145

2025 年 2 月 1 日&#xff0c;苹果公司宣布正式开源 Xcode 引擎 Swift Build145。 Swift 是苹果公司于 2014 年推出的一种开源编程语言&#xff0c;用于开发 iOS、iPadOS、macOS、watchOS 和 tvOS 等平台的应用程序。 发展历程 诞生&#xff1a;2014 年&#xff0c;苹果在全球…...

齿轮减速机和平行轴减速机有何区别?

减速机是传动系统中重要的组成部分&#xff0c;常用的减速机有四大系列&#xff0c;分别是平行轴减速机、同轴减速机、直角减速机和齿轮减速机。那么大家知道齿轮减速机和平行轴减速机投什么区别吗&#xff1f; 齿轮减速机的轴不一定是平行的&#xff0c;还可能存在相交轴或交错…...

基于Hexo实现一个静态的博客网站

原文首发&#xff1a;https://blog.liuzijian.com/post/8iu7g5e3r6y.html 目录 引言1.初始化Hexo2.整合主题Fluid3.部署评论系统Waline4.采用Nginx部署 引言 Hexo是中国台湾开发者Charlie在2012年创建的一个开源项目&#xff0c;旨在提供一个简单、快速且易于扩展的静态博客生…...

MIT6.824 Lecture 1-Introduction

balance&#xff1a;性能和容错 Faulty tolerance&#xff1a; Availablity、Recoverability、NV storage&#xff08;非易失性存储&#xff0c;比较贵&#xff09;、Replication&#xff08;多个数据副本&#xff09; consistency&#xff1a; Put&#xff08;key&#xff0c;…...

【Redis实战】投票功能

1. 前言 现在就来实践一下如何使用 Redis 来解决实际问题&#xff0c;市面上很多网站都提供了投票功能&#xff0c;比如 Stack OverFlow 以及 Reddit 网站都提供了根据文章的发布时间以及投票数计算出一个评分&#xff0c;然后根据这个评分进行文章的展示顺序。本文就简单演示…...

1Panel应用推荐:WordPress开源博客软件和内容管理系统

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…...

GGML、GGUF、GPTQ 都是啥?

GGML、GGUF和GPTQ是三种与大型语言模型(LLM)量化和优化相关的技术和格式。它们各自有不同的特点和应用场景,下面将详细解释: 1. GGML(GPT-Generated Model Language) 定义:GGML是一种专为机器学习设计的张量库,由Georgi Gerganov创建。它最初的目标是通过单一文件格式…...

MySQL主从复制原理及工作过程

一、主从复制原理 1、MySQL将数据变化记录到二进制日志中&#xff1b; 2、Slave将MySQL的二进制日志拷贝到Slave的中继日志中&#xff1b; 3、Slave将中继日志中的事件在做一次&#xff0c;将数据变化&#xff0c;反应到自身&#xff08;Slave&#xff09;的数据库 详细步骤&…...

Unity VideoPlayer播放视屏不清晰的一种情况

VideoPlayer的Rnder Texture可以设置Size,如果你的视屏是1920*1080那么就设置成1920*1080。 如果设置成其他分辨率比如800*600会导致视屏不清晰。...

发布:大彩科技DN系列2.8寸高性价比串口屏发布!

一、产品介绍 该产品是一款2.8寸的工业组态串口屏&#xff0c;采用2.8寸液晶屏&#xff0c;分辨率为240*320&#xff0c;支持电阻触摸、电容触摸、无触摸。可播放动画&#xff0c;带蜂鸣器&#xff0c;默认为RS232通讯电平&#xff0c;用户短接屏幕PCB上J5短接点即可切换为TTL电…...

Oh3.2项目升级到Oh5.0(鸿蒙Next)具体踩坑记录(一)

目录 1.自动修复部分 Cause: The project structure and configuration require an upgrade. Solution: 1. Use Migrate Assistant to auto-upgrade the project structure and configuration. 2. Manually upgrade the project structure and configuration by following th…...

pytest+request+yaml+allure 接口自动化测试全解析[手动写的跟AI的对比]

我手动写的:Python3:pytest+request+yaml+allure接口自动化测试_request+pytest+yaml-CSDN博客 AI写的:pytest+request+yaml+allure 接口自动化测试全解析 在当今的软件开发流程中,接口自动化测试扮演着至关重要的角色。它不仅能够提高测试效率,确保接口的稳定性和正确性…...

Redis存储⑤Redis五大数据类型之 List 和 Set。

目录 1. List 列表 1.1 List 列表常见命令 1.2 阻塞版本命令 1.3 List命令总结和内部编码 1.4 List典型使用场景 1.4.1 消息队列 1.4.2 分频道的消息队列 1.4.3 微博 Timeline 2. Set 集合 2.1 Set 集合常见命令 2.2 Set 集合间命令 2.3 Set命令小结和内部编码 2.…...

使用PyCharm进行Django项目开发环境搭建

如果在PyCharm中创建Django项目 1. 打开PyCharm&#xff0c;选择新建项目 2.左侧选择Django&#xff0c;并设置项目名称 3.查看项目解释器初始配置 4.新建应用程序 执行以下操作之一&#xff1a; 转到工具| 运行manage.py任务或按CtrlAltR 在打开的manage.pystartapp控制台…...

C# 综合运用介绍

.NET学习资料 .NET学习资料 .NET学习资料 C# 作为一种由微软开发的面向对象编程语言&#xff0c;在软件开发领域占据着重要地位。凭借其简洁、类型安全以及与.NET 框架的紧密结合等特性&#xff0c;C# 被广泛应用于多个领域。下面将详细介绍 C# 的综合运用。 一、C# 语言特性…...

Docker 和 Docker Compose

Docker 和 Docker Compose 是两个相关但用途不同的工具&#xff0c;它们在容器化应用的管理和部署中扮演不同的角色。以下是它们的核心区别&#xff1a; 1. 功能定位 Docker: 是一个容器化平台&#xff0c;用于创建、运行和管理单个容器。适用于单个容器应用的开发和测试。通过…...

文件上传到腾讯云存储、签名及设置过期时间

将文件上传到腾讯云对象存储&#xff08;COS&#xff0c;Cloud Object Storage&#xff09;可以通过腾讯云提供的 SDK 实现。以下是详细的步骤和示例代码&#xff0c;帮助您完成文件上传操作。 步骤 注册腾讯云账号并创建存储桶&#xff1a; &#xff08;1&#xff09;登录腾讯…...

从0开始达芬奇(6)

软件交互 就是与PR&#xff0c;AE软件进行交互。&#xff08;这个就不多说啦&#xff09; 快捷键&#xff08;以下是TIM总结的常用快捷键&#xff09;...

如何在Windows上使用Docker

引言 WSL2&#xff08;Windows Subsystem for Linux2&#xff09;是微软开发的一种技术&#xff0c;允许在 Windows 操作系统上运行 Linux 环境。它提供了一个兼容层&#xff0c;使得用户可以在 Windows 系统中直接运行 Linux 命令行工具、应用程序和开发工具&#xff0c;而无需…...

细胞计数专题 | 如何减少台盼蓝沉淀?

台盼蓝&#xff08;Trypan Blue&#xff09;是一种在生物学研究中广泛使用的染料&#xff0c;尤其常用于细胞活力检测。当细胞死亡时&#xff0c;其细胞膜会变得对台盼蓝具有通透性&#xff0c;染料因而能够进入细胞并与细胞内的蛋白质结合&#xff0c;产生染色效果。由此&…...

go流程控制

流程控制是每种编程语言控制逻辑走向和执行次序的重要部分&#xff0c;流程控制可以说是一门语言的“经脉”。 Go 语言中最常用的流程控制有 if 和 for&#xff0c;而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构&#xff0c;属于扩展类的流程控制。 if else…...

Spring Web MVC项目的创建及使用

一、什么是Spring Web MVC&#xff1f; Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中&#xff0c;通常被称为Spring MVC。 1.1 MVC的定义 MVC 是 Model View Controller 的缩写&#xff0c;它是软件工程中的一种软件架构…...

RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)

#作者&#xff1a;闫乾苓 系列前几篇&#xff1a; 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;一&#xff09;》&#xff1a;link 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;二&#xff09;》&#xff1a; lin…...

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_get_options函数

声明 就在 main函数所在的 nginx.c 中&#xff1a; static ngx_int_t ngx_get_options(int argc, char *const *argv); 实现 static ngx_int_t ngx_get_options(int argc, char *const *argv) {u_char *p;ngx_int_t i;for (i 1; i < argc; i) {p (u_char *) argv[i]…...

TCP长连接、HTTP短轮询、HTTP长轮询、HTTP长连接、WebSocket的区别

1.TCP长连接 &#xff08;1&#xff09;概念&#xff1a;该连接属于传输层的协议。客户端和服务器之间建立连接后&#xff0c;不立即断开该连接&#xff0c;而是一直保持这个状态&#xff0c;以便后续数据的持续、连续传输。&#xff08;2&#xff09;应用场景&#xff1a;适合…...

在 Flownex 中创建自定义工作液

在这篇博文中&#xff0c;我们将了解如何在 Flownex 中为流网添加和定义一种新的流体温度相关工作材料。 Flownex 物料管理界面 在 Flownex 中使用与温度相关的流体材料时&#xff0c;了解其特性与温度的关系非常重要。这种了解可确保准确预测各种热条件下的流体行为&#xff0…...

基于Spring Boot的图书个性化推荐系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

【抽象代数】1.1. 运算及关系

集合与映射 定义1. 设 为 的子集&#xff0c;定义 到 的映射 &#xff1a; 使得 &#xff0c;称 为 到 的嵌入映射。 定义2. 设 为 的子集&#xff0c; 为 到 的映射&#xff0c; 为 到 的映射&#xff0c;如果 &#xff0c;称为的开拓&#xff0c; 为 的限制&…...

拥抱开源,助力创新:IBM永久免费云服务器助力开源项目腾飞

近年来&#xff0c;开源项目蓬勃发展&#xff0c;为全球科技进步做出了巨大贡献。然而&#xff0c;服务器成本高昂常常成为开源项目的巨大障碍。许多优秀的项目因缺乏资源而难以持续发展&#xff0c;甚至夭折。令人振奋的是&#xff0c;IBM云计算平台推出了一项重磅活动&#x…...

Windows Docker笔记-简介摘录

Docker是一个开源的容器化平台&#xff0c;可以帮助开发人员将应用程序与其依赖项打包在一个独立的容器中&#xff0c;然后在任何安装的Docker的环境中快速、可靠地运行。 几个基本概念和优势&#xff1a; 1. 容器 容器是一个轻量级、独立的运行环境&#xff0c;包含了应用程…...

threejs 建筑设计(室内设计)软件 技术调研之五 墙体生成后自动生成房间(地面)

运用threejs 开发 建筑设计&#xff08;室内设计&#xff09;软件 技术调研 四 墙体添加真实门窗并保持原材质 在线体验地址&#xff1a;http://47.96.130.245:8080/design/index.html 实现功能&#xff1a; 墙体材质变换后&#xff0c;自动根据墙体的顶点生成相应的房间 视…...