数据结构初阶---二叉树---堆

news2024/12/16 20:18:19

一、树

1.树的概念

树是一种非线性的数据结构,由n(n≥0)个有限结点组成的一个有层次关系的集合。形状类似一棵倒挂的树,根朝上,分支向下。

根结点没有前驱结点,可以有n(n≥0)个后继结点。

其余结点被分为M个互不相交的集合,每一个集合都是一棵子树,每棵子树的根节点均有1个前驱结点,n(n≥0)个后继结点。因此树是递归定义的。

注:除根外的结点被分为互不相交的集合,如果相交,就不是树,而是图了。

子树之间存在交集,不属于树形结构,某结点存在多个父结点即它的前驱结点>1,也不属于树形结构。

2.与树有关的概念

节点的度:一个节点含有的子树的个数称为该节点的度; 
叶节点(终端节点):度为0的节点称为叶节点;(重要)
分支节点(非终端节点):度不为0的节点;
父节点(双亲结点):若一个节点含有子节点,则这个节点称为其子节点的父节点;(重要)
子节点(孩子节点):一个节点含有的子树的根节点称为该节点的子节点;(重要)
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;(重要)
树的高度或深度:树中节点的最大层次;(重要)
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;(重要)
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。                                                          森林:由m(m>0)棵互不相交的树的集合称为森林;

注:关于树的层次,一般而言是从1开始,即根所在层次为1,如果题目给定从0开始,那就从0开始,否则默认从1开始层次,因为对于无结点树而言,层次刚好是0,从0开始那么无结点树层次就为-1。

3.树的表示

如果我们使用数组来表示树,那么我们一般采用数组ArrayChild来存储孩子结点

#define N 6 //假设已知树的度为6
struct TreeNode
{
    TreeNodeTypeData val;
    struct TreeNode* ArrayChild[N];
};

但是,如果这样来表示树,那么相当于每一个分支结点,都有N个数组空间(N是树的度的值),但是树的分支结点不一定都有N个子结点,这样的表示就太过于浪费空间了。

如果用顺序表来表示树型结构,

struct TreeNode
{
    TreeNodeTypeData val;
    SeqList Childlist;
};

那么每个孩子结点都是一个顺序表,但是需要我们去构建一个顺序表,在C语言阶段也是非常费心费力的。

左孩子右兄弟表示法

树形结构的最优表示方法是左孩子右兄弟表示法

树形结构中创建两个指针,左孩子指针LeftChild与右兄弟指针RightBrother

struct TreeNode
{
    TreeNodeTypeData val;
    struct TreeNode* LeftChild;
    struct TreeNode* RightBrother;
};

我们通过每一次的根节点访问左孩子,就可以通过左孩子结点访问右兄弟指针找到该层次的其他结点;如果想访问下一层次,那么现在的左孩子结点访问它的左孩子结点,通过它的左孩子结点访问右兄弟指针能够找到其他所有结点。

上图中左孩子右兄弟表示形式里,未标识出来的左孩子LeftChild指针都应该指向NULL。

由此我们只需要访问上一层次某一棵子树的根节点的左孩子结点就可以遍历该层次属于该子树的所有结点。

如A的左孩子为B,A结点访问成员LeftChild得到B结点,B结点访问成员RightBrother就能够访问到同层次属于A孩子的C与D结点。如B的左孩子是E,那么通过B访问到E结点就可以遍历E、F结点。

4.树的实际运用

Linux树状目录结构。

Windows文件目录多盘形式---森林。

二、二叉树

1.概念

二叉树的结点的度均≤2(即每个结点的子结点不超过2个),同时二叉树区分左右子树(即区分左右结点),是有序树。

二叉树是特殊的树。

2.特殊的二叉树

①满二叉树

二叉树的所有结点的度都是2,那么就是满二叉树。对于满二叉树而言,每一层的结点数都达到了最大值。

假设满二叉树的深度为h,结点数为N,那么存在关系:2^h - 1 = N <==> h = log(N+1) (以2为底)

反过来说,如果一个二叉树的层数为K,它的结点数为2^K-1,那么这个二叉树就是满二叉树。

②完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。

对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树。

通俗来讲,除去最后一层的其它层结点数达到最大值,最后一层结点数不确定但是从左到右一定有序的二叉树就是完全二叉树。

对于完全二叉树来说,由于最后一层的结点有序,但是不确定最后一层结点个数,因此总的结点数N是一个范围,最小的时候最后一层只有1个结点,最大的时候是满二叉树。

那么N的范围为:[2^(h-1)-1+1 , 2^(h) -1]  化简--->[2^(h-1) , 2^(h) -1]

注意,对于完全二叉树来说,最后一层的结点一定是有序的!结点一定是从左至右增加的。

3.二叉树的性质

4.二叉树的存储结构

二叉树一般可以使用两种结构进行存储,顺序结构和链式结构。

①顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

顺序存储--->使用数组存储,只适用于完全二叉树。如果用数组存储的方式对任意二叉树,那么由于每个结点的度不同,会有许多数组空间的浪费,因此数组存储只适用于完全二叉树。

将完全二叉树的结点一层一层的存入数组中,

我们会发现,父子结点的下标是存在联系的。

②链式存储

对于不是完全二叉树和满二叉树的树形结构,我们采取链式结构来进行数据存储。

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面学到高阶数据结构如红黑树等会用到三叉链。

5.堆

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

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

简单的说,如果一个数组的内容满足堆的性质,那么堆就把这个数组数据看做一棵完全二叉树。

堆只分为小(根)堆和大(根)堆,小堆表示所有的父结点的数据都小于等于其子结点;大堆表示所有的父结点的数据都大于等于其子结点。除此之外都不是堆。

有序数组是不是堆?是,但是堆不一定是有序数组。

跟之前数据结构讲的栈不同于内存中的栈类似,

数据结构的堆是一种管理数据的结构,是完全二叉树;

而操作系统中的堆,指的是一个内存区域,这个区域中的空间可以通过malloc、calloc开辟、realloc扩容使用,最后free释放。

堆的物理结构是数组;逻辑结构是完全二叉树。

堆的意义:

1.堆排序---时间复杂度O(N*logN)

2.top k问题

对于堆的实现,我们想象自己操纵的是完全二叉树,但是我们实际上操纵的是数组。

数组实现堆

结构如下,我们来实现一下大(根)堆:

//堆是由读取规定的---与栈和队列一样,堆顶开始读取然后删除
//大堆---父节点始终大于等于子节点
#define HeapDataType int
typedef struct Heap
{
	HeapDataType* a;
	int size;
	int capacity;
}Heap;

接口函数

①初始化HeapInit

void HeapInit(Heap* php);

//初始化
void HeapInit(Heap* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}
②销毁HeapDestroy

void HeapDestroy(Heap* php);

//销毁
void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}
③插入HeapPush---O(logN)

void HeapPush(Heap* php, HeapDataType x);

在一个堆中插入一个新的数据x,那么我们就必须得将该数据移动到合适的位置,确保插入后新的数组仍然是一个大堆。

首先先写出扩容判断的代码以及插入的代码:

	//检测容量---不用单独开接口,因为只有一个插入
	if (php->size == php->capacity)
	{
		int NewCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HeapDataType* tmp = (HeapDataType*)realloc(php->a, sizeof(HeapDataType) * NewCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = NewCapacity;
	}
	//插入数据
	php->a[php->size] = x;
	php->size++;

那么就差最后一步,判断是否需要更换位置:

我们知道,对于大堆而言,父节点的数据始终大于等于子节点的数据,对于目前插入在最后一层的新结点而言,我们需要将其与它的父辈、祖辈,与它的祖先进行比较,如果新结点的数据大于父节点,那么就需要将新结点与父节点交换,然后接着向上比较:

需要交换数据,那么我们封装为一个Swap函数用来交换数据:

//交换函数
void Swap(HeapDataType* p1, HeapDataType* p2)
{
	HeapDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

所以对于插入的值的比较,我们需要通过该结点找到其父结点,根据前面学到的堆的父子结点关系,我们知道,子节点下标child,(child-1)/2就能够得到父节点下标parent

最后的比较要到什么时候终止呢?如果最慢的情况,插入的数据比原根节点都要大,那么就要换到A结点处--->因此循环的终止条件应为child == 0 。那么我们将判断和移动数据的过程封装成一个函数UpAdjust(向上调整)。

向上调整算法UpAdjust

void UpAdjust(Heap* php, int child);

    //向上调整算法
	UpAdjust(php, php->size - 1);

传入的child是插入结点的下标,由于前面插入后size自增1了,所以这个地方传进来的应该是size-1。

//向上调整算法---最坏情况调整深度h次,而完全二叉树的h与logN相关---因此时间复杂度logN
void UpAdjust(Heap* php, int child)
{
	assert(php);
	int parent = (child - 1) / 2;//父节点下标为子节点-1再除2
	while (child > 0)
	{
		if (php->a[parent] < php->a[child])
		{
			Swap(&php->a[parent], &php->a[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
			break;
	}
}
④删除HeapPop---O(logN)

void HeapPop(Heap* php);

在堆的实现中的删除操作,删除的是堆顶元素---即根结点元素。

但是我们又不能够直接进行覆盖操作,如果直接进行覆盖,一会导致原来的这个父子下标关系改变,摧毁了这个堆;二是数组的头删挪位覆盖时间复杂度为O(N)。

所以就有一种方法:我们先交换堆顶元素与最后一个元素,然后进行尾删--->就删除了堆顶元素。

对于数组结构而言,尾删是非常方便的,size自减1即可。然后这个时候,除了堆顶元素改变,其他位置上的元素均没有变化,其实类似于向上调整---我们实现一个向下调整算法DownAdjust。

//删除堆顶结点---时间复杂度O(logN)
void HeapPop(Heap* php)
{
	assert(php);
	Swap(&php->a[0], &php->a[php->size - 1]);//交换--->尾删原堆顶
	php->size--;//尾删原堆顶
	//向下调整算法---O(logN),要将交换上去的结点向下调整
	DownAdjust(php->a, php->size, 0);
}

DownAdjust向下调整算法内,我们将堆顶元素与它的子节点元素中较大的进行判断,如果堆顶元素大,那么符合大堆,不用进行交换元素,反之,堆顶元素小于较大的子结点元素,那么交换二者,将交换后的处于子节点处的该元素继续与这个位置上它的子结点的较大元素进行比较。

向下调整算法DownAdjust

 void DownAdjust(HeapDataType* a, int size, int parent);

//向下调整---时间复杂度O(logN)
void DownAdjust(HeapDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if ( child+1 < size && a[child] < a[child + 1])//小心越界
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * child + 1;
		}
		else
			break;
	}
}

为了防止if else判断两个子节点谁大的代码冗长,我们选择使用了假设判断法,先将其中的左节点下标给child,然后再if判断如果左节点数据小于右孩子结点,那么将右节点下标给child,实际上就与child自增1没有区别。 极端情况下,当我们判断两者大小时,可能出现child+1的越界访问的问题。 因此需要在if的条件中多加上一条child+1<size

找到更大的孩子节点、然后进行比较,这个整体是一个循环过程,因为交换到堆顶的数据最多需要进行深度h次交换,如果该数据足够小的话。循环进行条件就是chiild<size,即下标不能越界。在发现了该节点处的数据大于等于较大子节点的数据时,就满足了大堆性质,直接跳出循环即可。

⑤获取堆顶元素HeapTop

HeapDataType HeapTop(Heap* php);

//获取堆顶元素
HeapDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->size);

	return php->a[0];
}

有关于获取堆顶元素的接口,我们一般与删除堆顶元素联合起来使用,大堆的堆顶元素读取然后删除,执行k次,就能够找到这个堆中前k大的数据。

同时如果我们也可以这样获取降序的全部数据--->大堆就能够获取降序,小堆能够获取升序数据。

⑥获取堆有效元素个数HeapSize

int HeapSize(Heap* php);

//获取堆有效元素个数
int HeapSize(Heap* php)
{
	assert(php);

	return php->size;
}
⑦判断堆是否为空HeapEmpty

bool HeapEmpty(Heap* php);

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

	return php->size == 0;
}

堆的应用

1.堆排序问题

时间复杂度O(N*logN)。

上面通过数组实现了堆,那么给定一个数组,我们当然可以将数组的数据插入的堆结构中,进行向上调整,然后通过获取堆顶数据,删除堆顶向下调整的小套餐来打印排序后的数据,但是这样的操作有两个缺点:①需要一个完整的堆结构②原数组其实没有改变

那么实际上不需要如上操作进行排序,堆排序可以对该数组重新排序。

升序--->①建立大堆②进行排序

大堆父结点大于子节点,为什么会升序?===>这就涉及到伪删除,我们利用到堆接口函数删除堆顶元素的实现,本质是将尾部数据与堆顶数据互换再删除堆顶数据,那么其实此时堆顶数据在末尾,只要我们不删除,就能够实现每一次的伪删除会将最大的数据移动到数组尾部。每一次互换之后我们需要进行向下调整,当然调整不会包括互换后的尾结点数据。

降序--->①建立小堆②进行排序

降序为什么建立小堆同上。

下面演示升序:

①升序演示---建立大堆

建堆有两种方式:我们可以通过将每个数组数据从起始开始进行向上调整;也可以从尾结点的父结点开始进行向下调整。目的都是为了将数组数据排放成堆的形式。

从起始开始向上调整:时间复杂度O(N*logN)

	//数组建堆---
	//方法一:第一个数据成堆,其他数据依次入堆然后向上调整 O(N*logN)
	for (int i=1; i < n; i++)
	{
		UpAdjust(a, i);//数据依次向上调整成大堆
	}

从尾结点的父结点开始向下调整:时间复杂度O(N)

	//方法二:从最后一个结点的父结点开始,倒着对所有非叶子结点进行向下调整 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		DownAdjust(a, n, i);
	}

起始我们也能看出来时间复杂度差在哪里,对于方式二而言,最后一层结点数最多,但是对于方式二却不用对它们进行任何次数的调整,因此时间复杂度比方式一要小。

②升序演示---伪删除/选数

将堆顶与尾部数据交换,然后传入DownAdjust向下调整的end每次遍历减1。第一次交换后传入的size应该是减1之后的===>即下面代码的end = n-1。

	//伪删除---堆顶数据与尾部交换,size--,向下调整===>那么数组尾部就是最大数据 O(NlogN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		DownAdjust(a, end, 0);//依次向下调整
		end--;
	}

一个数据在向下调整最多层数h次,N个数据===>N*logN。

选数操作的时间复杂度O(N*logN)。 

推排序升序数组:

//堆排序
//堆排序是将给出的数组数据先排序成堆,然后再进行排序
//而不是将数据依次插入实现好的堆中
void HeapSort()//测试---堆排序
{
	int a[] = { 1,5,3,6,8,2,1,5,8 };
	int n = sizeof(a) / sizeof(a[0]);
	//数组建堆---
	//方法一:第一个数据成堆,其他数据依次入堆然后向上调整 O(N*logN)
	for (int i=1; i < n; i++)
	{
		UpAdjust(a, i);//数据依次向上调整成大堆
	}
	//方法二:从最后一个结点的父结点开始,倒着对所有非叶子结点进行向下调整 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		DownAdjust(a, n, i);
	}
	//伪删除---堆顶数据与尾部交换,size--,向下调整===>那么数组尾部就是最大数据 O(NlogN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		DownAdjust(a, end, 0);//依次向下调整
		end--;
	}
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
}

2.top k问题
思路一

前面接口函数中提到了top k问题的解决方法,我们可以通过将这些数据以大堆的形式全部插入到堆中,那么再循环k次来删除k次堆顶数据并获取前k个最大的数据。但是如果数据基数N非常大,那么我们需要为这个大堆开辟的空间是非常非常大的,举个例子,N=100亿--->100亿个整型--->约为400亿字节--->约为40G的内存大小,现如今普通人家的电脑内存大小,16-32G,无法完成,而且就算能够完成,也不适用。

思路二---最优解

在top k问题中,如果要在一个非常非常大的N个数据中,找出k个最大的数据,那么我们可以通过这样的办法实现:

创建一个k个数据的小堆,先将最开始的k个数据存入小堆中,然后在剩余的数据依次与小堆堆顶元素比较,如果大于堆顶元素,那么覆盖堆顶元素并向下调整,使其依然满足小堆。那么到最后,小堆中就是最大的k个数据了。这种方法是非常便捷的,时间复杂度为O(N*logk)即O(N),但是空间复杂度只有O(1),而思路一的空间复杂度达到了O(N),而且基本上难以实现。

时间复杂度O(N*logk)==O(N)。

那么通过最优解,就可以从N个数据中找k个最大的数据,假设我们在文件中保存1000000个随机值,然后通过堆来找出k个最大的值:

首先我们在文件中写入N=1000000个随机值:

//TopK问题---在N个数据中找到K个最大的数据
void CreatNData()//在文件中创建1000000个数据
{
	FILE* fin = fopen("data.txt", "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	int n = 10000000;
	srand(time(0));
	while (n--)
	{
		int x = (rand() + n) % 1000000;//数据都是小于1000000的
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

这里写入的数据模了1000000方便我们后续测试是否成功取出k个最大数据。

我们执行一次CreatNdatra函数,那么就能够创建这个data.txt文件并在其中写入1000000个数据。

由于我们需要取较大数据,那么就需要用小堆===>即原来适用于大堆的向上调整与向下调整算法需要进行细微的修改。

下面是小堆的向上向下调整算法(其实只需调整几个大于小于号)

//小堆的向上向下调整算法
//向上调整算法---最坏情况调整深度h次,而h大概率等于logN相关---因此时间复杂度logN
void UpAdjust_lowheap(HeapDataType* a, int child)
{
	int parent = (child - 1) / 2;//父节点下标为子节点-1再除2
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
			break;
	}
}
//向下调整---时间复杂度O(logN)---小堆
void DownAdjust_lowheap(HeapDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child] > a[child + 1])//小心越界
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * child + 1;
		}
		else
			break;
	}
}

写一个函数PrintTopK,解决topk问题:

void PrintTopK(FILE* file, int k);

创建一个minheap数组存储k个数据,VS不支持变长数组,那么我们使用malloc开辟k个数据大小的空间给minheap。

int* minheap = (int*)malloc(sizeof(int) * k);

第一步将该文件中的前k个数据存入小堆进行向上调整;

	//读前k个数据建小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		UpAdjust_lowheap(minheap, i);
	}

第二步将文件中剩余的N-k个数据依次遍历与堆顶数据比较,大于等于堆顶数据则替换堆顶数据然后进行向下调整使之依然成小堆。

	//剩下N-K个数据依次遍历与堆顶数据比较,大于等于堆顶数据进入堆
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)//先读到x中,若x大于等于堆顶,放入堆顶并向下调整
	{
		if (x >= minheap[0])
		{
			minheap[0] = x;
			DownAdjust_lowheap(minheap, k, 0);
		}
	}

fscanf读取结束返回EOF,那么循环条件可以直接写作如上图所示,将每次遍历的文件中的数据存入x中,x与堆顶数据进行判断,若大于等于堆顶数据,替换堆顶数据并向下调整。

注:scanf函数以及fscanf等函数,默认遇到空格或者换行\n终止。 

最后我们可以打印minheap来观察数据,记得最后需要释放堆中开辟的空间以及关闭文件。

	for(int i=0;i<k;i++)
	{
		printf("%d ",minheap[i]);
	}
	free(minheap);
	minheap = NULL;
	fclose(fout);

前面手动添加了取模1000000,我们可以通过手动修改data.txt中的5个数据使其大于1000000,再次执行程序来观察最大值的变化,从而判断程序是否正确无误。

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

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

相关文章

1. 机器学习基本知识(3)——机器学习的主要挑战

1.5 机器学习的主要挑战 1.5.1 训练数据不足 对于复杂问题而言&#xff0c;数据比算法更重要但中小型数据集仍然很普遍&#xff0c;获得额外的训练数据并不总是一件轻而易举或物美价廉的事情&#xff0c;所以暂时不要抛弃算法。 1.5.2 训练数据不具有代表性 采样偏差&#…

CentOS 上如何查看 SSH 服务使用的端口号?

我们知道&#xff0c;linux操作系统中的SSH默认情况下&#xff0c;端口是使用22&#xff0c;但是有些线上服务器并不是使用的默认端口&#xff0c;那么这个时候&#xff0c;我们应该如何快速知道SSH使用的哪个端口呢&#xff1f; 1、通过配置文件查看 cat /etc/ssh/sshd_confi…

【Java学习笔记】泛型

一、泛型的好处 好处&#xff1a;减少了类型转换的次数&#xff0c;提高了效率 二、泛型介绍 泛型&#xff1a;接受任何数据类型 的 数据类型 &#xff08;特别强调&#xff1a; E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间&#xff0c;就确定 E 是什么类型…

benchANT (Time Series: Devops) 榜单数据解读

近日&#xff0c;国际权威数据库性能测试榜单 benchANT 更新了 Time Series: Devops&#xff08;时序数据库&#xff09;场景排名&#xff0c;KaiwuDB 数据库在 xsmall 和 small 两类规格下的时序数据写入吞吐、查询吞吐、查询延迟、成本效益等多项指标刷新榜单原有数据纪录 &a…

多进程并发跑程序:pytest-xdist记录

多进程并发跑程序&#xff1a;pytest-xdist记录 pytest -s E:\testXdist\test_dandu.py pytest -s testXdist\test_dandu.py pytest -s &#xff1a;是按用例顺序依次跑用例 pytest -vs -n auto E:\testXdist\test_dandu.py pytest -vs -n auto&#xff0c;auto表示以全部进程…

网络层IP协议(TCP)

IP协议&#xff1a; 在了解IP协议之前&#xff0c;我们市面上看到的"路由器"其实就是工作在网络层。如下图&#xff1a; 那么网络层中的IP协议究竟是如何发送数据包的呢&#xff1f; IP报头&#xff1a; IP协议的报头是比较复杂的&#xff0c;作为程序猿只需要我们重…

前端传入Grule,后端保存到 .grl 文件中

前端传入Grule&#xff0c;后端保存到 .grl 文件中 通过简单的输入框&#xff0c;将Grule的部分拆解成 规则名称 规则描述 规则优先级 规则条件 规则逻辑Grule关键字 when Then 模拟了 if 判断的条件和逻辑部分 类似于 shell 和 ruby 之类的脚本语言&#xff0c;有 then 关键字…

vlan和vlanif

文章目录 1、为什么会有vlan的存在2、vlan(虚拟局域网)1、vlan原理1. 为什么这样划分了2、如何实现不同交换机相同的vlan实现互访呢3、最优化的解决方法&#xff0c;vlan不同交换机4、vlan标签和vlan数据帧 5、vlan实现2、基于vlan的划分方式1、基于接口的vlan划分方式2、基于m…

遗传算法与深度学习实战(27)——进化卷积神经网络

遗传算法与深度学习实战&#xff08;27&#xff09;——进化卷积神经网络 0. 前言1. 自定义交叉算子2. 自定义突变操作符3. 进化卷积神经网络小结系列链接 0. 前言 DEAP toolbox 中提供的标准遗传操作符对于自定义的网络架构基因序列来说是不够的。这是因为任何标准的交叉算子…

react-dnd 拖拽事件与输入框的文本选中冲突

问题描述 当我们使用拖拽库的时候&#xff0c;往往会遇到拖拽的一个元素他的子孙元素有输入框类型的dom节点&#xff0c;当拖拽的事件绑定在该元素身上时候&#xff0c;发现子孙的输入框不能进行文本选中了&#xff0c;会按住鼠标去选中文本的时候会触发拖拽 实际的效果&…

经典NLP案例 | 推文评论情绪分析:从数据预处理到模型构建的全面指南

NLP经典案例&#xff1a;推文评论情绪提取 项目背景 “My ridiculous dog is amazing.” [sentiment: positive] 由于所有推文每秒都在传播&#xff0c;很难判断特定推文背后的情绪是否会影响一家公司或一个人的品牌&#xff0c;因为它的病毒式传播&#xff08;积极&#xff0…

杨振宁大学物理视频中黄色的字,c#写程序去掉(原版改进,三)

上一节&#xff0c;我们分清了主次矛盾&#xff0c;并搞定了主要矛盾&#xff08;去掉黄色的字&#xff09;&#xff0c;这一节解决次要矛盾&#xff08;矩形色带&#xff09;。 我们的想法如图&#xff1a; 1&#xff0c;我们找到稳定黄色的最左边&#xff0c;最右边两点&…

第24周:文献阅读

目录 摘要 Abstract 一、现有问题 二、提出方法 三、创新点 模型结构创新 强化学习与GAN结合 属性特征与通顺性优化 四、方法论 生成对抗网络&#xff08;GAN&#xff09; 强化学习&#xff08;RL&#xff09; 模型组件 五、实验研究 数据集 数据预处理 评价指…

SQL server学习05-查询数据表中的数据(上)

目录 一&#xff0c;基本格式 1&#xff0c;简单的SQL查询语句 2&#xff0c;关键字TOP 3&#xff0c;关键字DISTINCT 二&#xff0c;模糊查询 1&#xff0c;通配符 三&#xff0c;对结果集排序 1&#xff0c;不含关键字DISTINCT 2&#xff0c;含关键字DISTINCT 3&…

【Azure 架构师学习笔记】- Azure Function (1) --环境搭建和背景介绍

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Function 】系列。 前言 随着无服务计算的兴起和大数据环境中的数据集成需求&#xff0c; 需要使用某些轻量级的服务&#xff0c;来实现一些简单操作。因此Azure Function就成了微软云上的一个必不可少的组成部分。 …

KeepAlive与RouterView缓存

参考 vue动态组件&#xff1c;Component&#xff1e;与&#xff1c;KeepAlive&#xff1e; KeepAlive官网介绍 缓存之keep-alive的理解和应用 Vue3Vite KeepAlive页面缓存问题 vue多级菜单(路由)导致缓存(keep-alive)失效 vue3 router-view keeperalive对于同一路径但路径…

Linux:进程(环境变量、程序地址空间)

目录 冯诺依曼体系结构 操作系统 设计操作系统的目的 操作系统的管理 进程 PCB fork 进程状态 进程状态查看 僵尸进程 孤儿进程 进程优先级 查看、修改进程优先级命令 竞争、独立、并行、并发 进程切换 活动队列和运行队列 活动队列 过期队列 active指针…

希迪智驾持续亏损8.2亿:毛利率下滑,冲刺“自动驾驶矿卡第一股”

《港湾商业观察》黄懿 近日&#xff0c;希迪智驾&#xff08;湖南&#xff09;股份有限公司&#xff08;下称“希迪智驾”&#xff09;向港交所主板递交上市申请&#xff0c;联席保荐人为中金公司、中信建投国际、中国平安资本&#xff08;香港&#xff09;。 资料显示&#…

Rust之抽空学习系列(三)—— 编程通用概念(中)

Rust之抽空学习系列&#xff08;三&#xff09;—— 编程通用概念&#xff08;中&#xff09; 1、变量&可变性 在Rust中&#xff0c;变量默认是不可变的 fn main() {let x 5;println!("x is {}", x); }使用let来声明一个变量&#xff0c;此时变量默认是不可变…

OpenCV中的识别图片颜色并绘制轮廓

一、实验原理 使用OpenCV库在图像中识别和绘制特定颜色&#xff08;黄色&#xff09;的轮廓 二、实验代码 import cv2 import numpy as np# 读取图片并调整大小 img cv2.imread(./color_1.png) img cv2.resize(img,(600,600))# 将图片从BGR颜色空间转换到HSV颜色空间 img_h…