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

目录

一、树的概念

二、树的构成

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

(二)、树的实现方法

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

(一)、二叉树的概念

(二)、二叉树的性质

(三)、特殊二叉树

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的数,只需建大堆重复上述步骤即可。这里代码就不给出了,大家可以自己尝试一下,如果逻辑依旧想不通,一定要画图,一定要画图,一定要画图,重要的事说三遍!!!

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】day15 142.环形链表II

142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则…

2025.2.6

一、C思维导图&#xff1a; 二、C&#xff1a; 三、注释代码 1> 配置文件&#xff1a;.pro文件 QT core gui # 引入的类库&#xff0c;core表示核心库 gui图形化界面库greaterThan(QT_MAJOR_VERSION, 4): QT widgets # 超过版本4的qt&#xff0c;会自动加widgets…

CSS(三)less一篇搞定

目录 一、less 1.1什么是less 1.2Less编译 1.3变量 1.4混合 1.5嵌套 1.6运算 1.7函数 1.8作用域 1.9注释与导入 一、less 1.1什么是less 我们写了这么久的CSS,里面有很多重复代码&#xff0c;包括通配颜色值、容器大小。那我们能否通过js声明变量来解决这些问题&…

643. 子数组最大平均数 I

目录 一、题目二、思路2.1 解题思路2.2 代码尝试2.3 疑难问题 三、解法四、收获4.1 心得4.2 举一反三 一、题目 二、思路 2.1 解题思路 和之前一样&#xff0c;用一个sum来存储统计情况&#xff0c;窗口滑动边统计&#xff0c;用两个for循环&#xff0c;一个初始化&#xff0…

go数据结构学习笔记

本博文较为完整的实现了go的链表、栈&#xff0c;队列&#xff0c;树&#xff0c;排序&#xff0c;链表包括顺序链表&#xff0c;双向链表&#xff0c;循环链表&#xff0c;队列是循环队列&#xff0c;排序包含冒牌、选择 1.链表 1.1 顺序链表 type LNode struct {data intn…

机器学习--python基础库之Matplotlib (1) 超级详细!!!

机器学习--python基础库Matplotlib 机器学习--python基础库Matplotlib0 介绍1 实现基础绘图-某城市温度变化图1.1绘制基本图像1.2实现一些其他功能 2 再一个坐标系中绘制多个图像3 多个坐标系显示-plt.subplots(面向对象的画图方法)4 折线图的应用场景 机器学习–python基础库M…

Java 23新特性

文章目录 Java 23新特性一、引言二、Markdown文档注释&#xff08;JEP 467&#xff09;示例 三、ZGC&#xff1a;默认的分代模式&#xff08;JEP 474&#xff09;1. 为什么要引入分代模式2. 使用分代模式的优势3. 如何启用分代模式 四、隐式声明的类和实例主方法&#xff08;JE…

【redis】数据类型之string

字符串类型是Redis最基础的数据结构。首先key都是字符串类型&#xff0c;而且其他几种数据结构都是在字符串类型基础上构建的&#xff0c;所以字符串类型能为其他四种数据结构的学习打下基础。 字符串类型的值实际可以是字符串&#xff08;简单的字符串、复杂的字符串&#xf…

前部分知识复习05

一、多级渐远贴图MipMap 选择贴图&#xff0c;可以勾选贴图的多级渐远效果 [IntRange]_MipMap("MipMap",Range(0,12))0 //多级渐远贴图的LOD调节滑杆 _MipMapTexture("MipMapTexture",2D)"white"{} //定义多级渐远贴图 多级渐远贴图的采样…

[高等数学]曲率

一、知识点 &#xff08;一&#xff09;弧微分 设函数 f ( x ) f(x) f(x) 在区间 ( a , b ) (a,b) (a,b) 内具有连续导数。 在曲线 y f ( x ) yf(x) yf(x) 上取固定点 M 0 ( x 0 , y 0 ) M_0(x_0,y_0) M0​(x0​,y0​) 作为度量弧长的基点&#xff0c;并规定依 x x x 增…

openGauss 3.0 数据库在线实训课程2:学习客户端工具gsql的使用

openGauss数据库状态查看 前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见&#xff1a;openGauss 3.0.0数据库在线实训课程 学习目标 学习openGauss数据库客户端工具gsql的使用。 课程作业 gsql是openGauss提供在命令行下运行的数据库连接工具&am…

模拟实现string类

目录 一.构造与析构函数 二.基础小功能的实现 1.clear 2.c_str 3外部对私有的查看 三.实现string的迭代器 四.string的增删查改 1.push_back尾插 1.1reserve扩容 1.2尾插 3.运算符重载 4.insert在任意位置插入 5.erase删除 5.1npos的处理 5.2函数的实现 6.find查…

机器学习之数学基础:线性代数、微积分、概率论 | PyTorch 深度学习实战

前一篇文章&#xff0c;使用线性回归模型逼近目标模型 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 本篇文章内容来自于 强化学习必修课&#xff1a;引领人工智能新时代【梗直哥瞿炜】 线性代数、微积分、概率论 …

记录一下 在Mac下用pyinstallter 打包 Django项目

安装: pip install pyinstaller 在urls.py from SheepMasterOneToOne import settings from django.conf.urls.static import staticurlpatterns [path("admin/", admin.site.urls),path(generate_report/export/, ReportAdmin(models.Report, admin.site).generat…

UE求职Demo开发日志#23 线性任务系统数据层实现

1 按上期设计创建数据结构&#xff08;做了一些修改&#xff09; USTRUCT(BlueprintType) struct ARPG_CPLUS_API FQuestNode {GENERATED_USTRUCT_BODY()// 记录前置节点IDUPROPERTY(EditAnywhere, BlueprintReadWrite,Category"QuestNode")TArray<int> Prede…

mysql8安装时提示-缺少Microsoft Visual C++ 2019 x64 redistributable

MySQL8.0安装包mysql-8.0.1-winx64进行安装&#xff0c;提示&#xff1a;This application requires Visual Studio 2019 x64Redistributable, Please install the Redistributable then runthis installer again。出现这个错误是因为我们电脑缺少Microsoft Visual C 这个程序&…

K8s 分布式存储后端(K8s Distributed Storage Backend)

K8s 分布式存储后端 在 K8s 中实现分布式存储后端对于管理跨集群的持久数据、确保高可用性、可扩展性和可靠性至关重要。在 K8s 环境中&#xff0c;应用程序通常被容器化并跨多个节点部署。虽然 K8s 可以有效处理无状态应用程序&#xff0c;但有状态应用程序需要持久存储来维护…

生产环境超实用shell脚本一

生产环境超实用shell脚本一 Shell脚本作为一种强大的自动化工具&#xff0c;能够帮助运维人员轻松应对各种复杂的任务。 本文将为您介绍服务器健康检查、日志清理、备份以及监控等多个方面&#xff0c;并详细阐述每个脚本的功能和应用场景&#xff0c;助力您提升运维效率&…

IM 即时通讯系统-46-OpenIM 提供了专为开发者设计的开源即时通讯解决方案

IM 开源系列 IM 即时通讯系统-41-开源 野火IM 专注于即时通讯实时音视频技术&#xff0c;提供优质可控的IMRTC能力 IM 即时通讯系统-42-基于netty实现的IM服务端,提供客户端jar包,可集成自己的登录系统 IM 即时通讯系统-43-简单的仿QQ聊天安卓APP IM 即时通讯系统-44-仿QQ即…

spy-debugger + Charles 调试移动端/内嵌小程序H5

简介说明&#xff1a; PC端可以用F12进行console等进行调试&#xff0c;但移动端App中使用webview就无法进行实时调试&#xff0c;针对这种情况 1. 安装 全局安装 spy-debugger sudo npm install spy-debugger -g // window不用加sudo2. spy-debugger 证书 其实spy-debugg…