【数据结构】堆的详解

news2025/1/12 16:17:05

本章的知识需要有树等相关的概念,如果你还不了解请先看这篇文章:初识二叉树

堆的详解

  • 一、二叉树的顺序结构及实现
    • 1、二叉树的顺序结构
    • 2、堆的概念及结构
  • 二、堆的简单实现 (以大堆为例)
    • 1、堆的定义
    • 2、堆的初始化
    • 3、堆的销毁
    • 4、堆的打印
    • 5、堆的插入
    • 6、堆顶元素的获取
    • 7、堆的删除
    • 8、堆元素个数的获取
    • 8、堆的判空
    • 10、堆简单的应用
  • 三、堆的创建
    • 1、向上调整建堆
    • 2、向下调整建堆
  • 四、堆的应用
    • 1、堆排序
    • 2、TOP-K问题


一、二叉树的顺序结构及实现

1、二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

在这里插入图片描述

2、堆的概念及结构

严格定义:如果有一个关键码的集合 K = K= K={ k 0 , k 1 , k 2 , . . , k n − 1 k_0,k_1, k_2,..,k_{n-1} k0k1k2..kn1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K i < = K 2 i + 1 K_i<= K_{2i+1} Ki<=K2i+1 K i ; < = K 2 ∗ i + 2 ( K i > = K 2 i + 1 且 K i > = K 2 ∗ i + 2 ) i = 0 , 1 , ⋅ ⒉ . . . , K_i;<= K_{2*i+2}(K_i>= K_{2i+1}且K_i>= K_{2*i+2}) i=0,1,·⒉..., Ki;<=K2i+2(Ki>=K2i+1Ki>=K2i+2)i=01⒉...则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

非严格定义:堆有两种,分为大堆与小堆,它们都是完全二叉树
大堆:树中所有父亲都大于等于孩子
小堆:树中所有父亲都小于等于孩子

在这里插入图片描述
在这里插入图片描述

在前一篇文章中我们也讲过,左孩子都是奇数,右孩子都是偶数,并且孩子和父亲下标也是有一定关系的(我们也可以通过找规律得到下面的关系):

leftchild = parent*2+1
rightchild = parent*2+2
parent = (child-1)/2	//偶数会被取整数,因此可以直接按照左孩子公式反推

二、堆的简单实现 (以大堆为例)

1、堆的定义

由于堆比较适合用数组存储,我们可以按照顺序表的结构来进行定义,但是切记数组存储是物理结构,我们要把这个物理结构给抽象为完全二叉树。

//堆的结构定义
typedef int HPDateType;
typedef struct Heap
{
	HPDateType* a;	//指向要存储的数据
	int size;		//记录当前结构存储了多少数据
	int capacity;	//记录当前结构的最大容量是多少
}HP;

2、堆的初始化

堆的初始化,我们可以给指针a开辟空间,也可以不开辟空间,这里选择不开辟空间。

//堆的初始化
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

3、堆的销毁

堆的销毁我们可以直接将空间进行释放,然后将指针置空size与capacity置成0就行了。

//堆的销毁
void HeapDestroy(HP*php)
{
	assert(php);
	free(php->a);	//指针置空
	php->a = 0;		//size置成0
	php->size = php->capacity = 0;	//capacity置成0
}

4、堆的打印

由于我们是使用数组实现堆,我们可以直接遍历一遍数组将它们打印出来就行了。

//堆的打印
void HeapPrint(HP* php)
{
	assert(php);
	int i = 0;
	for (i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
}

5、堆的插入

堆的插入就有一些复杂了,我们的插入数据以后要保证插入数据后的堆,还是一个大堆,不然就破坏了堆的结构。

我们先来考虑第一种情况:
在这里插入图片描述

我们再来考虑第二种情况:
在这里插入图片描述
我们总结一下这两种情况:
①首先在我们插入数据(子节点)之前,原始堆就要满足堆的结构,否则就是前面的代码出现了问题,与我们插入的数据无关!

②然后插入的子节点要与其父节点进行比较,如果小于其父节点则不交换,正常插入。如果其插入的子节点大于父节点就要进行向上调整交换,这个交换次数是不确定的,可能是一次也可能是两次,不过最多就是树的高度 h = log ⁡ 2 N h=\log_2N h=log2N次( N N N为节点个数),这也就是说我们的堆的插入是时间复杂度是 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)

接下来就是我们要根据这两种情况写出相应的代码了,这个时候这个孩子和父亲下标关系就很重要了,通过这个关系我们就能由子节点找到父节点,由父节点找到子节点了。

//堆的插入
void HeapPush(HP* php, HPDateType data)
{
	assert(php);
	//判断是否需要扩容
	if (php->capacity == php->size)
	{
		int new_capacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDateType* tmp = (HPDateType*)realloc(php->a, sizeof(HPDateType)*new_capacity);
		//realloc对于没有进行动态内存分配过的指针 调用会相当与一次malloc
		if (NULL == tmp)
		{
			perror("malloc fail:");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = new_capacity;
	}
	//数据插入
	php->a[php->size] = data;
	php->size++;
	//向上调整
	AdjustUp(php->a,php->size-1);
}

其中向上调整算法非常重要!!!

//交换函数
void Swap(HPDateType* x, HPDateType* y)
{
	HPDateType tmp = *x;
	*x = *y;
	*y = tmp;
}
//向上调整
void AdjustUp(HPDateType*a,int child)
{
	assert(a);
	int parent = (child - 1) / 2;	//找到刚插入的节点的父节点
	while (child>0)		//child=0说明子节点已经调整到了堆顶,已经不需要再进行调整了。
	{
		if (a[child] > a[parent])	//子节点比父节点大就交换
		{
			Swap(&a[child], &a[parent]);
			child = parent;		//更改孩子的下标,方便继续与上面新的父节点比较
			parent = (child - 1) / 2;	//更改父节点的下标,方便继续与下面新的子节点比较
		}
		else
		{
			break;//比较不满足条件,说明数据经过调整后已经符合大堆了
		}
	}
}

6、堆顶元素的获取

对于堆来说,堆顶的数据一定是堆里面最大或最小的数,所以堆顶数据的获取还是很有必要的,它的实现也并不复杂。

//堆顶元素的获取
HPDateType HeapTop(HP*php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

7、堆的删除

对于堆的删除,一般是删除堆顶元素,因为除了堆顶元素其他元素删除的意义并不大,但是堆顶的元素删除又会破环堆的结构。
在这里插入图片描述
而且如果采用直接删除堆顶元素,其他元素向前挪动之后再进行调整的话,会有很大的浪费,因为数组向前挪动的时间复杂度是 O ( n ) O(n) O(n)。但是数组的尾删效率很高是 O ( 1 ) O(1) O(1),所以数组尽量进行尾删。

于是便有了一种良好的先交换再向下调整的算法,它的思想是:先让堆顶元素与最后一个元素交换位置,这样原先的堆顶元素就变成了堆底元素,然后删除堆底元素,再对堆顶的元素进行向下调整,其核心算法就在于向下调整。
在这里插入图片描述
向下调整算法
向下调整算法要求我们:先取堆顶元素的子节点中较大的那个与堆顶元素进行比较,如果子节点大于父节点就进行交换,然后父节点的下标变到原先子节点的下标,再次执行上面的步骤,取父节点下面较大的子节点进行比较换位…
直到子节点不大于父节点或者是超出了数组边界。
在这里插入图片描述

//堆的删除
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	//交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//向下调整
	AdjustDown(php->a,php->size,0);
}

向下调整算法:

//向下调整
void AdjustDown(HPDateType* a, int n, int parent)
{
	//假设左孩子是最大的
	int child = parent * 2 + 1;
	while (child<n)
	{
		//判断假设是否正确,若不正确进行更改
		if (a[child + 1] > a[child])
		{
			++child;
		}
		if (child + 1 < n && a[child] > a[parent])
		{
			Swap(&a[child],&a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向下调整的算法与向上调整类似,这个交换次数是不确定的,可能是一次也可能是两次,不过最多就是树的高度 h = log ⁡ 2 N h=\log_2N h=log2N次( N N N为节点个数),这也就是说我们的堆的删除是时间复杂度也是 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)

比较一下向上调整算法与向下调整算法

  • 向上调整算法要求,数据插入之前原先的数据已经是一个堆了,才能重新建堆。
  • 向下调整算法的要求:左右子树必须是一个堆,才能调整重新建堆。

8、堆元素个数的获取

堆中的元素个数其实就是堆数据结构中的size。

// 堆元素个数的获取
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

8、堆的判空

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

10、堆简单的应用

学习到了这里,我们其实已经能利用堆处理一些问题了。

  • TOP-K问题
    堆顶的元素是最大(小)的元素,我们可以取堆顶K次删除K次,拿到一个数据集中最大(小)的前K个。
int main()
{
	HP hp;
	HeapInit(&hp);	//堆的初始化
	int arr[] = { 27,15,19,18,28,34,65,49,25,37 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); ++i)	//堆的插入,建堆
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint(&hp); //打印堆中的元素
	printf("\n");
	//选出最大的前五个
	int k = 5;
	for (int i = 0; i < k; i++)
	{
		printf("%d ", HeapTop(&hp));	//取堆顶的元素
		HeapPop(&hp);		//删除堆顶元素,重新定位新的最大的。
	}
	
	HeapDestroy(&hp);	//堆的销毁,防止内存泄漏
	return 0;
}

在这里插入图片描述

  • 排序问题
    排序问题与TOP-K问题类似,只不过这里的K是所有元素。
int main()
{
	HP hp;
	HeapInit(&hp);
	int arr[] = { 27,15,19,18,28,34,65,49,25,37 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); ++i)
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint(&hp);
	printf("\n");
	//排序
	while(!HeapEmpty(&hp))//只要不为空就一直进行排序。
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}

	HeapDestroy(&hp);
	return 0;
}

在这里插入图片描述

三、堆的创建

在上面的代码中我们看到我们经常会用一个数组去创建堆,因此我们还是很有必要再写一个函数——堆的创建!在上面的代码中我们创建一个堆其实是用的是堆的插入,即将数据插入到数组最后,再进行向上调整。这种方法能帮我们完成堆的创建,但是它的效率并不是很高,我们可以对其做一定优化。

1、向上调整建堆

  • 利用堆的插入进行堆的创建
void HeapCreat(HP* php, HPDateType* arr, int n)
{
	assert(php);
	HPDateType* tmp = (HPDateType*)malloc(sizeof(HPDateType) * n);
	if (NULL == tmp)
	{
		perror("malloc fail:");
		exit(-1);
	}
	php->a = tmp;
	php->capacity = n;
	for (int i = 0; i < n; ++i)
	{
		HeapPush(php, arr[i]);//这里使用了AdjustUp()函数
	}
}

但是这种建堆算法效率比较低,我们来求一下它的时间复杂度。
按照最坏的情况来算(完全二叉树的最坏情况是满二叉树,且每个节点都要调整):

在这里插入图片描述

向上调整建堆的第一层是不进行调整的,设 F F F是建堆交换调整的总次数, h − 1 h-1 h1是树的高度, N N N是树的结点个数,则有
F = 2 1 ∗ 1 + 2 2 ∗ 2 + 2 3 ∗ 3 + . . . . . . + 2 h − 2 ∗ h − 2 + 2 h − 1 ∗ h − 1 F=2^1*1+2^2*2+2^3*3+......+2^{h-2}*h-2+2^{h-1}*h-1 F=211+222+233+......+2h2h2+2h1h1
利用错位相减法得:
F = ( h − 2 ) ∗ 2 h + 1 (1) F=(h-2)*2^h+1 \tag{1} F=(h2)2h+1(1)
又因为二叉树满足:
N = 2 0 + 2 1 + 2 2 + 2 3 + . . . . . . + 2 h − 2 + 2 h − 1 = 2 h − 1 (2) N=2^0+2^1+2^2+2^3+......+2^{h-2}+2^{h-1}=2^h-1\tag{2} N=20+21+22+23+......+2h2+2h1=2h1(2)
将(2)带入(1)中得:
F = ( N + 1 ) ∗ ( log ⁡ 2 ( N + 1 ) − 2 ) + 1 (3) F=(N+1)*(\log_2{(N+1)}-2)+1\tag{3} F=(N+1)(log2(N+1)2)+1(3)
因此向上调整建堆的时间复杂度是 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)

2、向下调整建堆

向下调整算法是有的要求的:左右子树必须是一个堆,才能调整重新建堆,但给我们的数组本身就是乱序的,那我们应该怎样才能保证左右子树是一个堆呢?
答案是:我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

在这里第一个非叶子节点的子树是28,我们可以通过子节点与父节点的关系找到28的下标,然后向下调整,让①区域变成堆,然后下标减一到18的位置,然后向下调整,让②区域变成堆,然后下标减一到19的位置,然后向下调整,让③区域变成堆…直到下标为零时再调整一次这样就把堆给建立起来了。
在这里插入图片描述
代码实现:

//堆的创建
void HeapCreat(HP* php, HPDateType* arr, int n)
{
	assert(php);
	HPDateType* tmp = (HPDateType*)malloc(sizeof(HPDateType) * n);
	if (NULL == tmp)
	{
		perror("malloc fail:");
		exit(-1);
	}
	php->a = tmp;
	php->size=php->capacity = n;
	memcpy(php->a, arr, sizeof(HPDateType)*n);//内存拷贝函数
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);	//利用向下调整算法
	}

}

这种建堆算法效率是比较高的,我们来求一下它的时间复杂度。
按照最坏的情况来算(完全二叉树的最坏情况是满二叉树,且每个节点都要调整):

在这里插入图片描述
由于向下调整建堆是从倒数第二层开始调整的,所以我们假设树的高度为 h h h, F F F是建堆交换调整的总次数, N N N是树的结点个数。
F = 2 h − 2 ∗ 1 + 2 h − 3 ∗ 2 + 2 h − 4 ∗ 3 + . . . . . . + 2 1 ∗ h − 2 + 2 0 ∗ h − 1 (1) F=2^{h-2}*1+2^{h-3}*2+2^{h-4}*3+......+2^1*h-2+2^0*h-1\tag{1} F=2h21+2h32+2h43+......+21h2+20h1(1)
利用错位相减法得:
F = 2 h − ( h + 1 ) (2) F=2^h-(h+1) \tag{2} F=2h(h+1)(2)
又因为二叉树满足:
N = 2 0 + 2 1 + 2 2 + 2 3 + . . . . . . + 2 h − 2 + 2 h − 1 = 2 h − 1 (2) N=2^0+2^1+2^2+2^3+......+2^{h-2}+2^{h-1}=2^{h}-1\tag{2} N=20+21+22+23+......+2h2+2h1=2h1(2)
将(2)带入(1)中得:
F = N − ( log ⁡ 2 ( N + 1 ) + 1 ) (3) F=N-(\log_2{(N+1)}+1)\tag{3} F=N(log2(N+1)+1)(3)
因此向下调整建堆的时间复杂度是 O ( n ) O(n) O(n)

综上所述:向下调整建堆是更好的算法。
对比我们也可以发现,向上调整算法是那一层节点越多,向上调整越多,而向下调整算法是那一层节点越少,向下调整越多。因此向下调整算法更加优秀!

四、堆的应用

在前面我们已经简单说过了堆的应用:TOP-K与排序。

1、堆排序

但是上面的应用我们都是借助了堆这个数据结构(利用了堆的插入与删除)在实际应用时,给你一个数组让你进行排序,难道我们还要费很大的力气去建堆?这样显然太慢了,所以我们需要找到一种不用建立堆的数据结构就能进行排序的算法。

首先经过前面的学习我们都知道了,每个连续存储的数组可以看成一个完全二叉树,因此我们可以利用刚才堆的创建的思路对数组里面的数据进行建堆,然后进行排序。

但是假设我们要建立升序数组,我们应该建立大堆还是小堆呢?

  • 我们先来看看小堆怎么样。
    在这里插入图片描述

假设我们建立小堆的话,第一个元素就不用进行排序了,我们从第二个进行排序,但是从第二个元素开始重新排序的话我们的堆结构可能就被破坏了,不利于我们后续选数据,要不就是遍历一遍选一个数,这些效率都不太好。因此小堆并不适合建立升序数组。
在这里插入图片描述

  • 我们再来看看大堆
    我们可以将堆顶元素与堆底元素进行换位,这样我们最大的元素就到了正确的位置,然后对堆顶元素向下调整一次便可以重新建堆(因为左子树与右子树关系没有改变),再然后把最后一个元素不看成堆里面的元素,再将堆顶元素与堆底元素进行换位,这样我们次大的元素就到了正确的位置,然后对堆顶元素向下调整一次便可以重新建堆,再然后把最后两个元素不看成堆里面的元素如此继续循环往复,等数组里面的数被遍历一遍后,排序也就结束了,另外我们向下调整一次最多交换高度次 ( log ⁡ 2 n ) (\log_2n) log2n,因此堆排序的时间复杂度是 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)

在这里插入图片描述

//堆排序  升序建立大堆,降序建小堆!
void HeapSort(HPDateType* arr, int n)
{
	int parent = (n - 1 - 1) / 2;
	//建堆  --O(n)
	while(parent>=0)
	{
		AdjustDown(arr, n, parent);
		--parent;
	}
	//排序 --O(nlogn)
	int end = n - 1;
	while (end>0)
	{
		Swap(&arr[0], &arr[end]);
		//向下调整重新建堆
		AdjustDown(arr, end, 0);
		--end;
	}
}
int main()
{
	int arr[] = { 27,15,19,18,28,34,65,49,25,37 };
	int n = sizeof(arr) / sizeof(int);
	HeapSort(arr, n);
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

在这里插入图片描述

2、TOP-K问题

在前面堆的简单应用中我们也简单的学习了用堆的数据结构进行解决TOP-K问题,我们建立一个大堆,对堆顶元素进行删除拿到第一个数据,再重新向下调整建堆,再对堆顶元素进行删除拿到第二个数据…如此往复,便解决了TOP-K的问题。
在这里插入图片描述

但是一般情况下TOP-K问题的数据量都比较大,我们再利用上面的方法可能就不太行了,例如从100亿个数据中选择10个最大的,假设全是整数,那么需要400亿字节,而1G≈10亿字节,我们用数组存储这100亿个数就需要40G的内存,这显然内存不足,于是我们就需要把这么多的数据放进硬盘中了,利用文件读取来读取数据,可是硬盘中的数据并不能建堆,于是我们就要考虑其他的算法了。

我们可以建立一个K(K为要想要选取的数的个数)个数据的小堆,从文件中读取数据与堆顶的元素进行比较,如果大于堆顶元素就替换掉堆顶元素,然后向下调整,重新建立小堆,这样经过遍历所有数后,想要的K个数就在小堆里面!
在这里插入图片描述

代码示例

int main()
{
	//选最大的5个数
	int randK = 5;
	//打开文件
	FILE* pfin = fopen("data.txt", "w");
	if (NULL == pfin)
	{
		perror("fopen fail:");
		return;
	}
	//设置随机种子
	srand(time(NULL));
	int val = 0;
	for (int i = 0; i < 500; i++)
	{
		//插入5个明显更大的数据,方便判断TOP-K结果是否正确
		if (i != 0 && i % 7 == 0 && randK > 0)
		{
			fprintf(pfin, "%d ", 1000 + i);
			randK--;
			continue;
		}
		//造500个随机数
		val = rand() % 1000;
		fprintf(pfin, "%d ", val);
	}
	//关闭文件
	fclose(pfin);
	//以读的方式打开文件
	FILE* pfout = fopen("data.txt", "r");
	if (NULL == pfout)
	{
		perror("fopen fail:");
		return;
	}
	//取5个数建立小堆
	int min_heap[5];
	for (int i = 0; i < 5; i++)
	{
		fscanf(pfout, "%d", &min_heap[i]);
	}
	for (int i = (5 - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(min_heap, 5, i);
	}
	while (fscanf(pfout, "%d", &val) != EOF)
	{
		if (val > min_heap[0])
		{
			min_heap[0] = val;
			AdjustDown(min_heap, 5, 0);
		}
	}
	fclose(pfout);
	//打印堆中的数据
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", min_heap[i]);
	}
	return 0;
}

在这里插入图片描述

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

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

相关文章

网络传输:linux下的网络请求和下载(ping wget curl)、端口

一、下载和网络请求 1.ping命令 可以通过ping命令&#xff0c;检查指定的网络服务器是否可连通状态 语法&#xff1a;ping [-c num] ip或主机名 选项&#xff1a; -c 检查的次数&#xff0c;若不使用-c&#xff0c;将无限次数持续检查参数&#xff1a;ip或主机名&#xff0c…

基于Python的时间序列异常值检测

今天我们介绍一下使用python做时间序列数据分析和预测中异常值检测的方法&#xff0c;常用的异常值检测方法有以下几种&#xff1a; 3sigma: 基于正太分布&#xff0c;当数据值超过3个标准差(3sigma)时为异常值。z-score : z标准分数&#xff0c;它测量数据值到平均值的距离,当…

pandas对某一列的种类编码

文章目录背景实现背景 如果某一列的种类特别多&#xff0c;想要通过映射来编码&#xff0c;这样非常麻烦&#xff0c;所以可以对一个列全部一次性进行编码。 例如我的数据如下&#xff1a; 我需要编码专业这个列&#xff0c;我们可以看到这一列很多&#xff1a; 实现 使用…

【Unity】P3 基础设定

Unity基础设定父子关系InspectorTransformMesh FilterMesh Rendener第二种常用的父子操作Global 与 Local 模式Pivot 与 Center 模式声音组件创建多场景Asset 资源商店前言 上一篇博文主要围绕Unity的静态基础操作部分&#xff0c;从创建好的一个小方块cube开始&#xff0c;到对…

Selenium基于POM的自动化测试实践

什么是Page Object模式 Page Object 见名知意&#xff0c;就是页面对象&#xff0c;并将页面元素定位方法和元素操作进行分离。在实际自动化测试实战过程中&#xff0c;我们一般对脚本的实现分为三层&#xff1a; (1)对象层&#xff1a; 用于存放页面元素定位和控件操作 (2)逻…

《Nacos(3) - 集群搭建(详细)》

《Nacos(3) - 集群搭建》 提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!! 《Nacos3 - 集群搭建》《Nacos(3) - 集群搭建》1.集群结构图2.搭建集群2.1.初始化数据库2.2.下载nacos2.3.配置Nacos2.4.启动2.5.nginx反向代理2.6.优化1.集群结构图 官方给出的Na…

【JavaSE】对象的比较

哈喽&#xff0c;大家好&#xff01;我是保护小周ღ&#xff0c;本期为大家带来的是Java中自定义类型&#xff08;对象&#xff09;的三种比较方式&#xff0c;equals 方法, Comparable 泛型接口, Comparator 泛型接口 。在日常编程中&#xff0c;我们常常会需要比较的问题&…

cuda版本,pytorch(GPU)版本的选择和下载

cuda版本&#xff1a; 1.Nvidia控制面板里的cuda版本, 或使用nvidia-smi命令显示的cuda版本 是cuda的driver api版本 2.nvcc -V中的cuda版本&#xff0c;是cuda的runtime api版本&#xff0c;即cudatoolkit的版本 cudatoolkit的版本不能高于cuda driver api的版本&#xff…

23.2.28 Staffing System

员工管理系统功能介绍&#xff1a; 1&#xff09;服务器负责管理所有员工表单&#xff08;以数据库形式&#xff09;&#xff0c;其他客户端可通过网络连接服务器来查询员工表单。 2&#xff09;需要账号密码登陆&#xff0c;其中需要区分管理员账号还是普通用户账号。 3&am…

聚观早报 | 苹果2024年放弃高通;腾讯回应进军类 ChatGPT

今日要闻&#xff1a;苹果2024年放弃高通&#xff1b;腾讯回应进军类 ChatGPT&#xff1b;小米发布无线AR眼镜探索版&#xff1b;50%的美国企业已在使用ChatGPT&#xff1b;Snap推出ChatGPT驱动的聊天机器人 苹果2024年放弃高通 高通公司 CEO 兼总裁克里斯蒂亚诺・安蒙&#xf…

Node.js 是个啥?

趣学 Node.js - 死月 - 掘金小册带你重新体悟 Node.js 之美。「趣学 Node.js」由死月撰写&#xff0c;1923人购买https://s.juejin.cn/ds/SYVvuDw/ 在这里&#xff0c;我们先装作对 Node.js 不了解&#xff0c;从头来过吧。你有没有假装不了解 Node.js 我不知道&#xff0c;但…

界面开发(2)--- 使用PyQt5制作用户登陆界面

使用PyQt5制作用户登陆界面 上篇文章已经介绍了如何配置PyQt5环境&#xff0c;这篇文章在此基础上展开&#xff0c;主要记录一下如何使用 PyQt5 制作用户登陆界面&#xff0c;并对一些基础操作进行介绍。 下面是具体步骤&#xff0c;一起来看看吧&#xff01; 1. 打开 Pychar…

【IoT】2023裁员潮还在继续,构建规划能力也许是一剂良方

今天要分享的主题是华为的市场管理方法论。 市场管理这个词总体来说还是有些抽象&#xff0c;本质上来看或者说从个人的角度来看&#xff0c;其实就是一种规划的能力。 无论是创业&#xff0c;还是作为职场人&#xff0c;规划能力必将是你不可或缺的一种基础能力。 尤其是在这样…

Maven说明

目录 1.说明 2.详细说明 3.Maven模型 4.Maven常用的命令 5.Maven生命周期 6.Maven坐标 7.依赖管理与依赖范围 1.说明 Maven是专门用于管理和构建Java项目的工具&#xff0c;它是基于项目对象模型(POM)的概念&#xff0c;主要功能有&#xff1a; 提供了一套标准化的项目…

Ubuntu 下NGINX 的简单使用

1.NGINX的安装与卸载 1.1.安装NGINX apt-get install nginx1.2.NGINX操作命令 service nginx start #启动 service nginx reload #重新加载配置文件 service nginx restart #重启 service nginx status #查看运行状态 1.3.卸载NGINX apt-get remove nginx nginx-common #…

28 openEuler管理网络-配置主机名

文章目录28 openEuler管理网络-配置主机名28.1 简介28.2 使用hostnamectl配置主机名28.2.1 查看所有主机名28.2.2 设定所有主机名28.2.3 设定特定主机名28.2.4 清除特定主机名28.2.5 远程更改主机名28.3 使用nmcli配置主机名28 openEuler管理网络-配置主机名 28.1 简介 hostn…

XXL-JOB的基本使用

1、执行器 1.1下边配置执行器 下边配置执行器&#xff0c;执行器负责与调度中心通信接收调度中心发起的任务调度请求。 1、首先在媒资管理模块的service工程添加依赖&#xff0c;在项目的父工程已约定了版本2.3.1 XML <dependency><groupId>com.xuxueli</gro…

【Web安全社工篇】——水坑攻击

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a;努力赚钱不是因为爱钱“水坑攻击”&#xff0c;黑客攻…

CVPR 2023 接收结果出炉!再创历史新高!录用2360篇!(附10篇最新论文)

点击下方卡片&#xff0c;关注“CVer”公众号AI/CV重磅干货&#xff0c;第一时间送达点击进入—>【计算机视觉】微信技术交流群2023 年 2 月 28 日凌晨&#xff0c;CVPR 2023 顶会论文接收结果出炉&#xff01;这次没有先放出论文 ID List&#xff0c;而是直接 email 通知作…

【C语言】位段

位段 一.简介 位段和结构体很相似。不同的是&#xff1a; 位段的成员&#xff1a;成员名 : 数字且其成员必须是整型(char、int、unsigned int……) 示例&#xff1a; struct S {char a : 3;char b : 2;char c : 7; };S就是一个位段类型&#xff0c;其成员a为占3个比特位的…