数据结构进阶篇 之 【二叉树顺序存储(堆)】的整体实现讲解(赋完整实现代码)

news2024/11/27 2:22:29

在这里插入图片描述

做人要谦虚,多听听别人的意见,然后记录下来,看看谁对你有意见

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

1.二叉树的顺序结构

2.堆的概念及结构

3.堆的实现

3.1 向下调整算法 AdJustDown

3.2 向上调整算法 AdJustUP

3.3 堆的创建

3.3.1 向上建堆
3.3.2 向下建堆
3.3.3 堆的初始化与销毁
3.3.4 堆的插入(压栈)
3.3.5 取堆顶的数据
3.3.6 堆的删除
3.3.7 堆的数据个数
3.3.8 堆的判空

二、堆的完整实现代码

三、完结撒❀

–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–

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

1.二叉树的顺序结构


物理结构:数组
逻辑结构:二叉树

顺序结构存储就是使用数组来存储普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费,而完全二叉树更适合使用顺序结构存储

可能有些同学不太清楚普通二叉树使用数组来存储为什么会造成空间上的浪费,这里给大家讲解一下:
我们使用数组进行二叉树的存储时,父子节点之间需要满足的关系为

parent = (child+1)/2;
leftchild = parent2-1;
rightchild = parent
2+2;
ps:parent指父节点在数组中的下标位置,leftchild指左子节点在数组中的下标位置,rightchild指右子节点在数组中的下标位置

那么对于满二叉树和完全二叉树,我们按照一层一层(层序)往数组中进行存储。
举例如下图所示,大家可以按照下图简单计算验证一下父子之间是否满足父子关系:
在这里插入图片描述

可以知道,对于满二叉树和完全二叉树进行层序存储在数组中,按照下标计算都是满足上面所述的父子关系。

而对于普通二叉树(非满二叉树,非完全二叉树),我们依然按照上面存储进行计算
在这里插入图片描述
可以发现不符合父子节点之间的关系,问题在于2节点的右节点为空,而在存储时对于空节点我们并没有在数组中进行存储记录,相当于在数组中少了一个位置,那么我们如果把空节点加上,如下图:
在这里插入图片描述
这样就满足了父子间的关系,但是对于下标为4的位置并没有存储数据,就造成了空间浪费。

所以,对于普通二叉树我们一般使用链式结构进行存储,避免空间浪费。

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

2.堆的概念及结构

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

堆的性质:

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

在这里插入图片描述
堆的兄弟节点(同一父节点的子两个子节点)之间是不论大小的,而对于小堆其父节点的值一定小于子节点,对于大堆其父节点的值一定大于子节点。

3.堆的实现

堆初始化结构:

//堆初始化
void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = php->size = 0;
}

在给定一个数组去实现成堆之前我们需要先了解内部实现的核心逻辑

3.1 向下调整算法 AdJustDown

现在我们给出一个数组,逻辑上看做一颗完全二叉树。

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

我们通过从根节点开始的向下调整算法可以把它调整成一个小堆
向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
上面数组逻辑上的二叉树可画为:
在这里插入图片描述
可以看出来,根节点27影响了整体的小堆结构,那么我们如何将其转变为小堆呢?
在这里插入图片描述

(制作不是很好大家将就着看看就行)
按照上面图片显示的流程,我们在逻辑上就把之前的二叉树变成了小堆排序,而其逻辑实现思想就是向下调整。

向下调整

再强调一边:
向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

从根开始,比较其子节点的大小,如果为大堆排序就将父节点与子节点中较大的节点交换,如果为小堆就将父节点与子节点中较小的节点交换。交换完毕继续重复以上逻辑,直到父节点大于子节点(大堆)或是父节点小于子节点(小堆)即可完成堆排序。

代码实现:

void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

//向下调整O(logN)
void AdJustDown(HPDataType* a, int n, int parent)
{
	//从左孩子开始,child为小孩子那个
	 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[parent], &a[child]);
			 parent = child;
			 child = child * 2 + 1;
		 }
		 else
		 {
			 break;
		 }
	 }
}

3.2 向上调整算法 AdJustUP

在一个二叉树的数组中如果我们尾插了一个数据,可能就导致结构不再是堆。

所以我们如果是在现有的一个堆里进行数据尾插存储,那么我们要保证数据插入后还是堆,这时一般使用向上调整算法。

下面我们给出一个数组,请画出其逻辑结构二叉树:

int arr[] = {10,15,56,25,30,70}

二叉树:

在这里插入图片描述
如果我将5尾插在数组当中,那么就相当于是将56节点的右孩子连了一个5节点:
在这里插入图片描述
这显然已经不是小堆了,调整逻辑如下:
在这里插入图片描述
这就是向上调整的整体逻辑:

如果是进行小堆排序,将尾节点值与其父节点的值进行比较,如果小于父节点就交换,如果进行大堆,那就判断子节点是否大于父节点,若是大于就交换。

代码实现:

void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

//向上调整 O(logN)
void AdJustUP(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;

	//while (1)严格来说不行
	while(child>0)
	{
		if (a[child] < a[parent])//小堆<,大堆>
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向下调整算法和向上调整算法的时间复杂度都为O(logN),大家感兴趣可以算一下。

3.3 堆的创建

向上调整和向下调整都是基于已经形成了堆上面,那么如果随便给一个本就不是堆的数组,我们该如何进行建堆呢?

3.3.1 向上建堆

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个小堆,现在我们通过算法,把它构建成一个小堆。

int a[] = {536821};

根节点左右子树不是小堆,我们怎么调整呢?

这里我们从根的左孩子节点开始向上调整,根据数组存储顺序向后以此执行,直到最后一个节点为止。

其二叉树为:
在这里插入图片描述
调整逻辑:
在这里插入图片描述
从根节点的子节点开始,进行向上调整,一次调整完毕将子节点对应数组下标加1进入下一个节点进行向上调整,直到除了根的所有节点都调整完毕,二叉树便变成了堆。

代码实现:

//向上调整 O(logN)
void AdJustUP(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;

	//while (1)严格来说不行
	while(child>0)
	{
		if (a[child] < a[parent])//小堆<,大堆>
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//初始化建堆
void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);

	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;

	//向上建堆 O(N*logN)
	for (int i = 1; i < php->size; i++)
	{
		AdjustUp(php->a, i);
	}
}
3.3.2 向下建堆

向下建堆就是根据向下调整的逻辑进行。

我们把二叉树分为其根和子树,再把子树分为其根和子树,将每一个分好的子树都进行向下调整,直到叶子节点为止。

我们还拿上面数组为例:

int a[] = {536821};

这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成小堆

调整逻辑:在这里插入图片描述
代码实现:

//向下调整O(logN)
void AdJustDown(HPDataType* a, int n, int parent)
{
	//从左孩子开始,child为小孩子那个
	 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[parent], &a[child]);
			 parent = child;
			 child = child * 2 + 1;
		 }
		 else
		 {
			 break;
		 }
	 }
}

//初始化建堆
void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);

	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;
		//向下建堆 O(N)
	for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdJustDown(php->a, php->size, i);
	}
}

向上建堆和向下建堆的时间复杂度分别为O(N*logN),O(N)。
因为向下建堆的时间复杂度小,所以我们在实际工作中进行建堆一般是选择向下建堆

3.3.3 堆的初始化与销毁

在数据结构中,创建任何结构,都需要对其进行初始化和销毁。

代码实现:

//堆初始化
void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = php->size = 0;
}

//堆销毁
void HPDestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}
3.3.4 堆的插入(压栈)

插入一个数到数组的尾上,再进行向上调整算法,直到满足堆。
代码实现:

//向上调整 O(logN)
void AdJustUP(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;

	//while (1)严格来说不行
	while(child>0)
	{
		if (a[child] < a[parent])//小堆<,大堆>
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//压栈 O(logN)
void HPPush(HP* 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, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size] = x;
	php->size++;

	//数据尾插向上调整
	AdJustUP(php->a, php->size-1);
}

3.3.5 取堆顶的数据

在建好的堆中返回其根部数据。
代码实现:

//返回根数据
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size);

	return php->a[0];
}

3.3.6 堆的删除

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

代码实现:

//向下调整O(logN)
void AdJustDown(HPDataType* a, int n, int parent)
{
	//从左孩子开始,child为小孩子那个
	 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[parent], &a[child]);
			 parent = child;
			 child = child * 2 + 1;
		 }
		 else
		 {
			 break;
		 }
	 }
}

//删除根数据O(logN)
void HPPop(HP* php)
{
	assert(php);
	assert(php->size);

	//将根数据与最后一个子叶交换,再删除最后一个数据
	Swap(&php->a[0], &php->a[php->size-1]);
	php->size--;

	//向下调整
	AdJustDown(php->a, php->size, 0);
}
3.3.7 堆的数据个数

代码实现:

int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}
3.3.8 堆的判空

代码实现:

//判断堆是否为空
bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

二、堆的完整实现代码

Heap.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

typedef int HPDataType;

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

//小堆

//堆初始化
void HPInit(HP* php);
//初始化建堆
void HPInitArray(HP* php, HPDataType* a, int n);

//堆销毁
void HPDestory(HP* php);

//压栈
void HPPush(HP* php, HPDataType x);

//返回根数据
HPDataType HPTop(HP* php);

//删除根数据
void HPPop(HP* php);

//堆的数据个数
int HPSize(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 HPDestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}

void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

//向上调整 O(logN)
void AdJustUP(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;

	//while (1)严格来说不行
	while(child>0)
	{
		if (a[child] < a[parent])//小堆<,大堆>
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//压栈 O(logN)
void HPPush(HP* 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, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size] = x;
	php->size++;

	//数据尾插向上调整
	AdJustUP(php->a, php->size-1);
}

//返回根数据
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size);

	return php->a[0];
}

//向下调整O(logN)
void AdJustDown(HPDataType* a, int n, int parent)
{
	//从左孩子开始,child为小孩子那个
	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[parent], &a[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//删除根数据O(logN)
void HPPop(HP* php)
{
	assert(php);
	assert(php->size);

	//将根数据与最后一个子叶交换,再删除最后一个数据
	Swap(&php->a[0], &php->a[php->size-1]);
	php->size--;

	//向下调整
	AdJustDown(php->a, php->size, 0);
}

int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

//判断堆是否为空
bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

//初始化建堆
void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);

	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;

	//向上建堆 O(N*logN)
	/*for (int i = 1; i < php->size; i++)
	{
		AdjustUp(php->a, i);
	}*/

	//向下建堆 O(N)
	for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdJustDown(php->a, php->size, i);
	}
}

//建堆排序

//排序
// 升序
//小堆时间复杂度太大O(N^2),用大堆进行排序O(N*logN)
//大堆

//升序  大堆 O(N*logN)
//降序  小堆 O(N*logN)
void HeapSort(HPDataType* a, int n)
{
	//根据数组直接建堆 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdJustDown(a, n, i);
	}

	//交换根和尾的位置,删除尾,再向上调整 O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdJustDown(a, end, 0);
		--end;
	}
}

三、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤
在这里插入图片描述

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

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

相关文章

【系统架构师】-第18章-安全架构设计

(1)信息泄露&#xff1a;信息被泄露或透露给某个非授权的实体。 (2)破坏信息的完整性&#xff1a;数据被非授权地进行增删、修改或破坏而受到损失。 (3)拒绝服务&#xff1a;对信息或其他资源的合法访问被无条件地阻止。 (4)非法使用(非授权访问):某一资源被某个非授权的人或…

深度学习500问——Chapter05: 卷积神经网络(CNN)(2)

文章目录 5.6 有哪些池化方法 5.7 1x1卷积作用 5.8 卷积层和池化层有什么区别 5.9 卷积核是否一定越大越好 5.10 每层卷积是否只能用一种尺寸的卷积核 5.11 怎样才能减少卷积层参数量 5.12 在进行卷积操作时&#xff0c;必须同时考虑通道和区域吗 5.13 采用宽卷积的好处有什么 …

Linux部署Sonarqube+Gogs+Jenkins(一)

Linux部署SonarqubeGogsJenkins 一、1.Linux安装JDK11环境1. 本地进行上传2. 进入到/usr/java目录&#xff0c;并且进行解压3. 配置文件/etc/profile&#xff0c;配置环境变量4.让对应的配置文件生效5. 验证 二、Linux安装Python环境三、Linux安装Jenkins环境1、/usr目录下创建…

Redis 的慢日志

Redis 的慢日志 Redis 的慢日志&#xff08;Slow Log&#xff09;是用于记录执行时间超过预设阈值的命令请求的系统。慢日志可以帮助运维人员和开发人员识别潜在的性能瓶颈&#xff0c;定位那些可能导致 Redis 性能下降或响应延迟的慢查询。以下是 Redis 慢日志的相关细节&…

自定义类型(二)结构体位段,联合体,枚举

这周一时兴起&#xff0c;想写两篇文章来拿个卷吧&#xff0c;今天也是又来写一篇博客了&#xff0c;也是该结束自定义类型的学习与巩固了。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留下足迹。 为今天努力的自己打个卡&#xff0c;留个痕迹吧 2024.03.30 小闭…

GitHub文件克隆到本地(GitHub desktop快速上手版)

使用 GitHub Desktop 轻松地克隆 GitHub 上的项目。 打开 GitHub Desktop 应用程序。 在菜单栏中&#xff0c;单击“文件”&#xff0c;然后选择“克隆存储库”。 在弹出的窗口中&#xff0c;选择要克隆的存储库&#xff1a; 单击与要克隆的仓库位置对应的选项卡。或者&#…

回溯算法|216.组合总和III

力扣题目链接 class Solution { private:vector<vector<int>> result; // 存放结果集vector<int> path; // 符合条件的结果// targetSum&#xff1a;目标和&#xff0c;也就是题目中的n。// k&#xff1a;题目中要求k个数的集合。// sum&#xff1a;已经收集…

【Quixel Mixer】简单介绍

一、下载 官网下载地址&#xff1a;Quixel Mixer - All-in-one texturing & material creation tool 下载好之后双击exe来安装 等待安装完成 下载后打开&#xff0c;新建一个工程和Mix 二、界面介绍 我们先将软件界面分为如下3个部分 1号区域为菜单栏 2号区域介绍 2号…

【Linux】POSIX信号量{基于环形队列的PC模型/理解信号量的出现/参考代码}

文章目录 1.POSIX信号量1.1介绍1.2接口 2.基于环形队列的PC模型2.1环形队列常用计算2.2如何设计&#xff1f;2.3如何实现&#xff1f; 3.细节处理3.1空间资源和数据资源3.2push/pop3.3理解信号量的出现1.回顾基于阻塞队列的PC模型中条件变量的使用2.如何理解信号量的投入使用&a…

数据结构:链表的双指针技巧

文章目录 一、链表相交问题二、单链表判环问题三、回文链表四、重排链表结点 初学双指针的同学&#xff0c;请先弄懂删除链表的倒数第 N 个结点。 并且在学习这一节时&#xff0c;不要将思维固化&#xff0c;认为只能这样做&#xff0c;这里的做法只是技巧。 一、链表相交问题 …

报错:ImportError: cannot import name ‘imread‘ from ‘scipy.misc‘

报错内容&#xff1a; 问题代码通常是导入scipy库的版本出现了问题。 解决方法&#xff1a; 方法一&#xff1a; scipy版本还原到1.2.0 pip install scipy1.2.0 方法二&#xff1a; 使用from imageio import imread进行替换from scipy.misc import imread 使用imageio库同…

预处理详解(二)-- 条件编译 - 头文件包含 - ##和#运算符

目录 一.##和#运算符1.#运算符&#xff08;字符串化&#xff09;2.##运算符&#xff08;粘合符&#xff09; 二.条件编译&#xff08;很重要&#xff09;三.命名约定1.宏名的命名2.函数的命名 四.#undef(用于移除一个宏定义)五.命名行约定六.头文件被包含的方式1.本地文件包含2…

推特Twitter有直播功能吗?如何用Twitter直播?

现在各大直播平台已经成为社交媒体营销的一种重要渠道&#xff0c;它让品牌能够即时地与全球受众进行互动。据统计&#xff0c;直播市场正在迅速增长&#xff0c;预计到2028年将达到2230亿美元的规模。在这个不断扩张的市场中&#xff0c;许多社交媒体平台如YouTube、Facebook、…

消息队列的七种经典应用场景

在笔者心中&#xff0c;消息队列&#xff0c;缓存&#xff0c;分库分表是高并发解决方案三剑客。 在职业生涯中&#xff0c;笔者曾经使用过 ActiveMQ 、RabbitMQ 、Kafka 、RocketMQ 这些知名的消息队列 。 这篇文章&#xff0c;笔者结合自己的真实经历&#xff0c;和大家分享…

在ROS上快速验证PID算法

在ROS上快速验证PID算法 前言 最近有在外面出差授课的工作任务&#xff0c;其中有一个环节是给大家讲述PID相关的内容&#xff0c;在制作相关PPT的时候查询了很多资料&#xff0c;但是写着写着突然意识到一个问题&#xff0c;PID已经在控制专业学习过程以及工程开发时间中那么…

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…

【YOLOv5改进系列(9)】高效涨点----使用CAM(上下文增强模块)替换掉yolov5中的SPPF模块

文章目录 &#x1f680;&#x1f680;&#x1f680;前言一、1️⃣ CAM模块详细介绍二、2️⃣CAM模块的三种融合模式三、3️⃣如何添加CAM模块3.1 &#x1f393; 添加CAM模块代码3.2 ✨添加yolov5s_CAM.yaml文件3.3 ⭐️修改yolo.py文相关文件 四、4️⃣实验结果4.1 &#x1f39…

HTB devvortex靶机记录

做这个靶机的师傅们我先提一句&#xff0c;不知道是否是因为网速还是其他因素影响&#xff0c;登录后台管理后&#xff0c;有大概率会被其他人挤下去&#xff0c;所以做这道题的师傅可以考虑在没人的时候去做。 打开靶场以后老规矩nmap扫一遍 这里爆出了80端口和22端口&#xf…

解决Veeam做Replication复制或备份任务并发数量少问题

Veeam执行replication复制或者备份任务时&#xff0c;一直都只有两个任务并发在跑&#xff0c;其他同时间任务只能等待前两个任务处理完才可以开始。 解决方法&#xff1a; 进入Veeam-Bacup Infrastructure-Backup Proxies&#xff0c;可以看到VMware Backup Proxy&#xff0…

【并发】第二篇 ThreadLocal详解

导航 一. ThreadLocal 简介二. ThreadLocal 源码解析1. get2. set3 .remove4. initialValue三. ThreadLocalMap 源码分析1. 构造方法2. getEntry()3. set()4. resize()5. expungeStaleEntries()6. cleanSomeSlots()7. nextIndex()8. remove()9. 总结ThreadLocalMap四. 内存泄漏…