堆--二叉树的特有形式

news2024/11/20 2:32:09

目录

  • 前言
  • 1.二叉树的顺序结构及实现
    • 1.1二叉树的顺序结构
    • 1.2堆的概念及结构
  • 2.堆的功能函数的实现
    • 2.1堆结构体的定义
    • 2.2堆的初始化
    • 2.3堆的插入
    • 2.4 获取堆是否为空、堆大小、堆顶元素的函数
    • 2.5堆的销毁
    • 2.6对利用堆结构数组的数据建堆
    • 2.7堆的删除
    • 堆结构的源码
  • 3.堆排序
    • 建堆的时间复杂度:
  • 4.Topk问题
  • 5.堆的思维导图
  • 总结

前言

在没有学习堆排序之前,我们可能会存在一些问题
1.什么是堆的向上调整?
2.什么是堆的向下调整?
3.堆的向下调整,为啥从最后一个非叶子节点开始调整?而不是从堆顶开始把数据往下调整?
4.向上调整和向下调整都可以建堆吗?它们的时间复杂度分别是多少呢?
5.为啥使用堆排序–排升序建大堆、排降序建小堆呢?
6.topk问题,为啥找前k个最大的数据建小堆,找前k个最小的数据建大堆呢?
7.向上调整、向下调整的应用,能不能相互替代呢?

1.二叉树的顺序结构及实现

1.1二叉树的顺序结构

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

逻辑结构是我们想象出来的;而物理结构是实实在在存在的结构。
由上图我们可知二叉树的值在数组位置中的父子下标关系

parent = (child - 1) / 2;
leftchild = parent * 2 + 1;
rightchild = parent * 2 + 2;

1.2堆的概念及结构

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

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一颗完全二叉树。
    在这里插入图片描述

由上图可知

  • 小根堆中所有的父亲节点的值都小于或等于孩子节点的值。
  • 大根堆中所有的父亲节点的值都大于或等于孩子节点的值。

2.堆的功能函数的实现

2.1堆结构体的定义

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;//记录存储数据个数
	int capacity;//堆的容量
}HP;

2.2堆的初始化

void HeapInit(HP* php)
{
	assert(php);
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * 3);
	if (tmp == NULL)
	{
		perror("malloc::fail");
		return;
	}
	php->a = tmp;
	php->size = 0;
	php->capacity = 3;
}

首先我们需要为结构体成员变量a在堆上开辟sizeof(HPDataType) * 3的空间,保证其他函数也能使用,并将成员变量size初始化为0,成员变量capacity初始化为3。

2.3堆的插入

接下来我们以大堆为例,向大堆中插入数字,然后经过调整后,再使其成为大堆。
在这里插入图片描述

我们在堆底插入了一个数据成为二叉树的孩子节点,通过与其父亲节点的值比较,若孩子节点的值大于父亲节点,我们该节点向上调整(即交换该孩子节点和父亲节点的值)直到该节点的值小于等于其父亲节点的值为止。这时候我们需要写一个向上调整的函数,来进行堆的插入

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//堆的向上调整
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	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;
		}
	}
}

在堆的向上调整函数中,将孩子的节点的值与其父亲节点的值进行比较,若孩子节点值大于父亲节点的值,则通过child = parent;parent = (child - 1) / 2;的操作语句不断向上调整,直到将数组末尾的孩子节点调整到堆顶或者该孩子节点的值小于父亲节点的值为止。

堆的插入函数

//堆的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, 
			sizeof(HPDataType) * php->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc::fail");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size++] = x;
	AdjustUp(php->a, php->size - 1);
}

在堆的向上调整函数中,①用assertphp->a地址进行断言,若该地址为空则报错;②php->size == php->capacity判断开辟的堆空间是否已经满了,若堆空间满了,则需要使用realloc函数将堆的空间扩大为原来的两倍,从而达到动态开辟空间的效果③php->a[php->size++] = x;插入新的节点,然后使用堆的向上调整函数,使该结构重新成为堆。

进行调试的代码

void Test1()
{
	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 18);
	HeapPush(&hp, 99);
	HeapPush(&hp, 29);
	HeapPush(&hp, 45);
	HeapPush(&hp, 108);
	HeapPush(&hp, 66);
	HeapPush(&hp, 95);
}

代码调试的结果如下:
在这里插入图片描述

2.4 获取堆是否为空、堆大小、堆顶元素的函数

//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
//堆的大小
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
//堆顶元素的获取
HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

2.5堆的销毁

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

2.6对利用堆结构数组的数据建堆

// 堆的构建
void HeapCreate(HP* php, HPDataType* a, int n)
{
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc::fail");
		return;
	}
	php->capacity = n;
	php->size = n;
	for (int i = (n - 1 - 1) / 2; i > 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

2.7堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。 ⭐⭐⭐
在这里插入图片描述
在这里插入图片描述

堆的向下调整函数

//堆的向下调整
//堆的左右子树都是大堆或者都是小堆
void AdjustDown(HPDataType* a,int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		//选出左右孩子较大的那一个
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

在堆的向下调整函数中: ①选出要向下调整节点的左右孩子节点,较大的孩子节点;②将向下调整的双亲节点的值与其较大孩子节点的值进行比较,如果双亲结点的值较小,则进行交换,并进行parent = child;child = parent * 2 + 1;的操作进行向下调整,直到该节点的值大于或者等于其孩子结点的值或者该结点调整到堆底为止(即成为叶子节点)。(注意: 我们在实现堆的向下调整函数的时候,形参n为堆的数据个数,parent为向下调整的位置)

堆的删除函数:

//堆的删除
void HeapPop(HP* php)
{
	assert(php);
	//删除数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//堆顶数据向下调整
	AdjustDown(php->a, php->size, 0);
}
  • 将堆顶元素与最后一个元素进行交换
  • 删除最后一个元素
  • 将堆顶元素向下调整至满足堆的特性为止

调试运行的代码如下:

	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 18);
	HeapPush(&hp, 99);
	HeapPush(&hp, 29);
	HeapPush(&hp, 45);
	HeapPush(&hp, 108);
	HeapPush(&hp, 66);
	HeapPush(&hp, 95);
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}

代码运行的结果为:
在这里插入图片描述
通过观察代码运行的结果,我们可以发现打印出来的数据已经有序,这为我们使数据有序提供了一种思路,但是这种使数据有序的方法是有前提的:①必须手搓一个堆的数据结构才能实现②通过堆的删除然后向下调整,每删除一次就要向下调整一次,时间效率会大幅度降低,基于这两个前提实现堆数据的排序不现实。但是我们可以利用堆删除的思想实现堆排序,接下来让我们一起实现一下吧!

堆结构的源码

Heap.h

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

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;//记录存储数据个数
	int capacity;//堆的容量
}HP;
//堆的初始化
void HeapInit(HP* php);
//堆的向上调整
void AdjustUp(HPDataType* php, int child);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的向下调整
void AdjustDown(HPDataType* a, int n, int parent);
//堆的删除
void HeapPop(HP* php);
//堆的判空
bool HeapEmpty(HP* php);
//堆的大小
int HeapSize(HP* php);
//堆顶元素的获取
HPDataType HeapTop(HP* php);
//堆排序
void HeapSort(int* a, int n);
// 堆的构建
void HeapCreate(HP* php, HPDataType* a, int n);
//堆的销毁
void HeapDestroy(HP* php);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//堆的初始化
void HeapInit(HP* php)
{
	assert(php);
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * 3);
	if (tmp == NULL)
	{
		perror("malloc::fail");
		return;
	}
	php->a = tmp;
	php->size = 0;
	php->capacity = 3;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//堆的向上调整
//除了child位置,前面的数据构成堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	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;
		}
	}
}
//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
//堆的大小
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
//堆顶元素的获取
HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}
//堆的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, 
			sizeof(HPDataType) * php->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc::fail");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size++] = x;
	AdjustUp(php->a, php->size - 1);
}
//堆的向下调整
//堆的左右子树都是大堆或者都是小堆
void AdjustDown(HPDataType* a,int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		//选出左右孩子较大的那一个
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆的删除
void HeapPop(HP* php)
{
	assert(php);
	//删除数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//堆顶数据向下调整
	AdjustDown(php->a, php->size, 0);
}
// 堆的构建
void HeapCreate(HP* php, HPDataType* a, int n)
{
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc::fail");
		return;
	}
	php->capacity = n;
	php->size = n;
	for (int i = (n - 1 - 1) / 2; i > 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}
//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);
	php->capacity = php->size = 0;
	free(php->a);
	php->a = NULL;
}

3.堆排序

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

1.建堆:升序:建大堆 ; 降序:建小堆
2.利用堆删除的思想进行排序
在这里插入图片描述

需要调用的函数:

//堆的向上调整
//除了child位置,前面的数据构成堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	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 n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		//选出左右孩子较大的那一个
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

堆排序代码如下:

//堆排序
//排升序--建大堆
void HeapSort(int* a, int n)
{
	向上调整建堆
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}
	//向下调整建堆
	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--;
	}
}

对一堆无序的数据进行排序步骤:①进行建堆,可以向上调整建堆,默认堆里面已经有一个数据,用for循环向上插入进行调整建堆,直到插入并调整完所有数据为止;还可以向下调整建堆,从最后一个非叶子节点开始向下调整建堆,直到调整到堆顶②定义一个变量int end = n - 1;指向堆最后一个数据(即堆底的数据),交换堆顶和堆底的数据,AdjustDown(a, end, 0);,把交换后的堆顶的数据向下调整end--;删除交换后的堆底数据。(end为堆中的数据个数,同时也是堆底数据的下标)
调试的代码如下:

int main()
{
	int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	HeapSort(a, 10);
	return 0;
}

代码调试的结果为:
在这里插入图片描述

建堆的时间复杂度:

向上调整的时间复杂度:
图形理解:
在这里插入图片描述

可以观察到向上调整建堆从堆顶往下的节点数越多,向上调整次数越多,由向上调整建堆的计算过程可以得到时间复杂度为:O(N*logN)

向下调整的时间复杂度:
图形理解:
在这里插入图片描述

可以观察到向下调整建从最后一个非叶子节点开始向上的节点数越少,向下调整次数越多,由向下调整建堆的计算过程可以得到时间复杂度为:O(N)

向下调整排序的时间复杂度:
在这里插入图片描述

距离堆顶的节点数越多,向下调整的次数也越多,其过程与向上建堆的计算过程一样,所以时间复杂度为:O(N*logN)

总结:堆排序的时间复杂度=建堆时间复杂度+向下调整排序的时间复杂度,无论是向上调整建堆O(N*logN),还是向下调整建堆O(N),但因为向下调整排序的时间复杂度O(N*logN),根据大O渐进法的规则,所以堆排序的时间复杂度为:O(N*logN)

4.Topk问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

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

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
找前k个最大的元素的topk代码如下:

//堆的向下调整
//堆的左右子树都是大堆或者都是小堆
void AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		//选出左右孩子较小的那一个
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		//建小堆
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void CreateNData()
{
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (file == NULL)
	{
		perror("fopen::fail\n");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 10000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void PrintTopK(const char* file, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int* topk = (int*)malloc(sizeof(int) * k);
	if (topk == NULL)
	{
		perror("PrintTopK::malloc");
		return;
	}
	FILE* fin = fopen(file, "r");
	for (int i = 0; i < k; i++)
	{
		fscanf(fin, "%d", &topk[i]);
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(topk, k, i);
	}
	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int val = 0;
	int ret = fscanf(fin, "%d", &val);
	while (ret!=EOF)
	{
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}
		ret = fscanf(fin, "%d", &val);
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", topk[i]);
	}
	printf("\n");
	fclose(fin);
	free(topk);
}

①数据比较多的时候,我们可以把数据输出到“data.txt”文件中;②使用malloc开辟k个空间,然后使用向下调整函数建小堆(注意符号的改变)③把数据一个个读取到val变量中,然后与堆顶的数据比较,如果在读取的数据比堆顶的数据大,则val替换topk[0],向下调整成为小堆,重复该过程一直读取完所有数据为止。

为了方便验证是否为最大的前k个数据:①可以先使用造数据的函数

void Test()
{
	CreateNData();
	//PrintTopK("data.txt",5);
}

②修改数据:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
③注释掉造数据的函数,进行打印出前k个最大的数据,与“data.txt”文件的数据比较进行验证。

void Test()
{
	//CreateNData();
	PrintTopK("data.txt",5);
}

代码运行的结果为:
在这里插入图片描述

5.堆的思维导图

在这里插入图片描述

总结

在没有学习堆排序之前,我们可能会存在一些问题
1.什么是堆的向上调整?
答:假设已有一堆无序的数据,以建大堆为例,从第二个节点开始,模拟从堆底插入一个数据,如果它的值比父亲节点的大则向上调整,直到它交换的合适的位置(即重新调整为大堆),一直到最后一个节点的数据向上调整为止。
2.什么是堆的向下调整?
答:假设已有一堆无序的数,以建大堆为例,从最后一个非叶子节点开始(如果该节点的值小于左右孩子的较大的那一个,把该数据向下调整),一直到第一个节点的值向下调整为止。
3.堆的向下调整,为啥从最后一个非叶子节点开始调整,而不是从叶子节点开始呢?而不是从堆顶开始把数据往下调整?
答:堆的向下调整,通俗一点讲,就是把某节点的数据和它的子节点的数据进行交换,而叶子节点没有可以和它比较的(和它交换的)子节点,所以从最后一个非叶子节点开始向下调整才是有意义的;以建大堆为例,如果从堆顶开始将数据向下调整,不能保证与它的子节点的数据是最大的数据,所以需要从最后一个非叶子节点开始往上向下调整。
4.向上调整和向下调整都可以建堆吗?它们的时间复杂度分别是多少呢?
答:向上调整可以建堆,方向:从上往下,从第二个节点开始,堆越往下数据越多,要调整的次数越多,根据等比数列的求和公式、错位相减法,求出时间复杂度为O(N*log2N);向下调整可以建堆,方向:从下往上,从最后一个非叶子节点开始,堆越往上数据越少调整次数越多,要调整的次数越多,根据等比数列的求和公式、错位相减法,求出的时间复杂度为O(N)
5.为啥使用堆排序–排升序要建大堆、排降序要建小堆呢?
答:堆排序和堆删除的思想一样,把大堆(小堆)堆顶的数据与堆底的数据交换,然后把交换到堆底的最大数(最小的数)从堆中删去(因为已经有序,还在数组中,只不过不算做堆中的数据而已),交换到堆顶的数据向下调整,又可以选出新的堆中最大的数,继续交换、向下调整,直到堆中只有一个数据为止(注意: 前提是刚开始的时候,左右子树都为大堆(小堆));排升序建小堆的缺点:①需要额外开辟空间存放数据②取走堆顶的数据,剩下的数据关系全乱,需要重新建堆③需要挪动覆盖数据。
6.topk问题,为啥找前k个最大的数据建小堆,找前k个最小的数据建大堆呢?
答:前k个最大的数据,通过比较,大的数据替换掉小堆堆顶的数据才可以进堆,若是建大堆一开始在堆顶上的数据就是最大的,则无法通过比较大小让前k个最大的数据进入堆中。
7.向上调整、向下调整的应用,能不能相互替代呢?
答:向上调整可以建堆、实现堆的插入,向下调整可以建堆、实现堆的删除、堆的排序,两者不能相互替代。只有向上调整很难实现堆的删除、堆排序,只有向下调整很难保证在插入数据之后,堆还是大堆(小堆),两者有相同作用之处,也有自己的特点。

感谢大家的阅读,希望对大家认识堆的数据结构有些许帮助!若有不对,欢迎纠正!🎠🎠🎠

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

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

相关文章

2核4G服务器能安装多少个网站?亲测

2核4G服务器能安装多少个网站&#xff1f;2核4g配置能承载多少个网站&#xff1f;一台2核4G服务器可以安装多少个网站&#xff1f;阿腾云2核4G5M带宽服务器目前安装了14个网站&#xff0c;从技术角度是没有限制的&#xff0c;只要云服务器性能够用&#xff0c;想安装几个网站就…

AE关键帧

关键帧 根据上次说到的五大变换&#xff0c;找准起始时间点和起始动作再去找结束时间点和结束动作&#xff0c;其中包括可使用贝塞尔曲线对锚点进行拖拽&#xff0c;使其完成曲线运动 快捷键 n删除右侧&#xff0c;b删除左侧&#xff0c;ctrlshiftd裁剪&#xff0c;ctrld复制…

我记忆中的电脑城

目录 一、我记忆中的电脑城 二、电脑城衰退 三、拥抱趋势 在过去很长一段时间里&#xff0c;想要购买电子设备都逃不开一个叫“电脑城”的地方&#xff0c;那里鱼龙混杂良莠不齐&#xff0c;是令许多人记忆深刻分外难忘之处。 一、我记忆中的电脑城 想起上一次去电脑城&…

ylb-接口11实名认证

总览&#xff1a; 在web模块config包下&#xff0c;创建实名认证的一个配置类JdwxRealnameConfig&#xff1a; package com.bjpowernode.front.config;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype…

【NI USRP】 USRP 硬件资源和性能是怎么样的呢?是如何构成的呢

B系列 型号频段最大带宽通道FPGAADI 芯片B200mini70 MHZ - 6 GHZ56 MHz1X1Xilinx Spartan-6 XC6SLX150AD9364B200mini-i70 MHZ - 6 GHZ56 MHz1X1Xilinx Spartan-6 XC6SLX75AD9364B205mini-i70 MHZ - 6 GHZ56 MHz1X1Xilinx Spartan-6 XC6SLX75AD9364B20070 MHZ - 6 GHZ56 MHz1X…

AN OVERVIEW OF LANGUAGE MODELS RECENT DEVELOPMENTS AND OUTLOOK

LLM系列相关文章&#xff0c;针对《AN OVERVIEW OF LANGUAGE MODELS: RECENT DEVELOPMENTS AND OUTLOOK》的翻译。 语言模型综述&#xff1a;近年来的发展与展望 摘要1 引言2 语言模型的类型2.1 结构化LM2.2 双向LM2.3 置换LM 3 语言单元3.1 字符3.2 单词和子单词3.2.1 基于统…

Java正则表达式捕获组

捕获组是将多个字符视为一个单元的一种方法。 它们是通过将要分组的字符放在一组括号中来创建的。 例如&#xff0c;正则表达式(dog)创建包含字母d&#xff0c;o和g的单个组。 捕获组通过从左到右计算它们的左括号来编号。 在表达式((A)(B(C)))中&#xff0c;例如&#xff0c;…

Xline 源码解读(一) —— 初识 CURP 协议

01、Xline是什么 Xline 是一款开源的分布式 KV 存储引擎&#xff0c;其核心目的是实现高性能的跨数据中心强一致性&#xff0c;提供跨数据中心的meatdata 管理。那么 Xline 是怎么实现这种高性能的跨数据中心强一致性的呢&#xff1f;这篇文章就将带领大家一起来一探究竟。 02…

GAMES101 OpenCV环境安装

文章目录 Opencv 库编译Step 1.下载源码Step 2. 使用CMake编译Step3. 解决CMake 过程中的报错错误1&#xff1a; 错误的Python版本:错误1 解决办法 错误2&#xff1a;下载ippicv_2020_win_ia32_20191018_general.zip失败错误2 解决办法 错误3&#xff1a;ffmpeg相关文件下载失败…

ROS学习笔记(0):几个重要概念:节点、消息、主题、服务

1、节点&#xff08;node&#xff09; 节点是进行运算任务的进程。一个系统可以由很多节点组成&#xff0c;节点也可以称为软件模块。 ROS是以节点的形式开发的&#xff0c;节点是根据其目的&#xff0c;可以细分的可执行程序的最小单位。 主节点 由于机器人的元器件很多&…

win10查看、关闭和开启多个mysql服务

我的之前安装了2个MySQL版本&#xff0c;一个是MySQL8.0.17&#xff0c;一个是MySQL5.7.19 为什么要查看怎么关闭MySQL服务?如果是个人电脑&#xff0c;我觉得开启一个服务相当于开启一个进程&#xff0c;可能会占用部分内存。如果自己是游戏摆烂状态&#xff08;非学习状态&…

R语言forestploter包优雅的绘制孟德尔随机化研究森林图

在既往文章中&#xff0c;我们对孟德尔随机化研究做了一个简单的介绍。我们可以发现&#xff0c;使用TwoSampleMR包做出来的森林图并不是很美观。今天我们使用R语言forestploter包优雅的绘制孟德尔随机化研究森林图。 使用TwoSampleMR包做出来的森林图是这样的 而很多SCI文章…

qt和vue的交互

1、首先在vue项目中引入qwebchannel /******************************************************************************** Copyright (C) 2016 The Qt Company Ltd.** Copyright (C) 2016 Klarlvdalens Datakonsult AB, a KDAB Group company, infokdab.com, author Milian …

简易评分系统

目录 一、实验目的 二、操作环境 三、实验内容和过程 1.实验内容 2.代码 2.1 用户验证功能 2.2 菜单函数 2.3 评分功能 四、结果分析 总体的输出结果&#xff1a; 保存文件成功截图&#xff1a; 五、小结 一、实验目的 1.巩固和提高学生学过的基础理论和专业知识&am…

windows下安装consul、springboot整合consul

Spring Cloud Consul通过自动配置和绑定到Spring Environment和其他Spring编程模型习语&#xff0c;为Spring Boot应用程序提供Consul集成。通过一些简单的注解&#xff0c;可以快速启用和配置应用程序内的常用模式&#xff0c;并使用Hashicorp的Consul构建大型分布式系统。提供…

使用Dreambooth LoRA微调SDXL 0.9

本文将介绍如何通过LoRA对Stable Diffusion XL 0.9进行Dreambooth微调。DreamBooth是一种仅使用几张图像(大约3-5张)来个性化文本到图像模型的方法。 本教程基于通过LoRA进行Unet微调&#xff0c;而不是进行全部的训练。LoRA是在LoRA: Low-Rank Adaptation of Large Language …

如何通过CRM系统减低客户流失率并提高销售业绩?

销售人员如何提高业绩&#xff0c;减低客户流失率&#xff1f;通过CRM客户管理系统与客户建立良好的客户关系、提升客户体验助力销售人员业绩节节攀升&#xff0c;降低客户流失率。接下来我们就来说一说CRM系统如何实现的&#xff1f; 1.全渠道沟通提升客户体验 只有足够多的…

搜索结果处理

1、排序 #sort排序 GET /hotel/_search {"query": {"match_all": {}},"sort": [{"score": "desc"},{"price": "asc"}] }#找到121.6&#xff0c;31周围的酒店&#xff0c;距离升序排序 GET /hotel/_sea…

前端学习——JS进阶 (Day2)

深入对象 创建对象三种方式 构造函数 小练习 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…

python_openpyxl常用语法记录

目录 写在前面&#xff1a; 开始 工作薄 and 工作表 操作单元格 访问&#xff08;调用&#xff09;单个单元格 访问&#xff08;调用&#xff09;多个单元格 保存工作薄 用例 例&#xff1a;简单数据和条形图 操作样式 样式的默认值 单元格样式和命名样式 单元格样…