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

【数据结构】堆

文章目录

  • 前言
  • 堆的概念及结构
  • 堆初始化
  • 堆的判空
  • 堆的销毁
  • 插入数据
  • 删除数据
  • 堆的数据个数
  • 获取堆顶数据
  • 用数组创建堆
  • 对数组堆排序
  • 有关topk问题
  • 整体代码展示
  • 写在最后

前言

🚩前面了解了树(-> 传送门 <-)的概念后,本章带大家来实现一个跟树有关的数据结构——本章有对堆排序和topk问题的讲解)。
🚩普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把 (一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
如下图:
在这里插入图片描述
🚩所以,我们在实现 的时候可以将它看作为一棵二叉树来思考,这是它的 逻辑结构 。但其 物理结构 是一个实实在在的 顺序数组 (在内存当中存储的形式)。
🚩那么接下来就开始实现一个属于自己的堆吧!


堆的概念及结构

  • 本章是以大堆为例,只要会了大堆,小堆也是不在话下。

  • 堆是把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并且它的任意元素 Ki(i = 0, 1, 2, 3, …, n - 1) 都满足 Ki <= K(i * 2 + 1) 且 Ki <= (i * 2 + 2) ( 或者 Ki >= K(i * 2 + 1) 且 Ki >= K(i * 2 + 2) ),这样的堆我们称之为 小堆( 或者 大堆 )。我们还可以将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
    堆的性质:

    1. 堆中某个节点的值总是不大于或不小于其父节点的值;
    2. 堆总是一棵完全二叉树。
  • 有了对堆的概念和性质的理解,相信大家对堆的元素是如何在数组中存储的(物理结构)明白了许多,就是依照上面大堆小堆的概念来维持存储关系的。

图示:

在这里插入图片描述

在这里插入图片描述

  • 观察上图可知,第一张图是一个大堆的逻辑结构和物理结构,其每个结点的左右子结点都比这个结点要小或者等于;第二张图是一个小堆的逻辑结构和物理结构,其每个结点的左右子结点都比这个结点要大或者等于。所以,一个堆,他只能是小堆或者大堆,不具有这两个堆的性质的,都不属于堆,下面我们来判断一下那些是堆那些不是堆:
A. 100,60,70,50,32,65  // 这是一个大堆
B. 60,70,65,50,32,100  // 这不是堆
C. 65,100,70,32,50,60  // 这不是堆
D. 70,65,100,32,50,60  // 这不是堆
E. 32,50,80,70,65,100  // 这是一个小堆 
F. 50,100,70,65,60,32  // 这不是堆

其实如果想要更好的判断,只需要将其逻辑结构画出来,就一目了然了。

在这里插入图片描述

在这里插入图片描述

了解了堆的概念和结构后,接下来就是实现啦!!!


堆初始化

  • 首先我们需要三个文件:heap.h,heap.c,test.cheap.h用来存放所需头文件,堆的结构体和相关函数声明。heap.c用来实现相关函数接口,test.c用来测试。

下面是所需头文件:

#include <stdio.h>
// assert断言所需
#include <assert.h>
// 动态内存开辟函数所需
#include <stdlib.h>
// bool类型所需
#include <stdbool.h>
// 拷贝函数所需
#include <string.h>
  • 我们知道,堆的底层存储结构是一个顺序的数组,因此这里需要跟前面我们实现的顺序表一样用动态内存空间。使用动态空间,就需要一个变量来统计容量,当然,为了后面一些函数功能的需求,还需要一个变量来统计堆的数据个数。因此,一个堆的结构定义如下:
// 堆的元素的数据类型
typedef int HPDataType;
typedef struct Heap
{// 指向存储数据的连续的空间(数组)HPDataType* a;// 统计当前堆中数据的个数int size;// 统计当前空间的容量int capacity;
}Heap;
  • 一个堆所需的函数接口声明如下:
// 以大堆为例
// 堆初始化
void HeapInit(Heap* php);
// 数组创建堆
void HeapCreateArray(Heap* php, HPDataType* a, int n);// 插入数据
void HeapPush(Heap* php, HPDataType x);
// 删除数据
void HeapPop(Heap* php);// 获取堆顶数据
HPDataType HeapTop(Heap* php);// 获取堆数据个数
int HeapSize(Heap* php);// 堆判空
bool HeapEmpty(Heap* php);// 堆的销毁
void HeapDestroy(Heap* php);// 交换
void swap(HPDataType* a, HPDataType* b);
// 向上调整
void adjustup(HPDataType* a, int child);
// 向下调整
void adjustdown(HPDataType* a, int size, int parent);// 堆排序
void HeapSort(HPDataType* a, int n);
// topk问题
void PrintTopK(HPDataType* a, int n, int k);
  • 有了以上的铺垫,堆的初始化就是将堆的结构里的指针置为空,统计数据个数和统计空间容量的变量置为0即可。(当然也可以初始化的时候就开个初始空间)

相关函数接口功能的实现:

// 堆初始化
void HeapInit(Heap* php)
{// 防止传递一个NULL上来,传NULL的话php就是NULL,后面的操作就不能进行了assert(php);php->a = NULL;php->capacity = php->size = 0;
}

堆的判空

  • 有了统计堆的数据个数的变量size,判空就简单多了。只需要判断一下size是否为0即可,如果size0,说明为空,返回true,如果size不为0,说明堆中有数据,返回false

相关函数接口功能的实现:

// 堆判空
bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

堆的销毁

  • 堆的销毁也很简单,将堆空间释放即可。当然size跟统计容量的capacity也置为0

相关函数接口功能的实现:

// 堆的销毁
void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->capacity = php->size = 0;
}

插入数据

  • 首先,插入数据要看看容量够不够,如果不够,就需要扩容。

  • 插入数据就是在数组的最后一个位置插入,由于size是数据的个数也就是数组的长度,所以size就是指向堆的最后一个数据的下一个位置,因此我们只需要在size位置插入即可。当然,插入过后,多了一个数据,因此size要自加1

  • 当我们插入数据之后,现在堆的结构很可能就不是一个堆(以大堆为例)了,因此,这里需要对插入进来的那个数据执行一个向上调整的算法,图示:

在这里插入图片描述

  • 对于向上调整算法,我们需要找到这个结点(假设下标为child)的父亲结点(假设下标为parent),具体操作为:parent = (child - 1) / 2 ,找到父结点后,就与他进行比较,由于我们以大堆为例,所以如果child位置上的数据大于parent位置上的数据,就需要将这两个数据交换一下,然后循环往复,继续寻找父结点进行比较,直到到达应该处在的位置为止(直到是个堆为止)。

相关函数接口功能的实现:

// 插入数据
void HeapPush(Heap* php, HPDataType x)
{assert(php);// 如果容量满了就扩容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}// 在size位置插入数据,然后size自加1php->a[php->size++] = x;// 对插入的那个数据执行向上调整算法,重整堆// php->size - 1是插入的那个数据的下标adjustup(php->a, php->size - 1);
}

向上调整算法:

// 向上调整
void adjustup(HPDataType* a, int child)
{// 找父结点int parent = (child - 1) / 2;while (child > 0){// 如果child位置的数据大小大于parent位置的数据大小就交换if (a[child] > a[parent]){// 交换swap(&a[child], &a[parent]);// 交换过后,child依旧指向开始指向的那个数据child = parent;// 继续找父结点parent = (child - 1) / 2;}else break; // 如果小于等于了,说明此时的堆是个真正的堆了,直接跳出循环}
}

这里有个swap函数,其实现为:

// 交换
// 注意是传递地址交换,因为形参的改变不改变实参
void swap(HPDataType* a, HPDataType* b)
{HPDataType tmp = *a;*a = *b;*b = tmp;
}

删除数据

  • 删除数据其实是删除堆顶的数据,由于堆是由一个数组来存放数据的,那么我们该如何删除堆顶(数组的第一个元素数据)的数据呢?

  • 这里我们将堆顶数据和数组的最后一个数据交换一下,然后size减一,就实现了删除。

  • 当然,删除过后,此时的堆已经不再是堆了,所以我们需要对新的堆顶数据执行一下向下调整算法,图示:

在这里插入图片描述

  • 对于向下调整算法,我们先要找到该结点(假设下标为parent)的孩子结点,而孩子结点又分为左孩子结点(下标为parent * 2 + 1)和右孩子结点(下标为parent * 2 + 2),所以我们需要找出两个孩子结点当中较大的那个,如果该节点的数据比较大的那个孩子结点的数据要小,那就进行交换,然后循环往复继续向下寻找孩子结点重整堆。

  • 整个操作,我们可以先比较两个孩子的大小找出大的那个,然后在与大的这个孩子结点进行比较,如果父结点比他小(以大堆为例),说明这个孩子结点该上位了。然后继续向下执行这个操作。

相关函数接口功能的实现:

// 删除数据,删除堆顶的数据
void HeapPop(Heap* php)
{// 判断php的合法性并且要保证堆不为空,空就不能删了assert(php && !HeapEmpty(php));// 将堆的第一个数据与堆的最后一个数据交换swap(&php->a[0], &php->a[php->size - 1]);// size减一表示删除最后一个数据php->size--;// 对新的堆顶执行向下调整算法adjustdown(php->a, php->size, 0);
}

向下调整算法:

// 向下调整
void adjustdown(HPDataType* a, int size, int parent)
{// 先假设大的那个孩子结点为左孩子结点int child = parent * 2 + 1;while (child < size)  // 如果child小于此时数组的长度就继续{// 第一个判断是防止没有右孩子结点的情况// 第二个判断是如果右孩子存在并且右孩子结点的数据大于左孩子结点的数据,就child加一指向右孩子结点if (child + 1 < size && a[child + 1] > a[child]) child++;// 如果父节点数据小于child结点数据,就交换重整堆if (a[child] > a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else break;  // 如果父节点数据大于child结点数据,说明堆已经调整完毕,直接跳出循环不在调整}
}

堆的数据个数

  • 由于我们定义了一个变量来统计堆的数据个数,所以在这里只需要返回那个变量的值即可。

相关函数接口功能的实现:

// 获取堆数据个数
int HeapSize(Heap* php)
{assert(php);return php->size;
}

获取堆顶数据

  • 获取堆顶数据就是数组的第一个元素。

  • 如果此时堆为空,就不能获取了。

相关函数接口功能的实现:

// 获取堆顶数据
HPDataType HeapTop(Heap* php)
{assert(php && !HeapEmpty(php));return php->a[0];
}

用数组创建堆

  • 用数组创建堆就是将一个数组里面的所有元素拷贝到堆的数组里,然后进行建堆。

  • 我们首先开辟一个堆空间(就是一段连续的数组),长度与给的数组的长度相同,然后将给的数组里的元素拷贝过来,最后将堆里面的数据进行建堆。

  • 如何建堆?我们从最后一个结点的父节点开始,依次执行向下调整算法,直到根节点执行完全后,便建成了堆。当然我们也可以从第二个结点开始,依次执行向上调整算法,直到最后一个结点执行完后便建成了堆,不过这样的时间复杂度为O(n * logn),而前面的向下调整算法的方式的时间复杂度为O(n),所以这里我们采用向下调整算法的方式来建堆。至于这两个调整算法的时间复杂度是如何计算出来的,这里就不做讨论,它的本质其实是有关数列求和的计算。

向下调整算法方式建堆图示:
在这里插入图片描述

在这里插入图片描述

  • 有了上面的思路,接下来就是代码实现了。

相关函数接口功能的实现:

// 数组创建堆
void HeapCreateArray(Heap* php, HPDataType* a, int n)
{// php不能为NULL,a不能为NULLassert(php && a);// 开辟可存放n个数据的堆空间php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);if (php->a == NULL){perror("malloc fail");exit(-1);}// 拷贝数据memcpy(php->a, a, sizeof(HPDataType) * n);php->size = php->capacity = n;// 从最后一个结点的父节点开始依次执行向下调整算法,直到根结点执行完后结束// 这里i为什么初始化 (n - 2) / 2 呢? 其实就是最后一个结点的父节点的下标// 前面说了,寻找父节点的下标是 (child - 1) / 2// 这里是寻找最后一个结点的父结点,而最后一个结点的下标是 (n - 1)(n是数组长度)// 所以它的父节点就是 (n - 1 - 1) / 2 , 也就是(n - 2) / 2for (int i = (n - 2) / 2; i >= 0; i--) adjustdown(php->a, n, i);
}

对数组堆排序

  • 有了建堆的认识,堆排序也不难,只不过需要注意几个细节。

  • 对数组排序,首先就是要对这个数组建堆,如果是要将数组升序,就建为大堆,如果是要将数组降序,就建为小堆,为什么呢?这里以升序为例进行讲解,弄懂了升序,降序也只是大于小于号的问题了。

  • 当数组建成大堆形式后(同样的,使用向下调整算法建堆),堆顶元素是最大的,此时我们可以将堆顶元素与最后一个元素进行交换,这样最大的元素就到了数组的末尾了。然后我们对这个处在数组最后一个位置的最大元素视而不见,将交换过去的堆顶元素执行向下调整算法,这时,第二大的元素就到了堆顶,然后此时的堆顶元素继续与最后一个元素进行交换 (注意第一个交换过去的最大的元素已经不在范围内了,也就是说每将一个当前最大的数交换过去后,可视作size减一一次) ,然后再将交换过去的堆顶元素执行向下调整算法…这样循环往复,最终该数组就变成了升序。

在这里插入图片描述

相关函数接口功能的实现:

// 堆排序
void HeapSort(HPDataType* a, int n)
{assert(a);// 向下调整, 这里是建大堆for (int i = (n - 2) / 2; i >= 0; i--) adjustdown(a, n, i);// 排序(建的大堆就是升序)int k = n - 1;while (k > 0){swap(&a[0], &a[k]);adjustdown(a, k, 0);k--;}
}

有关topk问题

  • 如何在百万个成绩数据里面找出前十个最好的?这就是典型的topk问题。可以看到,它需要处理的数据非常多(当然也有的很少,不过面试一般就是问多的情况,让你找出前k个最那啥的),因此这里需要用到堆来解决。

  • 为什么堆可以解决呢?堆的堆顶要么是整个堆里最大的元素,要么是整个堆里最小的元素。根据这一性质,假如求很多数据中前k个最小的数据,我们可以先开辟一个堆空间,该堆空间可以存放k个数据,然后我们将很多数据中的前k个数据拷贝进堆里,并将其建成大堆,此时堆的堆顶元素就是堆里所有元素最大的那一个,接着,我们从很多数据的第k个数开始,遍历这些数据,如果该数据小于此时堆顶的数据,就将堆顶数据更新为这个小的数据,然后对其执行向下调整算法,执行完过后,堆顶还是堆中最大的那个元素,就这样判断并操作,直到遍历结束,堆中就是那前k个最小的数啦。最后将这些数打印即可。(找前k个最小的数就是建大堆,反之,建小堆,这里就不作讨论了)

  • 原理(以求前k个最小的数为例):每次比堆顶小的元素入堆并调整后,之前堆中最大的元素被抛弃,新的最大的元素上位了,这样循环往复下去,每次操作除大的进小的,当然最后堆中就是前k个最小的数啦。

相关函数接口功能的实现:

// topk问题
void PrintTopK(HPDataType* a, int n, int k)
{assert(a);// 开辟能够存放k个数据的堆空间HPDataType* topk = (HPDataType*)malloc(sizeof(HPDataType) * k);if (topk == NULL){perror("malloc fail");exit(-1);}// 拷贝前k个数据进堆memcpy(topk, a, sizeof(HPDataType) * k);// 找前k个最小的,建大堆for (int i = (k - 2) / 2; i >= 0; i--) adjustdown(topk, k, i);// 对topk堆进行 除大的进小的 操作for (int i = k; i < n; i++){if (a[i] < topk[0]){topk[0] = a[i];// 每次进来一个较小的元素都要执行一遍向下调整算法来调整堆adjustdown(topk, k, 0);}}// 打印  topkfor (int i = 0; i < k; i++) printf("%d ", topk[i]);// 最后使用完堆后记得释放free(topk);
}

整体代码展示

heap.h

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;// 以大堆为例
// 堆初始化
void HeapInit(Heap* php);
// 数组创建堆
void HeapCreateArray(Heap* php, HPDataType* a, int n);// 插入数据
void HeapPush(Heap* php, HPDataType x);
// 删除数据
void HeapPop(Heap* php);// 获取堆顶数据
HPDataType HeapTop(Heap* php);// 获取堆数据个数
int HeapSize(Heap* php);// 堆判空
bool HeapEmpty(Heap* php);// 堆的销毁
void HeapDestroy(Heap* php);// 交换
void swap(HPDataType* a, HPDataType* b);
// 向上调整
void adjustup(HPDataType* a, int child);
// 向下调整
void adjustdown(HPDataType* a, int size, int parent);// 堆排序
void HeapSort(HPDataType* a, int n);
// topk问题
void PrintTopK(HPDataType* a, int n, int k);

heap.c

#include "heap.h"// 堆初始化
void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->capacity = php->size = 0;
}
// 数组创建堆
void HeapCreateArray(Heap* php, HPDataType* a, int n)
{assert(php && a);php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);if (php->a == NULL){perror("malloc fail");exit(-1);}memcpy(php->a, a, sizeof(HPDataType) * n);php->size = php->capacity = n;for (int i = (n - 2) / 2; i >= 0; i--) adjustdown(php->a, n, i);
}// 插入数据
void HeapPush(Heap* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}php->a[php->size++] = x;adjustup(php->a, php->size - 1);
}
// 删除数据,删除堆顶的数据
void HeapPop(Heap* php)
{assert(php && !HeapEmpty(php));swap(&php->a[0], &php->a[php->size - 1]);php->size--;adjustdown(php->a, php->size, 0);
}// 获取堆顶数据
HPDataType HeapTop(Heap* php)
{assert(php && !HeapEmpty(php));return php->a[0];
}// 获取堆数据个数
int HeapSize(Heap* php)
{assert(php);return php->size;
}// 堆判空
bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}// 堆的销毁
void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->capacity = php->size = 0;
}// 交换
void swap(HPDataType* a, HPDataType* b)
{HPDataType tmp = *a;*a = *b;*b = tmp;
}
// 向上调整
void adjustup(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else break;}
}
// 向下调整
void adjustdown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child + 1] > a[child]) child++;if (a[child] > a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else break;}
}// 堆排序
void HeapSort(HPDataType* a, int n)
{assert(a);// 向下调整, 这里是建大堆for (int i = (n - 2) / 2; i >= 0; i--) adjustdown(a, n, i);// 排序(建的大堆就是升序)int k = n - 1;while (k > 0){swap(&a[0], &a[k]);adjustdown(a, k, 0);k--;}
}// topk问题
void PrintTopK(HPDataType* a, int n, int k)
{assert(a);HPDataType* topk = (HPDataType*)malloc(sizeof(HPDataType) * k);if (topk == NULL){perror("malloc fail");exit(-1);}memcpy(topk, a, sizeof(HPDataType) * k);// 找前k个最小的,建大堆for (int i = (k - 2) / 2; i >= 0; i--) adjustdown(topk, k, i);for (int i = k; i < n; i++){if (a[i] < topk[0]){topk[0] = a[i];adjustdown(topk, k, 0);}}for (int i = 0; i < k; i++) printf("%d ", topk[i]);free(topk);
}

test.c

#include "heap.h"void test1()
{Heap hp;HeapInit(&hp);HeapPush(&hp, 22);HeapPush(&hp, 122);HeapPush(&hp, 16);HeapPush(&hp, 45);HeapPush(&hp, 56);HeapPush(&hp, 18);HeapPush(&hp, 134);HeapPush(&hp, 99);HeapDestroy(&hp);
}void test2()
{Heap hp;HeapInit(&hp);int arr[] = { 34,113,78,44,98,99,35,26,18,68 };int n = sizeof(arr) / sizeof(arr[0]);HeapCreateArray(&hp, arr, n);while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");HeapDestroy(&hp);
}void test3()
{int arr[] = { 34,113,78,44,98,99,35,26,18,68 };int n = sizeof(arr) / sizeof(arr[0]);HeapSort(arr, n);for (int i = 0; i < n; i++) printf("%d ", arr[i]);printf("\n");
}void testTopK()
{int arr[] = { 34,113,78,44,98,99,35,26,18,68 };int n = sizeof(arr) / sizeof(arr[0]);PrintTopK(arr, n, 5);
}int main()
{//test1();test2();test3();testTopK();return 0;
}

写在最后

💝
❤️‍🔥后续将会持续输出有关数据结构与算法的文章,你们的支持就是我写作的最大动力!

感谢阅读本小白的博客,错误的地方请严厉指出噢~

相关文章:

【数据结构】堆

文章目录前言堆的概念及结构堆初始化堆的判空堆的销毁插入数据删除数据堆的数据个数获取堆顶数据用数组创建堆对数组堆排序有关topk问题整体代码展示写在最后前言 &#x1f6a9;前面了解了树&#xff08;-> 传送门 <-&#xff09;的概念后&#xff0c;本章带大家来实现一…...

电脑硬盘文件数据误删除/格式化为什么可以恢复? 怎么恢复?谈谈文件删除与恢复背后的原理

Hello 大家好&#xff0c; 我是元存储~ 主页&#xff1a;元存储的博客_CSDN博客 1. 硬盘数据丢失场景 我们在每天办公还是记录数据的时候&#xff0c;文件存储大多数都是通过硬盘进行存储的&#xff0c;因此&#xff0c;使用多了&#xff0c;各种问题就会出现&#xff0c;比如…...

Gateway服务网关

Spring Cloud Gateway为微服务架构提供一种简单有效的统一的 API 路由管理方式。Gateway网关是所有微服务的统一入口。网关的核心功能特性&#xff1a;请求路由和负载均衡&#xff1a;一切请求都必须先经过gateway&#xff0c;但网关不处理业务&#xff0c;而是根据某种规则&am…...

K8S + GitLab + Jenkins自动化发布项目实践(一)

K8S GitLab Jenkins自动化发布项目实践&#xff08;一&#xff09;发布流程设计安装Docker服务部署Harbor作为镜像仓库部署GitLab作为代码仓库常用Git命令发布流程设计 #mermaid-svg-pe9VmFytb9GmqMvG {font-family:"trebuchet ms",verdana,arial,sans-serif;font-…...

【数据结构篇C++实现】- 堆

文章目录&#x1f680;一、堆的原理精讲⛳&#xff08;一&#xff09;堆的概念⛳&#xff08;二&#xff09;看图识最大堆⛳&#xff08;三&#xff09;详解堆是用数组表示的树&#x1f680;二、堆的向下调整算法&#x1f680;三、堆的向上调整算法&#x1f680;四、将任意一棵…...

C++笔试题

作用域运算符(::)的作用&#xff1a;1.存在具有相同名称的局部变量时&#xff0c;访问全局变量。2.在类之外定义类相关函数。3.访问类的静态变量。4.在多重继承的情况下&#xff0c;如果两个基类中存在相同的变量名&#xff0c;可以使用作用域运算符来进行区分。5.限定成员函数…...

【Python】基本语法

数据类型 通过 print(type(x)) 可以输出 x 的数据类型&#xff0c;type() 函数可以获取数据类型 整数 a 10 print(type(a)) 浮点数 a 0.5 print(type(a)) 字符串 a hello print(type(a)) 获取字符串长度 a hello print(len(a))字符串拼接 a hello b world prin…...

用栈实现队列(图示超详解哦)

全文目录引言用栈实现队列题目介绍思路简述实现栈的部分队列的部分创建队列判断队列是否为空在队列尾入在队列头出访问队头元素释放队列总结引言 在上一篇文章中&#xff0c;我们了解了用两个队列实现栈。在这篇问章中将继续介绍用两个栈实现队列的OJ练习&#xff1a; 用栈实现…...

Spring - Spring 注解相关面试题总结

文章目录01. Spring 配置方式有几种&#xff1f;02. Spring 如何实现基于xml的配置方式&#xff1f;03. Spring 如何实现基于注解的配置&#xff1f;04. Spring 如何基于注解配置bean的作用范围&#xff1f;05. Spring Component, Controller, Repository, Service 注解有何区别…...

【数据结构】实现二叉树的基本操作

目录 1. 二叉树的基本操作 2. 具体实现 2.1 创建BinaryTree类以及简单创建一棵树 2.2 前序遍历 2.3 中序遍历 2.4 后序遍历 2.5 层序遍历 2.6 获取树中节点的个数 2.7 获取叶子节点的个数 2.8 获取第K层节点的个数 2.9 获取二叉树的高度 2.10 检测值为val的元素是否…...

代码随想录算法训练营第五十二天| ● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组

300.最长递增子序列 看完题后的思路 dp[i] [0,i]子数组中,以nums[i]结尾的子序列的长度 dp[i]dp[j]1 j从i-1向0遍历,在所有nums[j]<nums[i]中dp[j]最大 初始化 dp[0]1 代码 class Solution {public int lengthOfLIS(int[] nums) {if (nums.length0){return 0;}int[] dpne…...

手机验证发送及其验证(基于springboot+redis)保姆级

在Java开发中&#xff0c;发送手机验证码时需要考虑以下几个问题&#xff1a; 验证码的有效期&#xff1a;验证码应该有一定的有效期&#xff0c;一般设置为几分钟或者十几分钟。过期的验证码应该被认为是无效的&#xff0c;不能用于验证用户身份。手机号码格式的校验&#xf…...

【JavaScript 逆向】数美滑块逆向分析

声明本文章中所有内容仅供学习交流&#xff0c;相关链接做了脱敏处理&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01;案例目标验证码&#xff1a;aHR0cHM6Ly93d3cuaXNodW1laS5jb20vbmV3L3Byb2R1Y3QvdHcvY29kZQ以上均做了脱敏处理&#xff0c;Base64 编码及解码方…...

多任务之线程

文章目录一、多任务是什么&#xff1f;二、多任务-线程四、通过继承Tread类完成创建线程五、资源竞争六、同步与互斥锁七、对峙与避免死锁一、多任务是什么&#xff1f; 多个函数同时执行一件事情就是多任务&#xff0c;没有多任务的时候任务执行都是按照顺序的&#xff0c;而…...

(数字图像处理MATLAB+Python)第二章数字图像处理基础-第二节:色度学基础与颜色模型

文章目录一&#xff1a;颜色匹配二&#xff1a;CIE 1931-RGB系统三&#xff1a;CIE 1931标准色度系统四&#xff1a;CIE 1976Lab均匀颜色空间五&#xff1a;孟塞尔表色系统&#xff08;1&#xff09;孟塞尔明度(Value&#xff0c;记为V)&#xff08;2&#xff09;孟塞尔彩度(Ch…...

【华为OD机试 2023最新 】 网上商城优惠活动(C++)

文章目录 题目描述输入描述输出描述备注用例题目解析C++题目描述 某网上商场举办优惠活动,发布了满减、打折、无门槛3种优惠券,分别为: 每满100元优惠10元,无使用数限制,如100199元可以使用1张减10元,200299可使用2张减20元,以此类推;92折券,1次限使用1张,如100元,…...

记一次CentOS 8 部署packstack部署OpenStack失败案例,请直接看最后

首先你需要一台安装好CentOS8 的虚拟机&#xff0c;相关参数如图。两块网卡&#xff0c;网卡1 NAT IP 192.168.100.100 GW192.168.100.2 网卡2 可不做配置。能ping通百度。创建完成虚拟机记得打好快照。 开机编辑基本配置环境变量 [rootlocalhost ~]# nmcli connection show NA…...

【2023春招】美团技术岗笔试10min+AK

随手投递了前端&移动端,笔试2道算法+选择+行测题(为什么笔试会有行测题?) 目录 T1-火车栈结构 题意 输入描述 输出描述 样例 AC_Code T2-春游...

Echarts实现图表自适应屏幕分辨率

一&#xff1a;简介 之前做项目的时候要实现echarts图表随浏览器窗口大小变化而改变&#xff0c;echarts本身提供了一个resize()方法&#xff0c;然后我们需要用一个函数实现浏览器窗口监听&#xff0c;最初我选用的是window.onresize方法&#xff0c;当页面只有一个图表时可以…...

【2023年第十一届泰迪杯数据挖掘挑战赛】B题:产品订单的数据分析与需求预测 建模及python代码详解 问题一

相关链接 【2023年第十一届泰迪杯数据挖掘挑战赛】B题&#xff1a;产品订单的数据分析与需求预测 建模及python代码详解 问题一 【2023年第十一届泰迪杯数据挖掘挑战赛】B题&#xff1a;产品订单的数据分析与需求预测 建模及python代码详解 问题二 1 题目 一&#xff0e;问题…...

【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式国赛客观题以及详细题解

题1 概念题。 USRAT&#xff1a;异步串口通信&#xff0c;常用于数据传输&#xff1b;SW-DP&#xff1a;SWD 的全称应该是 The Serial Wire Debug Port (SW-DP),也就是串行调试端口&#xff0c;是 >ARM 目前支持的两种调试端口之一&#xff1b;JTAG-DP&#xff1a;另一个调试…...

java中Map遍历的4种方式

目录 1、map.entrySet()方式 2、map.keySet()方式 3、map.values()方式 4、forEach方式 本文以如下map案例&#xff1a; Map<String, String> map new HashMap<>(); map.put("student1", "张三"); map.put("student2", "…...

GCC 编译器的主要组件和编译过程

主要组件&#xff1a; 分析器&#xff1a;分析器将源语言程序代码转换为汇编语言。因为要从一种格式转换为另一种格式&#xff08;C到汇编&#xff09;&#xff0c;所以分析器需要知道目标机器的汇编语言。 汇编器&#xff1a;汇编器将汇编语言代码转换为CPU可以执行字节码。 …...

蓝桥杯冲刺 - week2

文章目录&#x1f4ac;前言&#x1f332;day1最大和 (DP质因数分解)901. 滑雪 - 记忆化搜索&#x1f332;day21227. 分巧克力 - 二分&#x1f332;day31221. 四平方和 - 空间换时间1230. K倍区间&#x1f332;day41076. 迷宫问题 - 路径2017-迷宫-填空&#x1f332;day5848. 有…...

第十四届蓝桥杯三月真题刷题训练——第 20 天

目录 第 1 题&#xff1a;纸张尺寸 问题描述 输入格式 输出格式 样例输入1 样例输出1 样例输入 2 样例输出 2 运行限制 代码&#xff1a; 解析&#xff1a; 第 2 题&#xff1a;最大数字 第 3 题&#xff1a;全排列的价值_递推公式 问题描述 输入格式 输出格式…...

【C++】科普:C++中的浮点数怎么在计算机中表示?

这里我们以8.25这个数为例说明计算机时如何存取float类型的数据的&#xff1a; float a 8.25;引言 1. 所占位数 首先&#xff0c;明确一个概念&#xff0c;float类型的数据在常规计算机中通常占4个字节&#xff0c;也就是32位。其内存分布如图&#xff1a; 位字段说明所占位…...

Linux 多线程:多线程和多进程的对比

目录一、多进程优缺点二、多线程优缺点三、使用多执行流的场景在多任务处理中&#xff0c;我们既可以使用多进程&#xff0c;也可以使用多线程。但多进程和多线程并不是随意选择的&#xff0c;因为它们应对的场景不同&#xff0c;优缺点也不同。 一、多进程优缺点 多进程就是在…...

IO流你了解多少

IO流你了解多少 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公…...

【C++】C++ 11 新特性之auto关键字

文章目录类型别名的思考auto简介auto关键字的特性类型别名的思考 随着程序越来越复杂&#xff0c;程序中用到的类型也越来越复杂&#xff0c;经常体现在&#xff1a; 类型难于拼写含义不明确导致容易出错 #include <string> #include <map> int main() {std::ma…...

nodejs的后端框架egg,thinkjs,nestjs,nuxtjs,nextjs对比

1. Egg.js&#xff1a;优点&#xff1a;Egg.js是一个基于Koa的Node.js企业级应用开发框架&#xff0c;它提供了完整的开发规范和一套稳定性和安全性较高的架构体系&#xff0c;能够帮助开发者快速构建高可用、高性能的应用程序。同时&#xff0c;Egg.js还提供了很多自定义插件和…...

福建自己建设网站/上海最新新闻事件今天国内

Atitit.软件与编程语言中的锁机制原理attilax总结 1. 用途 &#xff08;Db&#xff0c;业务数据加锁&#xff0c;并发操作加锁。1 2. 锁得类型 排它锁 “互斥锁 共享锁 乐观锁与悲观锁1 2.1. 自旋锁还是信号量1 2.2. -自动释放还是手动释放1 3. 实现方式&#xff0c;语言方式与…...

国外购物网站大全/bt樱桃 磁力岛

作为一个viewController&#xff08;VC)&#xff0c;想要消失的时候可以从parent VC里面调用dismissModalViewControllerAnimated来消去改VC&#xff0c;也可以在该VC里面手动调用self dismissModalViewControllerAnimated:YES来消去自己。 不过发现有时候调用dismissModalView…...

合肥电子商务开发网站建设/seo专员岗位要求

内容篇幅较长,请点击这里阅读全文...

app开发公司杭州/网络舆情优化公司

经常有人问我有关“大数据”的问题&#xff0c;而且多半情况下我们似乎是在各种不同的抽象和理解级别进行交谈。实时 和高级分析 之类的词语频频现身&#xff0c;并且我们总是立即开始谈论产品&#xff0c;这通常并不是一个好主意。 希望将类似本文的技术文章发送到您的收件箱吗…...

武汉建站公司/网站播放视频速度优化

为了很好地表示下面例子&#xff0c;先给出两张表ta和tb&#xff1a; 1、外连接&#xff1a;包括左向外联接、右向外联接或完整外部联接 1.1 左连接&#xff1a;left join 或 left outer join 例&#xff1a;select * from ta left join tb on ta.idtb.id 结果为&#xff1a; i…...

哪个网站可以接工程做/今日最新消息新闻

搭建hyperledger fabric 网络&#xff08;数据库 使用CouchDB &#xff09;参考上一篇随笔&#xff1a;快速搭建hyperledger fabric 1.3 网络 当CouchDB查询返回大型结果集时&#xff0c;可以使用一系列API&#xff0c;这些API可以通过调用链代码来对结果列表进行分页。 分页提…...