数据结构之二叉树的超详细讲解(2)--(堆的概念和结构的实现,堆排序和堆排序的应用)

news2024/11/15 17:18:00

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

数据结构之二叉树的超详细讲解(2)--(堆的概念和结构的实现,堆排序和堆排序的应用)

收录于专栏【数据结构初阶】
本专栏旨在分享学习数据结构学习的一点学习笔记,欢迎大家在评论区交流讨论💌

之前发布过数据结构之二叉树的超详细讲解(1)--(树和二叉树的概念和结构) ,今天重点讲解堆的概念和结构的实现,堆排序和堆排序的应用,感兴趣的宝子们赶紧点赞收藏起来吧!💓💓💓

目录

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

1.1二叉树的顺序结构

1.2 堆的概念及结构 

1.3堆算法的实现

1.3.1堆结构的实现

1.3.2堆操作函数 

1.3.2.1堆的初始化

1.3.2.2堆的销毁

1.3.2.3堆顶和堆尾元素的交换

1.3.2.4堆的调整算法

1.3.2.4.1向上调整建堆(以小根堆为例)

1.3.2.4.2向下调整建堆(以小根堆为例)

1.3.2.5数据进堆

1.3.2.6堆顶元素出堆

1.3.2.7输出堆顶元素

1.3.2.8堆的判空

练习:

2.堆排序

2.1向上调整建堆

2.1.1小根堆

2.1.2大顶堆

2.2向下调整建堆

2.2.1小根堆

2.2.2大根堆 

2.3堆排序的完整代码参考:

2.4建堆的时间复杂度 

2.4.1向下调整建堆的时间复杂度: 

2.4.1向上调整建堆的时间复杂度: 

2.5堆排序的应用-- TOP-K问题

方法一:

方法二:

 参考代码:

Heap.h:

Heap.c:

text.c:


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

1.1二叉树的顺序结构

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

1.2 堆的概念及结构 

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

这里运用了二叉树性质五:

堆的性质:

堆中某个结点的值总是不大于或不小于其父结点的值;

堆总是一棵完全二叉树。 

如下图所示: 

1.3堆算法的实现

1.3.1堆结构的实现

之前说到过,堆就是完全二叉树, 把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,所以我们这里使用动态数组的方式建堆,类似于使用动态数组构建顺序表

顺序表的构建和基本操作-CSDN博客

typedef int HPDataType;

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

 a就是我们的一个动态数组

size是我们堆中的元素个数

capacity是我们堆的容量,方便后面动态开辟空间

HPDataType方便后面我们进行类型的修改,比如我们不需要整形了,而是char类型,直接修改HPDataType就可以了

1.3.2堆操作函数 

//堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调正算法
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);
//堆顶元素出堆
void HPPop(HP* php);
//输出队顶元素
HPDataType HPTop(HP* php);
//堆的判空
bool HPEmpty(HP* php);
1.3.2.1堆的初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}
1.3.2.2堆的销毁
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

注意:

free释放掉a之后,还需要将a置为NULL 

1.3.2.3堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

为后面的调整算法算法埋下铺垫 

1.3.2.4堆的调整算法
1.3.2.4.1向上调整建堆(以小根堆为例)

如下图所示(以小根堆为例):

我们将新插入的数据不断的与它的parent节点进行比较 

代码展示:

void AdjustUp(HPDataType* a, int child)
{
	// 初始条件
	// 中间过程
	// 结束条件
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

注意:

根据我们之前学过的二叉树的性质,左child可以通过(child-1)/2和右孩子(child-2)/2来找到它的母亲节点,因为编译器的向下取整,所以我们直接使用int parent = (child - 1) / 2;来解决,不需要判断左右孩子

如下图 :

这里我们的循环停止条件有两种:

第一种:while (parent >= 0) 

第一种需要注意:当我们的parent = 0时,假设当时child还是比parent小,child  = parent,parent = (child - 1) / 2;还是会进入下一次循环,此时child = parent = 0,由于存在if的判断循环会结束,不会造成死循环现象,属于歪打正着

第二种就严谨很多:

第二种采用child作为循环条件的判断,当我们的child大于0,就会一直进行交换直到child等于0时,即为堆顶元素时,循环停止

1.3.2.4.2向下调整建堆(以小根堆为例)


 

注意:

向下调整建堆需要以27为根的左右子树都满足小堆的性质,只有根节点不满足.因此只需要将根节点往下调,往下调时需要与最小的那个值进行比较

代码展示:

void AdjustDown(HPDataType* a, int n, int parent)
{
	// 先假设左孩子小
	int child = parent * 2 + 1;

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

这里我们使用假设法,先假设左孩子小,然后进入循环,进入循环后进行判断,因为我们需要将较小的孩子与我们的parent进行交换,如果它本来就小,那就直接跳过,然后交换parent和child,如图所示:

1.3.2.5数据进堆

这里需要注意,数据进堆时,我们需要对进堆的数据进行调整,这样我们进堆结束后,即建堆完毕

代码展示:

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);
}
1.3.2.6堆顶元素出堆
void HPPop(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);
}

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。这样我们向下调整时两边都是小根堆,满足向下调整的条件

1.3.2.7输出堆顶元素
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}
1.3.2.8堆的判空
bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

练习:

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]

解析:

第一题:我们上面说到过,堆其实就是完全二叉树,所以我们直接构建完全二叉树:

只有A选项满足大堆的需求

第二题:

第三题:

只有c选项满足大顶堆的要求

第四题:

删除后,堆的调整如下:

2.堆排序

2.1向上调整建堆

2.1.1小根堆

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

 对每一次插入堆中的数据进行调整建堆

测试数据:

    int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 }; 

	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		//a[i++] = HPTop(&hp);
		HPPop(&hp);
	}
	printf("\n");

根据小根堆的性质:它的堆顶元素一定是数组中最小的数据,我们将堆顶数据输出,在重新建堆,直到堆中没有数据,这样就可以实现堆排序 

输出结果:

2.1.2大顶堆

建立大顶堆只需要改变我们之前写的向上向下调整代码:

向上调整代码:

void AdjustUp(HPDataType* a, int child)
{
	// 初始条件
	// 中间过程
	// 结束条件
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		//如果孩子比parent大
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

再向上调整代码中,我们只需要将if的判断改为>就可以了 

向下调整代码:

void AdjustDown(HPDataType* a, int n, int parent)
{
	// 先假设左孩子小
	int child = parent * 2 + 1;

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

 再向下调整代码中,我们需要调整两处,一个是我们需要找大的那个孩子,将<改为>,还有我们交换的条件需要孩子比母亲大,也是将<改为>

测试数据和代码不变:

所以你只需要会小根堆,大顶堆只需要改变三个地方 

2.2向下调整建堆

再实际应用中,我们建堆并不会使用向上调整建堆,因为时间复杂度不够低,但向上调整建堆更容易理解,大家可以根据自己的情况进行选择,这里我还是推荐向下调整建堆

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

 

我们这里第一个非叶子节点即最后一个节点的母亲节点:

也就是(n - 1 - 1) / 2,如图所示:

2.2.1小根堆

测试数据    int a[] = { 4,2,8,1,5,6,9,7,2,7,9 };

2.2.2大根堆 

数据和小根堆一样:

2.3堆排序的完整代码参考:

void HeapSort(int* a, int n)
{

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		printf("%d ", a[0]);
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}



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

2.4建堆的时间复杂度 

 我们上面说到过,向下建堆的时间复杂度是优于向上建堆的,这里我们具体分析一下:

2.4.1向下调整建堆的时间复杂度: 

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

这里我们是向下调整建堆,是从第一个非叶子节点开始一直到第一个节点的向下调整,如下图所示: 

根据上面的图,我们可以很简单的得到下面的等式: 

再根据我们高中学过的错位相减法,进一步化简: 

因此:向下建堆的时间复杂度为O(N)。 

2.4.1向上调整建堆的时间复杂度: 

因此向上调整建堆的时间复杂度为N*logN 

总结:

 向下调整:节点数量少的层*调整次数多的层 

 向上调整:节点数量多的层*调整次数多的层

 向下建堆的时间复杂度为O(N)。 

上调整建堆的时间复杂度为N*logN 

2.5堆排序的应用-- TOP-K问题

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

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

再比如:大家都玩过王者荣耀吧,金标或者国标都需要取前100名玩家,再几亿玩家中找出战力最高的100名玩家,如果你用快排的话,电脑估计要转冒烟了,但TOP-K可以很好的解决这个问题

方法一:

建立一个N个数的大堆,TOP K次 

这个方法是可行的,不过不够完美,因为再TOP K问题中,N往往是很大的,这样你建堆的一个内存就很大,算法的空间损耗会很大 

方法二:

用前K个数建一个小堆

 

 建堆的时间复杂度为O(K),然后还进行了(N-K)比较,所以总的时间复杂度为O(K + (N-K)*log(K)),假设是最坏的情况,每一次比较都需要向下调整,因为K是远小于N的,所以K,logK可以忽略不记,总时间复杂度为O(N),也就是上亿的数据,也能再秒之内完成 

测试方法二:

创建数据:

void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

输出结果:

创造一百完个随机数据

TOP-K求解:

void TestHeap3()
{
	int k;
	printf("请输入k>:");
	scanf("%d", &k);
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读取文件中前k个数
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	// 建K个数的小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	// 读取剩下的N-K个数
	int x = 0;
	while (fscanf(fout, "%d", &x) > 0)
	{
		if (x > kminheap[0])
		{
			kminheap[0] = x;
			AdjustDown(kminheap, k, 0);
		}
	}

	printf("最大前%d个数:", k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}

手动更改十个数据大于10000000的数,查看结果:

 输出结果:

 参考代码:

Heap.h:

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


typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
//堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调正算法
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);
//堆顶元素出堆
void HPPop(HP* php);
//输出队顶元素
HPDataType HPTop(HP* php);
//堆的判空
bool HPEmpty(HP* php);

Heap.c:

#define _CRT_SECURE_NO_WARNINGS 1


#include"Heap.h"

void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

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

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	// 初始条件
	// 中间过程
	// 结束条件
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		//如果孩子比parent大
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

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);
}

void AdjustDown(HPDataType* a, int n, int parent)
{
	// 先假设左孩子小
	int child = parent * 2 + 1;

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

// logN
void HPPop(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);
}

HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

text.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include <time.h>
#include"Heap.h"

void TestHeap1()
{
	int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
	HP hp;
	HPInit(&hp);
	for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}

	int i = 0;
	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		//a[i++] = HPTop(&hp);
		HPPop(&hp);
	}
	printf("\n");

	// 找出最大的前k个
	/*int k = 0;
	scanf("%d", &k);
	while (k--)
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}
	printf("\n");*/

	HPDestroy(&hp);
}

// 堆排序    O(N*logN)
// 冒泡排序  O(N^2) 
void HeapSort(int* a, int n)
{

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		printf("%d ", a[0]);
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}


void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand((unsigned int)time(NULL));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void TestHeap3()
{
	int k;
	printf("请输入k>:");
	scanf("%d", &k);
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读取文件中前k个数
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	// 建K个数的小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	// 读取剩下的N-K个数
	int x = 0;
	while (fscanf(fout, "%d", &x) > 0)
	{
		if (x > kminheap[0])
		{
			kminheap[0] = x;
			AdjustDown(kminheap, k, 0);
		}
	}

	printf("最大前%d个数:", k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}




int main()
{

	
	//CreateNDate();

	TestHeap3();

	return 0;
}

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

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

相关文章

python从0开始学习(十二)

目录 前言 1、字符串的常用操作 2、字符串的格式化 2.1 格式化字符串的详细格式&#xff08;针对format形式&#xff09; ​编辑 总结 前言 上一篇文章我们讲解了两道关于组合数据类型的题目&#xff0c;本篇文章我们将学习新的章节&#xff0c;学习字符串及正则表达式。 …

Gradle和Maven项目解决Spring Boot Configuration Annotation Processor not configured警告

问题描述 写了一个配置类,加了注解@ConfigurationProperties(prefix = “xxx”) 后一直报警告:Spring Boot Configuration Annotation Processor not configured 意思是 Spring boot 未配置注解处理器 解决过程 出现这个问题后,百度查了解决方式 1.maven项目 maven项目是…

logback 配置

https://zhuanlan.zhihu.com/p/673142694 配置结构 root 在 Logback 配置文件中&#xff0c; 元素用于配置根 Logger&#xff0c;它是整个日志系统的根节点。根 Logger 拥有最高级别&#xff0c;通常用于设置全局的日志级别和全局的 Appender&#xff08;附加器&#xff09;。…

[Algorithm][动态规划][路径问题][不同路径][不同路径Ⅱ][珠宝的最高价值]详细讲解

目录 1.不同路径1.题目链接2.算法原理详解3.代码实现 2.不同路径 II1.题目链接2.算法原理详解3.代码实现 3.珠宝的最高价值1.题目链接2.算法原理详解3.代码实现 1.不同路径 1.题目链接 不同路径 2.算法原理详解 思路&#xff1a; 确定状态表示 -> dp[i][j]的含义 走到dp[…

Mac 安装 git

文章目录 前言一、介绍二、下载三、验证四、配置五、Git常用命令六、git提交和撤销工作流程代码提交和提交同步代码撤销和撤销同步 FAQ1.homebrew 下载解决方法一&#xff08;强烈推荐&#xff09;&#xff1a;解决方法二&#xff1a; 总结 前言 Git 是一个开源的分布式版本控…

JavaScript 中的 Range 和 Selection 对象

JavaScript 中的 Range 和 Selection 对象 前言 最近在做鼠标框选的需求&#xff0c;鼠标框选就需要用到 Range 和 Selection 对象。 Range 表示选择的区间范围&#xff0c;Selection 表示选择的文档内容。 下面就详细说下这两个对象 一、Range Range 接口表示一个包含节…

太速科技-FMC125-两路125Msps AD,两路160Msps DA FMC子卡

FMC125-两路125Msps AD&#xff0c;两路160Msps DA FMC子卡 一、板卡概述 板卡可实现2路14bit 125Msps AD 和2路16bit 160MspsDA功能&#xff0c;FMC LPC连接器用于扩展到xilinx用于模拟信号、中频信号采集&#xff0c;信号发出等应用。 二、性能指标 板卡功能 参…

Vue3 Uncaught SyntaxError: Unexpected token <‘ 错误参考解决方法

1.最近在做一个登录首页的动画效果,动画组件是用的网上类似csdn方式,但是本地引入完全没问题,打包正式环境,直接报错,动画直接不起作用. 关于vue2的解决方法: 1.检查引用的 JavaScript 文件是否正确&#xff1a;确认所有引用的外部 JavaScript 文件路径是否正确&#xff0c;可…

陪玩系统源码,高质量的陪玩系统源码,游戏陪玩APP源码开发,语音陪玩源码搭建,整合需求精准定位

如今越来越多的人看到了游戏行业的市场&#xff0c;作为最近几年出现的一个新兴产业&#xff0c;需求是巨大的&#xff0c;因此开发陪玩app源码&#xff0c;正好可以优化服务体验来整合该市场。 原生陪玩源码or混合开发陪玩源码 游戏陪玩APP源码&#xff0c;基本上都是原生的&…

Docker安装MongoDB(Linux版)

文章目录 前言一、Docker环境的准备1.安装依赖2.安装Docker 二、使用Docker安装MongoDB1.mongo版本选取2.拉取合适的镜像3.宿主机创建MongoDB需要挂载的文件夹4.第一次无认证创建mongo用户5.启动需要认证的mongo容器 问题汇总总结 前言 本文章主要介绍在Centos系统&#xff0c…

ROS | 用IMU实现航向锁定

基本原理&#xff1a; 引入速度控制模块&#xff1a; /cmd_vel 设置目标角度&#xff0c;计算偏移差值 然后消息传递在z轴移动的角度 代码实现&#xff1a; C&#xff1a; CPP文件还需要编辑CMAKE文件 Python: Python文件需要给于权限&#xff1a;chmod x imu_node.py

【Muduo】三大核心之Poller、EPollPoller

Poller 在Muduo中&#xff0c;Poller负责基于IO多路复用机制进行IO事件监听和处理的组件&#xff0c;作为EPollPoller的基类&#xff0c;为后者提供了与PollPoller统一的IO复用接口&#xff0c;并且声明了一个关键的创建派生类的成员函数&#xff1a; static Poller *newDefa…

NDIS小端口驱动(五)

在需要的时候&#xff0c;我们也许需要NDIS微型端口程序信息&#xff0c;下面会从多个方面来讨论如何查询NDIS微型端口驱动。 查询无连接微型端口驱动程序 若要查询无连接微型端口驱动程序维护的 OID&#xff0c;绑定协议调用 NdisOidRequest 并传递 一个NDIS_OID_REQUEST 结…

OSPF多区域组网实验(华为)

思科设备参考&#xff1a;OSPF多区域组网实验&#xff08;思科&#xff09; 技术简介 OSPF多区域功能通过划分网络为多个逻辑区域来提高网络的可扩展性和管理性能。每个区域内部运行独立的SPF计算&#xff0c;而区域之间通过区域边界路由器进行路由信息交换。这种划分策略适用…

线性代数(二)

1.标量 标量也叫0D张量&#xff0c;一个标量就是一个数&#xff0c;它只有大小&#xff0c;没有方向。 import torch x torch.Tensor(3) print(x)2.向量 向量也叫1D张量。向量只有一个轴&#xff0c;沿着行的方向&#xff0c;或者沿着列的方向。向量一般指列向量。 import…

光伏储能EMS 风电智慧能量管理系统 -安科瑞王盼盼

安科瑞18721098782王盼盼 一&#xff1a;储能 EMS&#xff08;Energy Management System&#xff09; 储能 EMS&#xff1a;储能 EMS 是一个综合管理系统&#xff0c;用于整体管理和优化储能系统的运行。它基于电力系统的需求和需求响应&#xff0c;通过控制和协调储能设备的…

WPF中DataGrid实现多选框功能

1. 效果图 2. Model建立 public class RstModelCheck : ObservableObject {//为了显示Head1和Head2.而且View中绑定属性而非字段&#xff0c;否则不能显示。public string? Name { get; set; } public bool PlatenAll {get > _platenAll;set{SetProperty(ref _platenAl…

【代码随想录】【算法训练营】【第15天】 [102]二叉树的层序遍历 [226]翻转二叉树 [101]对称二叉树

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 15&#xff0c;一周中最困难的周三~ 题目详情 [102] 二叉树的层序遍历 题目描述 102 二叉树的层序遍历 解题思路 前提&#xff1a;二叉树的层级遍历 思路&#xff1a;利用队列的“先进先出…

C#利用WinForm实现可以查看指定目录文件下所有图片

目录 一、关于Winform 二、创建应用 三、功能实现 四、代码部分 一、关于Winform Windows 窗体是用于生成 Windows 桌面应用的 UI 框架。 它提供了一种基于 Visual Studio 中提供的可视化设计器创建桌面应用的高效方法。 利用视觉对象控件的拖放放置等功能&#xff0c;可…

信捷PLC 编程常用寄存器及编程技巧说明

最近在用信捷的PLC&#xff0c;分享下常用的寄存器和编程技巧说明。 技巧主要包括以下几个方面&#xff1a; 充分规划各个功能区&#xff1a;在编写程序时&#xff0c;需要充分规划各个功能区。 考虑伺服步进功能和气缸手动功能的应用&#xff0c;手动操作时&#xff0c;可…