【数据结构】堆

文章目录

  • 前言
  • 堆的概念及结构
  • 堆初始化
  • 堆的判空
  • 堆的销毁
  • 插入数据
  • 删除数据
  • 堆的数据个数
  • 获取堆顶数据
  • 用数组创建堆
  • 对数组堆排序
  • 有关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自加1
	php->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不能为NULL
	assert(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) / 2
	for (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);
		}
	}
	
	// 打印  topk
	for (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;
}

写在最后

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

软测界的黑科技,难道不来瞧瞧?

写在前面&#xff1a; 在当今互联网时代&#xff0c;软件已经渗透到了人们生活的方方面面&#xff0c;各种类型的软件应运而生&#xff0c;为人们的工作和生活提供了更便捷的服务。然而&#xff0c;随着软件的不断增长和复杂性的不断提高&#xff0c;软件测试变得越来越重要。…

如何成为优秀的程序员

崔宝秋&#xff0c;现任小米首席架构师、小米云平台负责人。1995年赴美留学&#xff0c;纽约州立大学石溪分校计算机科学系博士毕业&#xff0c;曾任IBM高级工程师和高级研发经理、雅虎搜索技术核心团队主任工程师、LinkedIn主任工程师&#xff0c;2012年回国加入小米科技。 20…

安全防御之入侵检测篇

目录 1.什么是IDS&#xff1f; 2.IDS和防火墙有什么不同&#xff1f;3.IDS的工作原理&#xff1f; 4.IDS的主要检测方法有哪些&#xff1f;请详细说明 5.IDS的部署方式有哪些&#xff1f; 6.IDS的签名是什么意思&#xff1f;签名过滤器有什么用&#xff1f;例外签名的配置作…

性能测试(三)----loadrunner的使用

一)Controller的使用: 1)在VUG中针对写好的脚本创建场景: 2)手动打开Controller进行脚本的添加并创建场景: 点击完成之后直接打开Controller所在的组件 3)针对场景来进行设置: Basic schedule:点击这个选项进行设置 可手动修改每个用户组的Quantity来修改并发用户总量 3.1)初始…

css绘制一个Pinia小菠萝

效果如下&#xff1a; pinia小菠萝分为头部和身体&#xff0c;头部三片叶子&#xff0c;菠萝为身体 头部 先绘制头部的盒子&#xff0c;将三片叶子至于头部盒子中 先绘制中间的叶子&#xff0c;利用border-radius实现叶子的效果&#xff0c;可以借助工具来快速实现圆角的预想…

ChatGPT常用开源项目汇总

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

基于jmeter+perfmon的稳定性测试记录

最近承接了项目中一些性能测试的任务&#xff0c;因此决定记录一下&#xff0c;将测试的过程和一些心得收录下来。 说起来性能测试算是软件测试行业内&#xff0c;有些特殊的部分。这部分的测试活动&#xff0c;与传统的测试任务差别是比较大的&#xff0c;也比较依赖工具&…

c++之模板入门详解(五千字长文详解)

c之模板入门详解 文章目录c之模板入门详解泛型编程函数模板函数模板概念函数模板格式模板的原理函数模板的实例化模板实例化的个数对于同不同类型的传参&#xff01;如何处理这个问题呢&#xff1f;关于具体存在的函数和模板函数的优先级问题&#xff01;类模板类模板的用法&am…

若依框架 --- ruoyi 表格的设置

表格 字典值转换 (1) 方式1&#xff1a;使用字典枚举的方式 var isDownload [[${dict.getType(YES_OR_NO)}]];{field : isDownload,title : 是否允许下载,formatter: function(value, row, index) {return $.table.selectDictLabel(isDownload, value);} }, (2) 方式2&…

Java正则表达式及Pattern与Matcher使用详解

文章目录一、正则表达式详解1、符号定义&#xff08;1&#xff09;基本书写符号&#xff08;2&#xff09;限定符&#xff08;3&#xff09;匹配字符集&#xff08;4&#xff09;分组构造&#xff08;5&#xff09;字符转义2、常用正则表达式举例3、Java中RegularExpressionVal…

flutter 输入时插入分隔符

每四位插入一个分隔符import package:flutter/services.dart;class DividerInputFormatter extends TextInputFormatter {final int rear; //第一个分割位数,后面分割位,,数final String pattern; //分割符DividerInputFormatter({this.rear 4, this.pattern });overrideTex…

【Linux】虚拟地址空间

进程地址空间一、引入二、虚拟地址与物理内存的联系三、为什么要有虚拟地址空间一、引入 对于C/C程序&#xff0c;我们眼中的内存是这样的&#xff1a; 我们利用这种对于与内存的理解看一下下面这段代码&#xff1a; 运行结果&#xff1a; 观察父子进程中 val 变量的值&…

uniapp中使用百度地图(初学者保姆式教学,持续更新)

uniapp中使用百度地图(保姆式教学&#xff0c;从零开始) 最近在写一个移动端的地图项目&#xff0c;也是首次完整的去了解百度地图api&#xff0c;这篇博客会手把手的教你如何使用百度地图api和一些常见问题&#xff0c;后续我也会继续更新完善此博客 1、百度地图api&#xf…

实验九 TSP问题

《算法设计与分析》实验报告 所在院系 计算机与信息工程学院 学生学号 学生姓名 年级专业 2020级计算机科学与技术 授课教师 彭绪富 学 期 2022-2023学年第一学期 提交时间 2022年10月26日 目 录 实验九-1&#xff1a;TSP问题 一、实验目的与要求 二…

html+css制作

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>校园官网</title><style type"text/css">*{padding: 0;margin: 0;}#logo{width:30%;float: left;}.nav{width: 100%;height: 100px;background-color…

mybatis如何解析常用的标签

通过这三行就解析好了一个mybatis配置文件&#xff0c;我们看看如何工作的&#xff1f; String resource "mybatis-config.xml"; Reader reader Resources.getResourceAsReader(resource); SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().b…

【进阶C语言】qsort库函数(详解)

qsort库函数1. qsort到底是什么&#xff1f;2. qsort库函数的功能3. qosrt函数详解4. 冒泡排序的实现5. qsort库函数如何实现冒泡排序6. qsort库函数排序结构体数据7. 使用冒泡排序的思想来实现类似于qsort1. qsort到底是什么&#xff1f; qsort是C语言库函数里面的一种&#x…

【Flutter·学习实践·配置】认识配置文件pubspec.yaml

目录 简介 pubspec.yaml 添加Pub仓库 其他依赖方式 依赖本地包 依赖Git 简介 简单说就是包管理工具&#xff0c;类似于Android 提供了 Gradle 来管理依赖&#xff0c;iOS 用 Cocoapods 或 Carthage 来管理依赖&#xff0c;Node 中通过 npm 等。 让我们能很好的管理第三…

固定优先级仲裁器设计

前言仲裁器Arbiter是数字设计中非常常见的模块&#xff0c;应用也非常广泛。定义就是当有两个或两个以上的模块需要占用同一个资源的时候&#xff0c;我们需要由仲裁器arbiter来决定哪一个模块来占有这个资源。一般来说&#xff0c;提出占有资源的模块要产生一个请求(request)&…
最新文章