数据结构从入门到精通——堆

  • 前言
  • 一、二叉树的顺序结构及实现 (堆)
    • 1.1二叉树的顺序结构
    • 1.2堆的概念及结构
  • 二、堆的练习题
    • 答案
  • 三、堆的实现
    • 3.1堆向下调整算法
    • 3.2堆的创建
    • 3.3建堆时间复杂度
    • 3.4堆的插入
    • 3.5堆的删除
    • 3.6堆的代码实现
  • 四、堆的具体实现代码
    • Heap.h
    • Heap.c
    • Test.c
    • 堆的初始化
    • 堆的销毁
    • 数据交换函数
    • 堆的向上交换
    • 元素入堆
    • 堆的向下交换
    • 元素出堆
    • 堆顶元素
    • 堆是否为空
  • 五、堆的应用
    • 5.1 数组向上调整建堆
    • 5.2数组向下调整建堆
    • 5.3堆排序
    • 5.4TOP-K问题
      • 直接建数据
      • 文件建数据
      • 完整代码
        • test.c
        • 数据交换
        • 向下调整
        • 主函数


前言

堆是一种特殊的树形数据结构,具有完全二叉树的特性。在堆中,父节点的值总是大于或等于(大顶堆)或小于或等于(小顶堆)其子节点的值。堆通常用于实现优先队列,其中每个元素都有一个优先级,优先级最高的元素总是位于堆的根节点。堆的插入和删除操作的时间复杂度都是O(log n),因此堆是一种高效的数据结构。此外,堆还可以用于实现内存管理,例如垃圾回收和内存分配等。


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

1.1二叉树的顺序结构

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

1.2堆的概念及结构

如果有一个关键码的集合K = {K0 ,K1 ,K2 ,…,Kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <=K2*i+1 且Ki <=K2*i+2 ( Ki>=K2*i+1 且Ki >= K2*i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

在这里插入图片描述

二、堆的练习题

  1. 下列关键字序列为堆的是:()
    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,100,70,65,60
    F 、50,100,70,65,60,32

  2. 已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是()。
    A 、1
    B、 2
    C 、3
    D 、4

  3. 一组记录排序码为(5 11 7 2 3 17),则利用堆排序方法建立的初始堆为
    A、(11 5 7 2 3 17)
    B、(11 5 7 2 17 3)
    C、(17 11 7 2 3 5)
    D、(17 11 7 5 3 2)
    E、(17 7 11 3 5 2)
    F、(17 7 11 3 2 5)

  4. 最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()
    A、[3,2,5,7,4,6,8]
    B、[2,3,5,7,4,6,8]
    C、[2,3,4,5,7,8,6]
    D、[2,3,4,5,6,7,8]

答案

1.A
2.C
3.C
4.C

三、堆的实现

3.1堆向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

在这里插入图片描述

3.2堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6}; 

在这里插入图片描述

3.3建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

在这里插入图片描述
因此:建堆的时间复杂度为O(N)。

3.4堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

在这里插入图片描述

3.5堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

在这里插入图片描述

3.6堆的代码实现

typedef int HPDataType;
typedef struct Heap
{
 	HPDataType* _a;
 	int _size;
 	int _capacity; 
}Heap;
 
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* 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);

四、堆的具体实现代码

Heap.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int capacity;
	int size;
}HP;

void Swap(HPDataType* a, HPDataType* b);//数据交换函数
void AdjustUp(HPDataType* a, int child);//向上交换
void AdjustDown(HPDataType* a, int n,int parent);//向下交换
//堆的初始化
void HPInit(HP* php);
//堆的销毁
void HPDestroy(HP* php);

//插入数据
void HPPush(HP* php,HPDataType x);

HPDataType HPTop(HP* php);//堆顶元素
//删除堆顶元素
void HPPop(HP* php);
bool HPEmpty(HP* php);

Heap.c

#include "Heap.h"
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity =  php->size = 0;
}
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType temp = *a;
	*a = *b;
	*b = temp;
}
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("newnode realloc : ");
			return;
		}
		php->a = newnode;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}
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 = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HPPop(HP* php)
{
	assert(php);
	assert(!HPEmpty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent* 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
HPDataType HPTop(HP* php)
{
	assert(php);
	return php->a[0];
}
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

Test.c

#include"Heap.h"

int main()
{
	//int a[] = { 50,100,70,65,60,32 };
	int a[] = { 60,70,65,50,32,100 };

	HP hp;
	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}

	printf("%d\n", HPTop(&hp));
	HPPop(&hp);
	printf("%d\n", HPTop(&hp));
	while (!HPEmpty(&hp))
	{
		printf("%d\n", HPTop(&hp));
		HPPop(&hp);
	}

	HPDestroy(&hp);

	return 0;
}

堆的初始化

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

堆是一种特殊的树形数据结构,通常用于实现优先队列。在初始化堆时,需要按照一定规则将元素填充到堆中。一般来说,堆的初始化可以采用从上到下、从左到右的方式遍历数组,对于每个非叶子节点,将其与其子节点中较大的一个进行交换,确保父节点的值不小于其子节点的值,从而满足堆的性质。这种操作被称为堆化或调整。通过遍历整个数组并进行堆化操作,最终可以得到一个满足堆性质的堆结构。

堆的销毁

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

堆的销毁是释放由堆分配的内存空间的过程。当不再需要堆上分配的对象时,必须显式地销毁它们以释放内存,防止内存泄漏。销毁操作通常通过调用对象的析构函数来完成,它会执行必要的清理任务,如释放对象拥有的资源。销毁后,对象变得无效,不应再被使用。在C++中,可以使用delete操作符来销毁堆上分配的对象。在销毁过程中,需要特别注意避免重复销毁和野指针问题。

数据交换函数

void Swap(HPDataType* a, HPDataType* b);//数据交换函数

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType temp = *a;
	*a = *b;
	*b = temp;
}

堆的向上交换

void AdjustUp(HPDataType* a, int child);//向上交换

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 = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

堆的向上交换是在堆排序算法中常用的一个操作。在堆排序过程中,当某个节点的值大于其父节点时,需要进行向上交换,即将该节点与其父节点交换位置,以保持堆的性质。这种交换操作从下往上进行,直至满足堆的定义要求。向上交换是堆排序中调整堆结构的关键步骤之一,有助于提高排序效率。

元素入堆

//插入数据
void HPPush(HP* php,HPDataType x);
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("newnode realloc : ");
			return;
		}
		php->a = newnode;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

元素入堆是指将一个元素插入到堆(Heap)这种数据结构中的过程。堆通常是一种特殊的树形数据结构,其每个父节点的值都大于或等于(在最大堆中)或小于或等于(在最小堆中)其子节点的值。元素入堆的过程通常涉及到调整堆的结构,以保持其性质。在插入新元素后,可能需要通过“上浮”或“下沉”操作来调整元素位置,确保堆的性质得以维持。这个过程对于堆排序、优先队列等算法至关重要。

堆的向下交换

void AdjustDown(HPDataType* a, int n,int parent);//向下交换

void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent* 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

堆的向下交换是堆排序算法中的一个重要步骤。在堆排序中,首先构建一个最大堆或最小堆,然后通过不断将堆顶元素与堆尾元素交换并重新调整堆结构,达到排序的目的。向下交换是指将堆顶元素与其子节点中较大的(对于最大堆)或较小的(对于最小堆)元素交换位置,然后重新调整子堆,以保持堆的性质。这个过程重复进行,直到整个堆排序完成。向下交换是堆排序算法中的关键步骤,能够确保堆的性质得以维持,从而实现快速排序。

元素出堆

void HPPop(HP* php);

void HPPop(HP* php)
{
	assert(php);
	assert(!HPEmpty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

出堆操作是堆数据结构中的一种常见操作,主要用于从堆中移除并返回堆顶元素(即具有最大或最小值的元素)。在执行出堆操作时,首先需要将堆顶元素与堆的最后一个元素交换位置,然后调整剩余元素以维持堆的性质。对于最大堆,堆顶元素总是最大的,而对于最小堆,堆顶元素总是最小的。出堆操作的时间复杂度通常为O(log n),其中n是堆中元素的数量。通过出堆操作,可以高效地获取并删除堆中的最大或最小元素,从而在各种算法和数据结构中实现高效的数据处理和查询。

堆顶元素

HPDataType HPTop(HP* php);//堆顶元素

HPDataType HPTop(HP* php)
{
	assert(php);
	return php->a[0];
}

堆是否为空

bool HPEmpty(HP* php);
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

五、堆的应用

5.1 数组向上调整建堆

void HPInitArray(HP* php, HPDataType* a, int n)
void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("php->a malloc :");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->capacity = php->size = n;
	//向上排序 时间复杂度N*log N
	for (int i = 1; i < php->size; i++)
	{
		AdjustUp(php->a, i);
	}
}

数组向上调整建堆是一种构建堆(Heap)的方法,通常用于实现堆排序算法。该方法从数组的中间位置开始,将每个元素作为潜在的堆顶,然后通过向上调整操作,确保以该元素为根的子树满足堆的性质(最大堆或最小堆)。向上调整操作包括将根节点与其子节点比较,并在必要时交换它们的位置,以确保堆的性质得以维持。通过从数组的中间位置到第一个元素的顺序进行向下调整,最终可以构建出一个完整的堆结构。这种方法的时间复杂度为O(nlogn),其中n是数组的长度。
在这里插入图片描述

在这里插入图片描述

5.2数组向下调整建堆

void HPInitArray(HP* php, HPDataType* a, int n)
void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("php->a malloc :");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->capacity = php->size = n;
	向上排序 时间复杂度N*log N
	//for (int i = 1; i < php->size; i++)
	//{
	//	AdjustUp(php->a, i);
	//}
	//向下排序,时间复杂度N
	for (int i = (php->size - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

数组向下调整建堆是指在构建一个最大堆(或最小堆)时,从数组末尾开始,逐个向上调整每个非叶子节点,使其满足堆的性质。具体步骤如下:

  1. 从最后一个非叶子节点开始,向前遍历数组。
  2. 对于每个节点,检查其是否满足堆的性质,即是否大于(或小于)其子节点。
  3. 如果不满足堆的性质,则将其与其较大的子节点交换位置,并继续向下调整子树,直到满足堆的性质。
  4. 重复步骤2和3,直到遍历完所有节点。

通过这种向下调整的方式,可以高效地构建一个最大堆(或最小堆),为后续的堆排序等操作提供基础。这种办法的时间复杂度是O(N).
在这里插入图片描述
在这里插入图片描述

5.3堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  • 建堆
    • 升序:建大堆
    • 降序:建小堆
  • 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

在这里插入图片描述

void HeapSort(HPDataType* a, int n)
{
	for (int i = (n-1 - 1)/2; i >=0 ; i--)
	{
		AdjustDown(a,n,i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序是一种基于二叉堆数据结构的排序算法。它首先将待排序序列构造成一个大顶堆(或小顶堆),然后依次将堆顶元素(最大值或最小值)与堆尾元素交换并删除,再通过调整堆结构使其保持为堆,重复此过程直至堆为空。这样,就能得到一个有序序列。堆排序的时间复杂度O(nlogn),空间复杂度为O(1)。

5.4TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

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

  • 用数据集合中前K个元素来建堆
    • 前k个最大的元素,则建小堆
    • 前k个最小的元素,则建大堆
  • 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
ps:剩余的数据可能是非递增的,想要递增的话,可以自己添加排序算法

直接建数据

void PrintTopK(int* a, int n, int k)
{
 // 1. 建堆--用a中前k个元素建堆
 
 // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
}
 
void TestTopk()
{
 	int n = 10000;
 	int* a = (int*)malloc(sizeof(int)*n);
 	srand(time(0));
 	for (size_t i = 0; i < n; ++i)
	 {
 		a[i] = rand() % 1000000;
 	}
	a[5] = 1000000 + 1;
 	a[1231] = 1000000 + 2;
 	a[531] = 1000000 + 3;
 	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
 	a[2335] = 1000000 + 6;
 	a[9999] = 1000000 + 7;
 	a[76] = 1000000 + 8;
 	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
 	PrintTopK(a, n, 10);
}

文件建数据

void CreateNDate()
{
	int k = 10000;
	srand((unsigned int)time(NULL));
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("pf fopen :");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fprintf(pf, "%d\n", rand()%10000 + i );
	}
	fclose(pf);
}

完整代码

test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
void CreateNDate()
{
	int k = 10000;
	srand((unsigned int)time(NULL));
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("pf fopen :");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fprintf(pf, "%d\n", rand()%10000 + i );
	}
	fclose(pf);
}
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
int main()
{
	//CreateNDate();
	int k = 0;
	printf("输入需要排序的个数:  \n", &k);
	scanf("%d", &k);
	int* a = (int*)malloc(sizeof(int) * k);
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("pf fopen :");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(pf, "%d", &a[i]);
	}
	for (int i = (k - 1 - 1) / 2 ; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}
	int x = 0;
	while (fscanf(pf, "%d", &x) != EOF)
	{
		if (a[0] < x)
		{
			a[0] = x;
			AdjustDown(a, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}

	fclose(pf);
	return 0;
}
数据交换
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
向下调整
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
主函数
int main()
{
	//CreateNDate();  在需要新数据的时候开启或关闭
	int k = 0;
	printf("输入需要排序的个数:  \n", &k);
	scanf("%d", &k);
	int* a = (int*)malloc(sizeof(int) * k);
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("pf fopen :");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(pf, "%d", &a[i]);
	}
	for (int i = (k - 1 - 1) / 2 ; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}
	int x = 0;
	while (fscanf(pf, "%d", &x) != EOF)
	{
		if (a[0] < x)
		{
			a[0] = x;
			AdjustDown(a, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}

	fclose(pf);
	return 0;
}

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

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

相关文章

基于单片机的温度控制系统设计

基于单片机的温度控制系统设计 摘要: 最近这些年&#xff0c;随着科学技术的不断发展和进步&#xff0c;单片机技术通过在各行各业中的应用也日臻完善。而温度测控系统也因单片机所特有的强大处理能力、功耗低以及体积小等优点向着小型化和智能化发展。本设计以STC89C52单片机…

Linux服务器之间免密登录

文章目录 1. 原理2. 密钥文件作用解释3. 实操 1. 原理 2. 密钥文件作用解释 #mermaid-svg-uJJwWJXgqGtbNwNB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-uJJwWJXgqGtbNwNB .error-icon{fill:#552222;}#mermaid-s…

AttributeError: module ‘tensorflow‘ has no attribute ‘placeholder‘解决办法

1.报错代码 self.inputs_base_structure_left tf.placeholder(dtypetf.float32, shape[None, 2048, 2], name"inputs_left") # initial a inputs to siamese_network2. 报错原因 AttributeError: module tensorflow has no attribute placeholder 这个错误发生的…

基于Java+SpringBoot+vue的智能农场管理系统详细设计和实现

基于JavaSpringBootvue的智能农场管理系统详细设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文…

KKVIEW: 远程控制软件哪个好用

远程控制软件哪个好用 随着科技的发展和工作方式的改变&#xff0c;远程控制软件越来越受到人们的关注和需求。无论是在家中远程办公&#xff0c;还是技术支持人员为远程用户提供帮助&#xff0c;选择一款高效稳定的远程控制软件至关重要。在众多选择中&#xff0c;有几款远程…

做外贸如何打动老是邮件不回复的客户

有人说&#xff1a;进入公司半年&#xff0c;都没有碰到什么大客户&#xff0c;小客户接了没利润&#xff0c;不想接&#xff0c;很难找到自己的定位&#xff0c;不知道如何去开发客户。 这是一个范围很大的问题&#xff0c;每个行业不一样&#xff0c;做外贸很多时候都是相通…

ET框架新起一个服务及实现服务之间的消息通讯

1.配置文件StartSceneConfig 2. SceneFactory switch (scene.SceneType) {case SceneType.Activity:break; } 定义SceneType枚举类型 public enum SceneType: uint {Activity 66, } 3.在InnerMessage.proto文件中定义消息 //ResponseType Activity2Other_Test message Ot…

Unity3d Shader篇(十五)— 激光扫描效果

文章目录 前言一、什么是X射线或激光扫描效果&#xff1f;1. X射线或激光扫描效果原理2. X射线或激光扫描效果优缺点优点&#xff1a;缺点&#xff1a; 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三、效果四…

Labelme

文章目录 前言一、遇到问题二、排查问题1.分析问题2.验证问题2.1对比两者&#xff0c;格式是一致的&#xff0c;唯一不同之处是imagePath 不一样&#xff0c;labelme 生成的是图片的名称&#xff0c;不包含路径&#xff1b;而自动生成的是完整路径的图片名称。2.2再次思考两者的…

elasticsearch8.12 分词器安装

分词器的主要作用将用户输入的一段文本&#xff0c;按照一定逻辑&#xff0c;分析成多个词语的一种工具 分词器下载地址 analysis-ik Releases infinilabs/analysis-ik GitHub 一个简便 安装方式 安装完成之后 会提示重启&#xff0c;重启es即可 ./bin/elasticsearch-pl…

【AI论文阅读笔记】ResNet残差网络

论文地址&#xff1a;https://arxiv.org/abs/1512.03385 摘要 重新定义了网络的学习方式 让网络直接学习输入信息与输出信息的差异(即残差) 比赛第一名1 介绍 不同级别的特征可以通过网络堆叠的方式来进行丰富 梯度爆炸、梯度消失解决办法&#xff1a;1.网络参数的初始标准化…

C++ 拷贝构造函数和运算符重载

目录 一. 拷贝构造函数 1. 引入 2. 拷贝构造的概念 3. 浅拷贝 4. 深拷贝 二. C运算符重载 1. 概念 2. 注意事项 3.举例 一. 拷贝构造函数 1. 引入 我们在创建对象时&#xff0c;能不能创建一个与原先对象一模一样的新对象呢&#xff1f;为了解决这个问题&#x…

C++——类和对象(2)

1. 类的6个默认成员函数 当一个类中什么都没有&#xff0c;编译器会帮类自动生成6个默认成员函数例如&#xff1a; class Date {}; 此篇文章主要围绕构造函数与析构函数进行讲解。 2. 构造函数 2.1 概念 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> usi…

如何零基础入门Prometheus

本公众号的精品教程《玩转Prometheus监控》是一套零基础的入门教程&#xff0c;基于多年实战经验编写而成&#xff0c;内容完整覆盖了产品的核心技术要点&#xff0c;适合想入门和进阶技术的朋友学习。 整个系列总共24篇课程&#xff0c;由基础知识开始&#xff0c;逐步进阶学…

复现文件上传漏洞

一、搭建upload-labs环境 将下载好的upload-labs的压缩包&#xff0c;将此压缩包解压到WWW中&#xff0c;并将名称修改为upload&#xff0c;同时也要在upload文件中建立一个upload的文件。 然后在浏览器网址栏输入&#xff1a;127.0.0.1/upload进入靶场。 第一关 选择上传文件…

webpack5零基础入门-8清空前次打包文件与处理图标字体资源

1.配置output中的clean属性为true output: {/**文件输出路径 绝对路径*///__dirname 表示当前文件的文件夹目录path: path.resolve(__dirname, dist),//所有文件的输出目录/**文件名 */filename: static/js/dist.js,//入口文件输出文件名clean: true,//在打包前将path整个目录内…

【学习笔记】红队视角下的windows应急响应

1. 上线的方法 exe上线→开360晶核的情况比较困难 2&#xff09;白加黑 接下来的讲解就是基于白加黑上线&#xff0c;看如何应对应急 2. 演示 360环境启动 shell whoami →死 -beacon 如何去查杀 看外联&#xff1a; netstat -ano 提取IP 威胁情报api调用→查是否是恶意…

【Qt】QListView 显示富文本,设置文本内容颜色

【Qt】QListView 显示富文本&#xff0c;设置文本内容颜色 文章目录 I - 控件使用II - 显示富文本III - 注意事项 I - 控件使用 Qt 的 MVC 架构为 MV &#xff0c;Controller 部分继承到了 View 里&#xff0c;View(视图) 设置 Model(模型)&#xff0c;Model 设置数据 这里使用…

设备维修带来的无限价值——易点易动设备管理系统的优势

在化工工厂中&#xff0c;设备的正常运行是保障生产顺利进行的关键。然而&#xff0c;设备难免会出现故障和损坏&#xff0c;而及时有效的设备维修对于提高生产效率和降低成本至关重要。为了解决这一问题&#xff0c;易点易动设备管理系统应运而生&#xff0c;以其卓越的功能和…

TEASEL: A transformer-based speech-prefixed language model

文章目录 TEASEL&#xff1a;一种基于Transformer的语音前缀语言模型文章信息研究目的研究内容研究方法1.总体框图2.BERT-style Language Models&#xff08;基准模型&#xff09;3.Speech Module3.1Speech Temporal Encoder3.2Lightweight Attentive Aggregation (LAA) 4.训练…