数据结构初阶--二叉树的顺序结构之堆

news2024/11/18 3:29:49

目录

一.堆的概念及结构

1.1.堆的概念

1.2.堆的存储结构

二.堆的功能实现

2.1.堆的定义

2.2.堆的初始化

2.3.堆的销毁

2.4.堆的打印

2.5.堆的插入

向上调整算法

堆的插入

2.6.堆的删除

向下调整算法

堆的删除

2.7.堆的取堆顶元素

2.8.堆的判空

2.9.堆的求堆的大小

三.堆的创建

3.1.向上调整建堆

时间复杂度

3.2.向下调整建堆

时间复杂度

四.堆的应用

4.1.堆排序

步骤一:建堆

步骤二:排序

4.2.TOP-K问题


一.堆的概念及结构

1.1.堆的概念

若n个关键字序列L[1...n]满足下面某一条性质,则称为堆(Heap)

  1. 若满足:L(i)>=L(2i)且L(i)>=L(2i+1)(1<=i<=n/2)--大根堆(大顶堆)
  2. 若满足:L(i)<=L(2i)且L(i)<=L(2i+1)(1<=i<=n/2)--小根堆(小顶堆)

大根堆在逻辑视角上可以看成所有子树根>=左,右的完全二叉树。相应的小根堆也可以看成根<=左,右的完全二叉树。

堆的性质:

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

1.2.堆的存储结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。

二.堆的功能实现

2.1.堆的定义

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;//开辟一个动态数组a
	int size;//当前元素个数
	int capacity;//数组的最大容量
}HP;

定义一个struct来保存堆的信息,主要包含数组首元素的地址a,数组中当前元素个数size以及数组的最大容量capacity。堆的定义同顺序表的定义类似。

2.2.堆的初始化

void HeapInit(HP* php)
{
	//判空
	assert(php);

	//将数组首元素的地址位置空
	php->a = NULL;

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

在初始化堆之前,首先需要对传入的参数php进行断言判断其是否为空,然后将数组首元素的地址置为空NULL,最后再将size和capacity都初始化为0。

调试分析:

2.3.堆的销毁

void HeapDestory(HP* php)
{
	//判空
	assert(php);

	//释放
	free(php->a);
	php->a = NULL;

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

在销毁堆之前,首先需要对传入的参数php进行断言判断其是否为空,然后调用free函数释放数组首元素的地址,并将数组首元素的地址置为空NULL,最后再将size和capacity都置为0。

调试分析:

2.4.堆的打印

void HeapPrint(HP* php)
{
	//判空
	assert(php);

	//打印
	for (int i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

首先判断传入的参数php是否为空,然后进行for循环依次打印数组中的各个元素。

2.5.堆的插入

当在堆中插入新元素时,对于小根堆,新元素放到表尾,与父结点对比,若新元素比父结点更小,则将二者互换。新元素就这样一路向上调整,直到无法继续上升为止。

向上调整算法

当在堆的末尾插入一个新元素,而新插入的元素可能会破坏堆的性质,这时就要进行调整。以小堆为例,当新元素大于其对应的父结点,则满足堆的性质,无需调整;当新元素小于其对应的父结点,则不满足堆的性质,要进行调整。

调整规则:

若新插入的元素child小于其对应的父结点parent,则调用Swap函数,将二者进行交换,此时child来到父结点parent的位置,其对应的新的父结点的下标为(child-1)/2,然后将child继续与parent进行比较,依次往上执行,直到child大于其对应的父结点parent,则跳出循环。

循环判断条件为:child>0,这是考虑到最坏的情况,也就是当child一直小于其对应的父结点时,child经过最后一次交换来到根结点的位置时,此时堆的调整已经结束。

实现:

//交换数据
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 (child > 0)
	{
		if (a[child] < a[parent])//大堆:a[child]>a[parent]
		{
			//将父子结点进行交换
			Swap(&a[child], &a[parent]);

			//把父结点的下标赋值给孩子
			child = parent;

			//再去查找父结点的下标
			parent = (child - 1) / 2;
		}
		else
		{
			//若已构成堆,则直接跳出循环
			break;
		}
	}
}

堆的插入

void HeapPush(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, sizeof(HPDataType) * newCapacity);

		//判空
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}

		//将新开辟的内存空间的首地址tmp赋值给a
		php->a = tmp;
		//更新capacity
		php->capacity = newCapacity;
	}

	//插入
	//先将元素插入到堆的末尾,即最后一个孩子后面
	//插入之后如果堆的性质遭到了破坏,则将新插入结点顺着其双亲往上调整到合适的位置
	php->a[php->size] = x;//注意:size指向数组最后一个元素的下一个位置
	php->size++;

	//向上调整
	AdjustUp(php->a, php->size - 1);
}

在堆中插入元素之前,首先需要检查当前容量是否为空或者已满。若容量为空,则调用realloc函数开辟四个元素的内存空间,若容量已满,则调用realloc函数将内存空间开辟到原来的二倍,并将新开辟的内存空间的首地址tmp赋值给a,同时更新capacity。接着便可以插入元素,因为size是指向数组最后一个元素的下一个位置,所以先将新元素x插到下标为size的位置,然后再将size+1。最后再调用AdjustUp函数,进行向上调整。

调试分析:

运行结果:

2.6.堆的删除

堆的删除是删除堆顶的元素,将堆顶的元素与最后一个元素交换,然后删除数组最后一个元素,再进行向下调整。

向下调整算法

当对堆进行删除时,删除的往往是堆顶元素,删除之后可能会破坏堆的性质,这时就要进行调整。以小堆为例,在删除之前,首先将堆顶元素与最后一个元素交换,交换完之后再将最后一个元素删除。然后从根结点开始依次向下调整,直到把它调整为一个小堆。

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

调整规则:

首先选出根结点的左右孩子中较小的那一个,这里先假设左孩子最小,然后将左孩子与右孩子进行比较,若左孩子小于右孩子,则不变;若左孩子大于右孩子,则将右孩子设为最小。然后将最小的孩子与父结点进行比较,如果比父结点小,则交换,交换完之后,把孩子结点child所在的下标赋值给父结点parent,并让child指向新的父结点的左孩子,然后依次向下比较,直到调整到叶子结点的位置;如果比父结点大,则调整结束。

实现:

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

void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.选出左右孩子中小的那一个
	int child = parent * 2 + 1;//假设左孩子最小

	while (child < size)
	{
		//当右孩子存在且右孩子小于左孩子
		if (child + 1 < size && a[child + 1] < a[child])//大堆:a[child+1]>a[child]
		{
			++child;//则将右孩子置为child
		}

		//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束
		if (a[child] < a[parent])//大堆:a[child]>a[parent]
		{
			//交换数据
			Swap(&a[child], &a[parent]);

			//3.继续往下调,最多调整到叶子结点就结束
			//把孩子的下标赋值给父亲
			parent = child;

			//假设左孩子最小
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

堆的删除

void HeapPop(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);
}

在删除之前,首先需要判断数组是否为空,若为空则无法进行删除,若不为空则可以进行删除。然后调用Swap函数将数组的第一个待删除元素与数组的最后一个元素进行交换,并让size-1,删除最后一个元素。最后再调用函数AdjustDown,进行向下调整。

调试分析:

运行结果:

2.7.堆的取堆顶元素

HPDataType HeapTop(HP* php)
{
	//判空
	assert(php);

	//判断数组是否为空
	assert(php->size > 0);

	//根结点即为堆顶元素
	return php->a[0];
}

在取堆顶元素之前,首先要对数组进行判空操作,若数组为空则无法进行读取操作,若数组不为空则直接读取数组的首元素,数组的首元素也就是根结点,即为堆顶元素。

调试分析:

运行结果:

2.8.堆的判空

bool HeapEmpty(HP* php)
{
	//判空
	assert(php);

	//看size的大小是否为0
	return php->size == 0;
}

判断堆是否为空,只需判断size是否等于0,若size为0,则数组为空,即堆为空;若size不为0,则数组不为空,即堆不为空。

调试分析:

2.9.堆的求堆的大小

int HeapSize(HP* php)
{
	//判空
	assert(php);

	//size的大小即为数组的大小,也就是堆的大小
	return php->size;
}

求堆的大小,只需求数组中当前元素个数,也就是求size的大小。

调试分析:

三.堆的创建

3.1.向上调整建堆

向上调整建堆,实际上是模拟堆的插入过程。首先,将数组中的第一个元素看做是堆的根结点,然后将数组中的元素依次插入堆中,每插入一个元素,就调用函数AdjustUp向上调整一次,直到将所有的元素均插入堆中。

实现:

for (int i = 1; i < n; ++i)//从第一个位置插入
{
	AdjustUp(a, i);
}

时间复杂度

因为堆是一棵完全二叉树,而满二叉树又是一种特殊的完全二叉树,为了简化计算,我们不妨假设此处的堆是一棵满二叉树。

由上图得,对于高度为h的满二叉树构成的堆,最多进行向上调整的次数设为T(N),有:

综上,证得向上调整建堆的时间复杂度为O(N*logN)

3.2.向下调整建堆

首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,调用函数AdjustDown依次向下调整。每调整一次,则将i的值减1,让其来到倒数第二个非叶子结点的位置,重复上述操作,直到i来到根结点的位置。

实现:

for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2
{
	AdjustDown(a, n, i);
}

注意:

我们可以直接通过向上调整算法来建堆,但是我们不可以直接通过向下调整算法来建堆。因为向下调整算法的前提:左右子树必须是堆,才能调整。

时间复杂度

因为堆是一棵完全二叉树,而满二叉树又是一种特殊的完全二叉树,为了简化计算,我们不妨假设此处的堆是一棵满二叉树。

由上图得,对于高度为h的满二叉树构成的堆,最多进行向上调整的次数设为T(N),有:

综上,证得向上调整建堆的时间复杂度为O(N)。 

四.堆的应用

4.1.堆排序

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

  1. 建堆(升序建大堆,降序建小堆);
  2. 利用堆删除思想来进行排序 。

注意:

升序也可以建小堆,只是每次都要通过建堆的方式选出最小的元素。当进行第一次建堆选出最小的元素并放在数组起始位置时,剩余的元素关系就会发生错乱:原本的左孩子结点会变成新的根结点,右孩子结点会变成新的左孩子结点。然后将剩下的元素继续进行建堆,选出剩余元素中最小的元素并放入数组起始位置的下一个位置,重复上述操作,直到整个数组有序。整体时间复杂度为O(N^2),可见效率太低,没有使用到堆的优势。因此,升序要建大堆。

我们以升序建大堆为例:

步骤一:建堆

这里采用时间复杂度较低的向下建堆法来进行大根堆的建立。

实现:

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

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.选出左右孩子中小的那一个
	int child = parent * 2 + 1;//假设左孩子最小

	while (child < size)
	{
		//当右孩子存在且右孩子小于左孩子
		if (child + 1 < size && a[child + 1] > a[child])//大堆:a[child+1]>a[child]
		{
			++child;//则将右孩子置为child
		}

		//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束
		if (a[child] > a[parent])//大堆:a[child]>a[parent]
		{
			//交换数据
			Swap(&a[child], &a[parent]);

			//3.继续往下调,最多调整到叶子结点就结束
			//把孩子的下标赋值给父亲
			parent = child;

			//假设左孩子最小
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	//建堆
	//建堆方式二:向下调整
	//向下调整算法的左右子树必须是堆,因此不能使用该方法直接建堆
	//时间复杂度:O(N)
	//首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,依次向下调整
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2
	{
		AdjustDown(a, n, i);
	}
}

int main()
{
	int a[] = { 27,15,19,18,28,34,65,49,25,37 };

	HeapSort(a, sizeof(a) / sizeof(a[0]));

	return 0;
}

调试分析:

建堆前:                                                                      

建堆后:

步骤二:排序

这里用到堆的删除思想。先交换数组的首尾元素,此时尾结点中的元素为堆中的最大值。然后将堆的最后一个元素排除在外,并继续从根结点开始,对堆进行向下调整。重复上述操作,直到堆中仅剩一个元素为止。

实现:

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

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.选出左右孩子中小的那一个
	int child = parent * 2 + 1;//假设左孩子最小

	while (child < size)
	{
		//当右孩子存在且右孩子小于左孩子
		if (child + 1 < size && a[child + 1] > a[child])//大堆:a[child+1]>a[child]
		{
			++child;//则将右孩子置为child
		}

		//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束
		if (a[child] > a[parent])//大堆:a[child]>a[parent]
		{
			//交换数据
			Swap(&a[child], &a[parent]);

			//3.继续往下调,最多调整到叶子结点就结束
			//把孩子的下标赋值给父亲
			parent = child;

			//假设左孩子最小
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	//建堆
	//建堆方式二:向下调整
	//向下调整算法的左右子树必须是堆,因此不能使用该方法直接建堆
	//时间复杂度:O(N)
	//首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,依次向下调整
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2
	{
		AdjustDown(a, n, i);
	}


	//排序
	//时间复杂度:O(N*logN),其中N为元素个数,logN为向上调整的次数,也即树的高度
	int end = n - 1;
	while (end > 0)
	{
		//将第一个结点与最后一个结点交换
		Swap(&a[0], &a[end]);

		//向下调整选出次大的数
		AdjustDown(a, end, 0);
		--end;
	}
}

int main()
{
	int a[] = { 27,15,19,18,28,34,65,49,25,37 };

	HeapSort(a, sizeof(a) / sizeof(a[0]));

	return 0;
}

调试分析:

排序前:

排序后:

小结:

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

建堆的时间复杂度为:O(N),排序的时间复杂度为:O(N*logN)。取影响结果较大的一个,也就是O(N*logN)。所以堆排序的时间复杂度为O(N*logN)。

4.2.TOP-K问题

TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于TOP-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

法一:

堆排序,采用时间复杂度最低的堆排序,时间复杂度为O(N*logN);

法二:

首先建立N个数的大根堆,然后Top/Pop k次,时间复杂度为O(N+k*logN);

注意:上述两种方法在数据量非常大时,是不太可取的。

法三:

假设N非常大,比如N是100亿,而K比较小,假如k是100,如何求解?

首先,将数据集合中前k个数建立小根堆,时间复杂度:O(k);
然后,将剩下的N-k个元素依次和堆顶元素进行比较,如果比堆顶元素大,就替换堆顶元素,并进行向下调整;
待N-k个元素依次和堆顶元素比较完之后,堆中剩余的k个元素就是所求的最大的前k个元素,时间复杂度:O((N-k)*logk)。

注意:法三较于前两种方法有很高的空间效率。

实现:

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

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.选出左右孩子中小的那一个
	int child = parent * 2 + 1;//假设左孩子最小

	while (child < size)
	{
		//当右孩子存在且右孩子小于左孩子
		if (child + 1 < size && a[child + 1] < a[child])//大堆:a[child+1]>a[child]
		{
			++child;//则将右孩子置为child
		}

		//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束
		if (a[child] < a[parent])//大堆:a[child]>a[parent]
		{
			//交换数据
			Swap(&a[child], &a[parent]);

			//3.继续往下调,最多调整到叶子结点就结束
			//把孩子的下标赋值给父亲
			parent = child;

			//假设左孩子最小
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void PrintTopK(int* a, int n, int k)
{
	//1.建堆:用a中前k个元素建堆
	int* kMinHeap = (int*)malloc(sizeof(int) * k);
	assert(kMinHeap);

	for (int i = 0; i < k; i++)
	{
		//将a中前k的元素放进kMinHeap中
		kMinHeap[i] = a[i];
	}

	//建立小根堆
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(kMinHeap, k, i);
	}


	//2.将剩余的n-k个元素依次与堆顶元素比较,不满则替换
	for (int j = k; j < n; j++)
	{
		//若后面的元素大于堆顶元素,则进行替换,并向下调整
		if (a[j] > kMinHeap[0])
		{
			kMinHeap[0] = a[j];
			AdjustDown(kMinHeap, k, 0);
		}
	}


	//3.打印最大的前k个元素
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kMinHeap[i]);
	}

	printf("\n");

	//销毁
	free(kMinHeap);
}

void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	assert(a);

	srand((size_t)time(0));

	for (int i = 0; i < n; i++)
	{
		//产生一万个不大于100万的随机数
		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);
}

int main()
{
	TestTopk();

	return 0;
}

运行结果:

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

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

相关文章

PyMol选择配体周围的氨基酸残基

PyMOL选择配体周围的氨基酸残基 1. 问题 经常使用PyMOL做蛋白质和小分子的可视化&#xff0c;可以直接生成用于文章发表的高质量图片&#xff0c;图片生成可参考pymol作图 等教程。但是&#xff0c;用了这么久一直没发现可以直接用于选择配体周围一定距离氨基酸残基的功能。 …

Java阶段五Day17

Java阶段五Day17 文章目录 Java阶段五Day17师傅后台功能师傅审核列表相关功能启动进程和启动方式 后台审核详情查询查询审核详情流程远程调用图片服务 缓存逻辑缓存逻辑流程查询引入缓存流程完成缓存逻辑面试题整理 附录redis分布式——架构演变 师傅后台功能 师傅审核列表 相…

熟练掌握ChatGPT解决复杂问题——学会提问

目录 引言 一、5W1H分析法 1. 简单的问题&#xff08;what、where、when、who&#xff09; 2.复杂的问题&#xff08;why、how&#xff09; 2.1 为什么&#xff08;Why&#xff09;——原因 2.2 方式 &#xff08;How&#xff09;——如何 二、如何提问得到更高质量的答案…

mysql按照日期分组统计数据

目录 前言按天统计按周统计按月统计按年统计date_format参数 前言 mysql的date_format函数想必大家都使用过吧&#xff0c;一般用于日期时间转化 # 例如 select DATE_FORMAT(2023-01-01 08:30:50,%Y-%m-%d %H:%i:%s) # 可以得出 2023-01-01 08:30:50# 或者是 select DATE_FOR…

python中*与**的使用

文章目录 前言一、*与**在函数定义时二、*与**在函数调用时 前言 在python中*与**的使用要区分是在函数定义时还是在函数调用时。 一、*与**在函数定义时 def deng(*args,**kwargs):print(args)print(kwargs)deng(1,2,3,a 4,b 5)在函数定义时参数前面使用*&#xff0c;代表…

面试必考精华版Leetcode872. 叶子相似的树

题目&#xff1a; 代码&#xff08;首刷看解析 day23&#xff09;&#xff1a; class Solution { public:void dfs(TreeNode* root,vector<int>& seq){if(!root->left&&!root->right){seq.emplace_back(root->val);}else{if(root->left){dfs(ro…

java讲解Spring Boot配置文件级别 相互覆盖关系 解决一方不愿意给数据库密码 一方不愿意给源码时 数据库配置问题

前面 我们讲过Spring Boot 修改临时变量的方式 但另一个场景 就是 我们 在本地开发环境 用的是一个配置 但如果项目经理上线 他想改这些配置 怎么弄呢 特别是数据库之类的配置 很多线上是不太一样的 那么 我们先看一个比较基本的方法 在配置文件的同目录下创建一个目录 叫 con…

Sketch打不开AI文件?转换方法在这里

1、对比设计软件 Sketch 与 AI 软件功能 Sketch 与 Illustrator 都是行业内优秀的矢量图形设计软件&#xff0c;各有千秋。Sketch 从 2010 年面世&#xff0c;专注 APP 界面设计&#xff0c;深受初学者与专业人士喜爱。Illustrator 拥有更悠久的历史&#xff0c;是处理复杂图标…

好用的数据库管理软件之idea(idea也有数据库???)

1.建立maven项目&#xff08;maven项目添加依赖&#xff0c;对于后期连接数据库很方便&#xff09; 2.连接数据库。。。 这里一定注意端口号&#xff0c;不要搞错了 和上一张图片不一样哦 3.数据库测试代码。。。 然后你就可以在这里边写MySQL代码了&#xff0c;这个工具对于新…

线程概念linux

何为线程&#xff1a; 线程是程序中负责执行的单位&#xff0c;它可以被看作是进程的一部分&#xff0c;是进程的子任务。线程与进程的区别在于&#xff0c;进程是一个资源单位&#xff0c;而线程是进程的一部分&#xff0c;它只有栈这个独立的资源&#xff0c;其他资源如代码…

Html页面连线工具

在项目中遇了一个需要连线配置的功能。该功能引用了 bootstrap、layui 、svg-line等插件 下载链接 lixhttps://download.csdn.net/download/dongyan3595/88168121

SpringBoot使用redis作为缓存的实例

目录 什么是缓存&#xff1f; 缓存的作用&#xff1f; 缓存的成本&#xff1f; 实际项目中的应用 代码展示 什么是缓存&#xff1f; 缓存就是数据交换的缓冲区&#xff08;称作Cache [ kʃ ] &#xff09;&#xff0c;是存贮数据的临时地方&#xff0c;一般读写性能较高。 缓…

持续集成这样做,App自动化测试效率提高50%

持续集成是一种开发实践&#xff0c;它倡导团队成员需要频繁的集成他们的工作&#xff0c;每次集成都通过自动化构建&#xff08;包括编译、构建、自动化测试&#xff09;来验证&#xff0c;从而尽快地发现集成中的错误。让正在开发的软件始终处于可工作状态&#xff0c;让产品…

XSS漏洞简单测试

1、如何防御 在springboot中可以使用拦截器进行输入框转换 2、XSS漏洞简单测试 网站&#xff1a;xss注册一下 注册账号&#xff0c;创建项目 选择对应的 点击下一步 在指定的地方输入即可 xxxxx文本 <script srchttp://xsscom.com//YdIbhc></script> 在我的项…

Linux 3.10 GCC版本太低升级到5.2.0

一、问题描述 某次升级内核&#xff0c;执行make menuconfig报当前版本太低&#xff0c;现场版本为4.8.5&#xff0c;需要升级到5.1.0版本以上&#xff0c;查看官网后&#xff0c;决定升级到5.20版本。注&#xff1a;GCC依赖于gmp 4.2, mpfr 2.4和mpc 0.8&#xff0c;报错如下&…

【机器学习】在 MLOps构建项目 ( MLOps2)

My MLOps tutorials: Tutorial 1: A Beginner-Friendly Introduction to MLOps教程 2&#xff1a;使用 MLOps 构建机器学习项目 一、说明 如果你希望将机器学习项目提升到一个新的水平&#xff0c;MLOps 是该过程的重要组成部分。在本文中&#xff0c;我们将以经典手写数字分类…

后端进阶之路——浅谈Spring Security用户、角色、权限和访问规则(三)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

Cocos creator(2d) 使用 shader + uv 实现单张图片衔接滚动效果

在游戏中&#xff0c;当我们需要让背景图片无缝衔接无限滚动时(打飞机这种背景一直滚动&#xff0c;或者肉鸽游戏地图一直在走等等)&#xff0c;通常的做法是 在游戏中放两个背景node&#xff0c;在update中控制这两张背景图片的移动&#xff0c;并让其收尾衔接即可。(具体代码…

2023-08-04 LeetCode每日一题(不同路径 III)

2023-08-04每日一题 一、题目编号 980. 不同路径 III二、题目链接 点击跳转到题目位置 三、题目描述 在二维网格 grid 上&#xff0c;有 4 种类型的方格&#xff1a; 1 表示起始方格。且只有一个起始方格。2 表示结束方格&#xff0c;且只有一个结束方格。0 表示我们可以…

自然语言处理: 第六章Transformer- 现代大模型的基石

理论基础 Transformer&#xff08;来自2017年google发表的Attention Is All You Need (arxiv.org) &#xff09;&#xff0c;接上面一篇attention之后&#xff0c;transformer是基于自注意力基础上引申出来的结构&#xff0c;其主要解决了seq2seq的两个问题: 考虑了原序列和目…