树和堆的精讲

news2025/1/12 6:17:11

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互相支持,蟹蟹!
👑👑👑💎👑👑👑

思维导图:

1:树的相关概念
1.1 在数据结构中数的重要性

首先:树在数据结构中扮演着重要的角色,它是一种非线性的数据结构,可以用来表示层次关系或者具有树状结构的数据。

其次:树的常见应用包括文件系统、组织结构、编译器中的抽象语法树

再者:树有个好处,就是当节点有序的时候(即有序树),那么在这个树上搜索一个 节点 是很快的(log级别),所以,现在的索引一般都是用各种树( 数据库 如mysql大多用B+树)

1.2 树的结构

1.3 树的概念

树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。
有一个 特殊的结点,称为根结点 ,根节点没有前驱结点
除根节点外,其余每一个节点被分成多个不相交的小树 ,其中每一个小树 又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0 个或多个后继
树是递归定义的。
1.4 树的常见术语
节点的度 :一个节点含有的子树的个数称为该节点的度(或者说当前节点的 直接后继 );
如上图: A节点 的度为 6
叶节点或终端节点 :度为 0 的节点称为叶节点(没有直接后继的节点);
如上图: B C H I... 等节点为叶节点
非终端节点或分支节点 :度不为 0 的节点( 除根节点和叶子结点之外的节点
如上图: D E F G... 等节点为分支节点
双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点(注意 没有父亲节点的说法 ) 如上图: A B 的父节点
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点; 如上图: B A 的孩子节点
兄弟节点 :具 有相同父节点的 节点互称为兄弟节点; 如上图: B C 是兄弟节点
树的度 :一棵树中, 最大的节点的度 称为树的度; 如上图:树的度为 6
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
树的高度或深度 :树中节点的最大层次; 如上图:树的高度为 4(注意: 高度为3 也是对的 ,从数组下标出发)
堂兄弟节点 双亲在同一层 的节点互为堂兄弟;如上图: H I 互为兄弟节点
节点的祖先 :从根到该节点所经分支上的所有节点;如上图: A 是所有节点的祖先
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是 A 的子孙
森林 :由 m m>0 )棵互不相交的树的集合称为森林;
2:树的应用

文件的目录

组织结构:

3:堆的概念
3.1二叉树的概念

因为堆是与二叉树相关的,所以这里就不得不说一下二叉树

二叉树是一种树形数据结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树有很多种不同的形式,比如满二叉树、完全二叉树、平衡二叉树等

注意:

二叉树不存在度 大于2的节点

二叉树的左子树 和右子树的次序是不能颠倒的(颠倒后是不同的树)

3.3 特殊的二叉树
满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K ,且结点总数是 2 ^K -1,则它就是满二叉树。
2. 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K 的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的结点一一对 应时称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树
3.3 堆的概念
如果有一个关键码的集合  ,把它的所有元素 按完全二叉树的顺序存储 方式存储 在一个一维数组中,
将根节点最大的堆叫做最大堆或大根堆:双亲节点 大于或者等于任何一个子节点(递归定义
根节点最小的堆叫做最小堆或小根堆。双亲节点 小于或者等于任何一个子节点(递归定义
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
4:堆的结构

首先:无论是一个大堆还是个小堆:在结构上都是有根节点,左子树,右子树

 其次:堆要不是大堆,要不是小堆(有序的)

最后:堆的本质其实还是数组在物理结构上,在逻辑结构上是一个二叉树

typedef int HP_DataType;
typedef struct Heap
{
	HP_DataType* a;
	int size;//有效数据个数
	int capacity;//空间容量

}HP;

5: 堆的创建

以建一个小堆为例来分析:

 上调算法的思路分析:

1)关键的如何确定每一次的双亲以及孩子节点的下标

二叉树的性质之一: 下标上  child = 2 * parent + 1 

所以双亲的下标:parent = (child -1) / 2 

2)因为每次进入数据最坏的情况下就是把孩子上调到根节点 此时就是最小堆了

所以结束条件的判断  child > 0

3)因为是建一个小堆:所以 若当前孩子节点 的值 > 双亲节点 的值就进行交换

并且交换完后 双亲 和 孩子继续迭代

child = parent ;

parent = (child -1) / 2  ;

 交换
void Swap(HP_DataType* p1, HP_DataType* p2)
{
	assert(p1);
	assert(p2);
	HP_DataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
向上调整代码:
void Adujust_up(HP_DataType* a,int pos)
{
	assert(a);
	int child = pos;
	int parent = (child - 1) / 2;//下标从0开始
	while (child > 0)
	{
		if (a[child] < a[parent]) //进行上调
		{
			/*int swap = php->a[child];
			php->a[child] = php->a[parent];
			php->a[parent] = swap;*/
			Swap(&(a[child]), &(a[parent]));
			//再继续下一轮
			child = parent;
			parent = (child - 1) / 2;
		}
		else//  同时也避免了 parent == child == 0 进入死循环
		{
			break;
		}
	}
}
数据进堆的代码:
void HP_push(HP* php, HP_DataType x)
{
	/*
	建小根堆
	1:空间是否有
	2:插入数据  同时 size++
	3:是否需要对插入的当前节点与双亲节点调整(递归的定义)
	*/
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		//HP_DataType* tmp = (HP_DataType*)mealloc(php->a,sizeof(HP_DataType) * newcapacity);//扩容用realloc
		HP_DataType* tmp = (HP_DataType*)realloc(php->a,sizeof(HP_DataType) * newcapacity);//扩容用realloc
		if (tmp == NULL)
		{
			perror("malloc fail");
			return ;
		}
		//扩容成功
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	Adujust_up(php->a,php->size);//判断插入的节点是否需要向上调整
	//Adujust_up(&x,php->size);//判断插入的节点是否需要向上调整
	php->size++;

}
5.2:堆的销毁

这个相比较之下就 so easy 

void HP_Destroy(HP* php)
{
	assert(php);
	free(php->a);
	php->size = 0;
	php->capacity = 0;
}
5.3:堆的删除(删除堆顶元素)

思路分析:我们一般会这样思考:直接删除堆顶元素不就完了吗,但是删除之后的堆不在满足小堆的结构。若是重新建小堆的话,在时间上的开销是很大滴

我们不妨格局打开,难道我就一定从堆顶这一个位置着手吗?

让堆顶元素与堆尾元素的值进行交换;此时堆的结构发生了改变但是相对于直接删除堆顶元素而言这并不是多多问题,此时我们在对改变的那个分支结构进行上调即可

草图见下:

 和向上调整思维一样:需要考虑双亲与孩子节点的下标

parent = 0;

child = 2 * parent + 1 ; //注意数组下标是从0 开始

 让父亲节点来到孩子节点的位置  parent =  child 

此时孩子节点再继续下走 child = 2 * parent + 1 ; 

注意:

1:采用假设思想 假设左孩子 节点在值最小

2:循环判断条件 孩子节点的下标 要 小于 数组的大小

3:找左右孩子最小节点的时候,若是右孩子节点值小,判断条件是 必须保证右孩子节的存在  child + 1  < num   

向下调整代码:

void Adujust_Down(HP_DataType* a, int num,int parent_i)//必须保证左右子树是小根堆   第三个参数:双亲下标
{
	/*
	向下调整:保证此时还是一个小根堆  
	当 parent > child 进行交换
	关键是不知道左右孩子谁是最小节点
	*/
	assert(a);
	int parent = parent_i;
	int child = 2 * parent + 1;  //假设左孩子为最小的孩子节点
	while (child < num) //保证孩子在数组里面
	{
		if (child + 1 < num && a[child + 1] > a[child ]) //child + 1 < num 保证有孩子存在    a[child + 1] < a[child + 1] 右孩子可能是最小节点
		{
			child += 1;//更新最小孩子节点  
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			//父 与 子 依次下移
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
		 父 与 子 依次下移
		//parent = child;
		//child = 2 * parent + 1;
	//	以上2句代码在这err
	}
}

 堆顶元素删除代码:

void HP_Pop(HP* php)
{
	/*
	把堆顶元素与堆尾元素的值进行交换;
	其次删除
	最后检查是否需要 进行向下调整
	注意对堆进行调整的时候必须保证 左右子树是有序堆
	*/
	assert(php);
	assert(!HP_Empty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	Adujust_Down(php->a, php->size,0);//必须保证是小根堆   

}
5.4:堆的判空
bool HP_Empty(HP* php)
{
	assert(php);
	return php->size == 0;
}
5.5:堆顶元素获取
HP_DataType HP_Top(HP* php)
{
	assert(php);
	assert(!HP_Empty(php));
	return php->a[0];
}
 5.6 完整代码

Heap.h

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

//堆的本质就是数组  注意:数组不一定是堆
/*
成为堆的条件
1:一定是完全二叉树 (包括满二叉树)
2:双亲节点的val > \ < 孩子节点的val(递归定义)
*/

typedef int HP_DataType;
typedef struct Heap
{
	HP_DataType* a;
	int size;//有效数据个数
	int capacity;//空间容量

}HP;

void HP_Init(HP* php);
void HP_Destroy(HP* php);
void HP_push(HP* php,HP_DataType x);
void HP_Pop(HP* php);
bool HP_Empty(HP* php);
int HP_Size(HP* php);
HP_DataType HP_Top(HP* php);
void Swap(HP_DataType* p1, HP_DataType* p2);

void Adujust_Down(HP_DataType* a, int num, int parent_i);//必须保证左右子树是小根堆   第三个参数:双亲下标
void Adujust_up(HP_DataType* a, int pos);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

void HP_Init(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size= 0;
	php->capacity = 0;
}
void HP_Destroy(HP* php)
{
	assert(php);
	free(php->a);
	php->size = 0;
	php->capacity = 0;
}
void Swap(HP_DataType* p1, HP_DataType* p2)
{
	assert(p1);
	assert(p2);
	HP_DataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void Adujust_up(HP_DataType* a,int pos)
{
	assert(a);
	int child = pos;
	int parent = (child - 1) / 2;//下标从0开始
	while (child > 0)
	{
		if (a[child] < a[parent]) //进行上调
		{
			/*int swap = php->a[child];
			php->a[child] = php->a[parent];
			php->a[parent] = swap;*/
			Swap(&(a[child]), &(a[parent]));
			//再继续下一轮
			child = parent;
			parent = (child - 1) / 2;
		}
		else//  同时也避免了 parent == child == 0 进入死循环
		{
			break;
		}
	}
}

void HP_push(HP* php, HP_DataType x)
{
	/*
	建小根堆
	1:空间是否有
	2:插入数据  同时 size++
	3:是否需要对插入的当前节点与双亲节点调整(递归的定义)
	*/
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		//HP_DataType* tmp = (HP_DataType*)mealloc(php->a,sizeof(HP_DataType) * newcapacity);//扩容用realloc
		HP_DataType* tmp = (HP_DataType*)realloc(php->a,sizeof(HP_DataType) * newcapacity);//扩容用realloc
		if (tmp == NULL)
		{
			perror("malloc fail");
			return ;
		}
		//扩容成功
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	Adujust_up(php->a,php->size);//判断插入的节点是否需要向上调整
	//Adujust_up(&x,php->size);//判断插入的节点是否需要向上调整
	php->size++;

}
void Adujust_Down(HP_DataType* a, int num,int parent_i)//必须保证左右子树是小根堆   第三个参数:双亲下标
{
	/*
	向下调整:保证此时还是一个小根堆  
	当 parent > child 进行交换
	关键是不知道左右孩子谁是最小节点
	*/
	assert(a);
	int parent = parent_i;
	int child = 2 * parent + 1;  //假设左孩子为最小的孩子节点
	while (child < num) //保证孩子在数组里面
	{
		if (child + 1 < num && a[child + 1] > a[child ]) //child + 1 < num 保证有孩子存在    a[child + 1] < a[child + 1] 右孩子可能是最小节点
		{
			child += 1;//更新最小孩子节点  
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			//父 与 子 依次下移
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
		 父 与 子 依次下移
		//parent = child;
		//child = 2 * parent + 1;
	//	以上2句代码在这err
	}
}

void HP_Pop(HP* php)
{
	/*
	把堆顶元素与堆尾元素的值进行交换;
	其次删除
	最后检查是否需要 进行向下调整
	注意对堆进行调整的时候必须保证 左右子树是有序堆
	*/
	assert(php);
	assert(!HP_Empty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	Adujust_Down(php->a, php->size,0);//必须保证是小根堆   

}
bool HP_Empty(HP* php)
{
	assert(php);
	return php->size == 0;
}
int HP_Size(HP* php)
{
	assert(php);
	return php->size;
}
HP_DataType HP_Top(HP* php)
{
	assert(php);
	assert(!HP_Empty(php));
	return php->a[0];
}
6:与堆相关算法的时间复杂度以及证明
6.1 下调算法来建堆

下调的时间复杂度 O(N)

证明如下:

下面就以最坏的情况来分析:满二叉树

6.2 上调算法来建堆

对应时间复杂度: N * (log N)     注意: N:节点个数

证明如下:

 综合来看的话,虽然上调和下调的算法都可以完成建堆,但是在效率:下调 的算法更友好

7: 堆的应用
7.1堆排序

通过上面时间复杂度的解析想必堆在排序问题上自然也是占上风的。

7.1.1 把数据排成升序

若是想把一组数据排成升序我们到底是建大堆还是建小堆???

 可能有些人会说,自然是建小堆了,这样每次从堆顶取出的元素永远都是当前堆中最小 的元素。确实是没有问题,但是每取出堆顶的元素后,堆中的数据不再是满足父与子之间的关系了,这就需要对堆来进行调整,也就是对余下的 N-1个元素重新建堆

我们可以建大堆:

1)首先把数组的数据进行调整,建成一个大堆,采用下调的方法

 从第一个非叶子节点开始向下调整:孩子节点值 大于双亲节点值就进行调整;

直至调整到根节点为止

2)堆顶元素与堆尾元素进行交换,因为堆顶元素是当前堆中最大的了

3):再对余下的n-1个数进行调整找出最大,放在倒数 第二个位置

其实就是把每次堆顶元素进行头插,最终就变成升序数组

数组原来数据:a[ ] = {9,5,11,15,12,13}

运行结果:

升序排列的结果:

代码:

void Heap_Qsort(int* a, int num)
{
	/*
	   构造一个升序的堆
	1:需要先建一个大堆
	2:堆顶与堆尾交换
	3:下调
	*/
	assert(a);
	int pos = 0;
	//把数组排成一个大堆
	for (pos = (num - 1 - 1) / 2; pos >= 0; pos--)
	{
		Adujust_Down(a, num, pos);
	}
	// 堆顶与堆尾交换 &&  对非堆尾元素进行调整
	for (pos = num - 1; pos >= 0; pos--)
	{
		Swap(&a[0], &a[pos]);
		// 依次选出堆顶最大的元素
		Adujust_Down(a, pos, 0);//此时pos 即表示 堆尾也表示 每次去掉堆尾元素的个数
	}

}

7.1.2 把数据排成降序

相信有了前面升序排列的思维,再来这个降序排列应该是不在话下。

分析:

首先把数组整成一个小堆

其次就是堆顶与堆尾元素进行交换;交换之后在对非堆尾的元素进行下调

排成小堆的结果:

降序的结果:

 

对应代码:可以复用上次升序的结果注意,有些地方需要改动

7.2经典的TopK问题

Top-K 问题是一类常见的算法问题,其中目的是从一组元素中找到排名前K的元素。具体来说,对于给定的一组数据。Top-K 问题要求找到其中最大(或最小)的K个元素。

生活中 的栗子:

 当数据量非常大的时候(100万亿级别),我们就不能对这100万亿的数据全部进行排序了,

这是为什么呢?

因为内存的空间是非常有限的,这100万亿的数不会存储在内存上,而是在磁盘上,我们需要从文件里面进行读写 的操作。

 所以说我们可以先对100万亿里面的前 K 个数据进行建堆,注意此时堆的大小就是固定的,就只存储K 个数据;

其次依次从余下的数据里面取出与堆顶元素进行比较,若满足指定的条件,则把当数据进行与堆顶元素 的交换

最后:交换之后再对堆中数据进行调整;之后就是重复以上操作,直至数组里面余下数据与堆顶元素比较完之后,堆中的K个数据就是所求

求最大的前三个数据:

首先对前3 数据进行建一个小堆,注意这里不能建大堆(若是建大堆的话,可能最大的数据在前三个数,其余2个数据在余下的 N-K个数里面,这样就不能搞了)

若是大于堆尾元素就替换掉当前的堆尾元素,并对当前堆中数据进行建小堆 的调整

草图见下:

 代码:

void Print_TopK(int k ,int*a,int num_a)
{
	/*
	TopK 终极问题:当数据足够大的时候,面临空间的问题:直接减堆解决不了问题
	只能对前K个数先建堆 ==》在对后 n-k个数据依次进行与堆顶数据判断,是否取代当前堆顶元素 ==》 取代后需要重新调整
    */

	//找最大的K个数
	int* topk_arr = (int*)malloc(sizeof(int) * k);
	if (topk_arr == NULL)
	{
		return;
	}
	// 前 K个数写到topK_arr这个数组里面
	int i = 0;
	for (i; i < k; i++)
	{
		topk_arr[i] = a[i];
	}
	//对前K个数进行小堆的建立
	int pos = k;
	for (pos = (k - 1 - 1) / 2; pos >= 0; pos--)
	{
		Adujust_Down(topk_arr, pos, 0);
	}
	//依次判断是否替换堆顶元素
	for (i = k; i < num_a; i++)
	{
		if (a[i] > topk_arr[0])
		{
			Swap(&a[i], &topk_arr[0]);
			Adujust_Down(topk_arr, k, 0);
		}
	}
	//对最大的前K个数降序输出  
	for (i = k - 1; i >= 0; i--)
	{
		Swap(&topk_arr[0], &topk_arr[i]);  //注意堆尾下标每次是不同的
		Adujust_Down(topk_arr, i, 0); 
	}

	for ( i = 0; i < k; i++)
	{
		printf("%d\n", topk_arr[i]);
	}

}

 运行结果:

结语:

关于堆这一结构在我们日常生活中应用是非常广泛的,当然了对于这个TopK的问题,在面试中也是不可避免的!以上就是我share 的内容了,对于这块的知识体系着实不太好理解,难度相比之前的链表也不在一个层次,我们需要做到物理结构与逻辑结构的双向结合,当然了画图自然是必不可少(对于我这种小白而言,脑子转不过来)。希望此篇博客可以对你有些帮助,要是觉得不错的话,还希望各位大佬们多多支持(这篇博客也是倾注了不少尽力)。

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

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

相关文章

(简易部署)恒创科技「幻兽帕鲁香港游戏服务器」搭建教程

近期&#xff0c;作为一款主打多人游戏模式的全新开放世界生存制作游戏——《幻兽帕鲁》(Palworld) &#xff0c;可谓“高效出圈”&#xff0c;自 2024 年 1 月 19 日在 Steam 平台发售抢先试玩版后&#xff0c;短短两个星期&#xff0c;幻兽帕鲁 steam 在线峰值已突破 200 万&…

23.java-日志框架

日志框架 介绍 : 程序中的日志可以用来记录程序运行过程中的信息&#xff0c;并可以进行永久存储。 生活中的日志&#xff1a; 生活中的日志就好比日记&#xff0c;可以记录你生活的点点滴滴。 引入 : 目前输出语句的弊端 : 信息只能展示在控制台不能将其记录到其他的位置&a…

Caddy 自动HTTPS 反向代理、重定向、静态页面 - docker版

简介 Caddy 是一个通用的、易于使用的 Web 服务器&#xff0c;具有以下特点&#xff1a; 快速: Caddy 使用 Go 语言编写&#xff0c;以高性能著称。 安全: Caddy 支持 HTTPS、自动证书生成、HTTP/2 等安全功能。 易用: Caddy 的配置文件简单易懂&#xff0c;易于配置。 功能丰…

代码随想录刷题笔记-Day19

1. 二叉搜索树的最小绝对差 530. 二叉搜索树的最小绝对差https://leetcode.cn/problems/minimum-absolute-difference-in-bst/ 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝…

Git基本操作(超详细)

文章目录 创建Git本地仓库配置Git配置命令查看是否配置成功重置配置 工作区、暂存区、版本库添加文件--场景一概述实例操作 查看.git文件添加文件--场景二修改文件版本回退撤销修改情况⼀&#xff1a;对于工作区的代码&#xff0c;还没有 add情况⼆&#xff1a;已经 add &#…

单片机和RTOS

一.单片机和RTOS区别 单片机是一种集成了处理器、内存、输入输出接口和外围设备控制器等功能的微型计算机系统。它通常用于控制简单的嵌入式系统&#xff0c;如家电、汽车电子、工业控制等。单片机具有低功耗、低成本和高可靠性等特点。 而RTOS&#xff08;Real-Time Operati…

HTTP 请求 400错误

问题 HTTP 请求 400错误 详细问题 客户端发送请求 public static UserInfo updateUserInfo(UserInfo userInfo) {// 创建 OkHttpClient 对象OkHttpClient client new OkHttpClient();// 创建请求体MediaType JSON MediaType.parse("application/json; charsetutf-8&…

DS:栈和队列的相互实现

创作不易&#xff0c;感谢友友们三连&#xff01;&#xff01; 一、前言 栈和队列的相互实现是用两个栈去实现队列或者是用两个队列去实现栈&#xff0c;这样其实是把问题复杂化的&#xff0c;实际中没有什么应用价值&#xff0c;但是通过他们的相互实现可以让我们更加深入地理…

快乐学Python,通过用户和订单的数据分析,制定营销策略【实战】

在互联网行业中&#xff0c;电子商务领域绝对是数据分析用途最多的地方&#xff0c;各大电商平台都依赖数据分析帮助其挖掘用户订单增长机会。比如某宝的随手买一件&#xff0c;核心思路也就是根据用户的日常浏览内容及停留时间&#xff0c;以及订单的关联度来进行推荐的。 本篇…

ssm在线学习平台-计算机毕业设计源码09650

目 录 摘要 1 绪论 1.1 选题背景及意义 1.2国内外现状分析 1.3论文结构与章节安排 2 在线学习平台系统分析 2.1 可行性分析 2.2 系统业务流程分析 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 在线学习平台总体设计 …

基于ant的图片上传组件封装(复制即可使用)

/*** 上传图片组件* param imgSize 图片大小限制* param data 上传数据* param disabled 是否禁用*/import React, { useState,useEffect } from react; import { Upload, Icon, message} from antd; const UploadImage ({imgSize 50,data { Directory: Image },disabled f…

文物保护系统守护历史岁月,成都青铜展科技闪耀

一、“吉金万里-中国西南青铜文明展”隆重开幕 1月27日&#xff0c;“吉金万里-中国西南青铜文明展”在成都金沙遗址博物馆向公众开放&#xff0c;奉上一场精彩的青铜文明“盛宴”。本次展览汇集了中国西南地区32家文博单位&#xff0c;以青铜器为代表的294件经典文物&#xf…

vue3+ts项目推荐(企业级0到1开发)H5

一、技术方案 基于 vue3typescript 中大型项目开发解决方案基于 vant 组件库快速构建H5界面解决方案基于 vue-router 的前端路由解决方案基于 vite 的 create-vue 构建vue3项目解决方案基于 pinia 的状态管理解决方案基于 pinia-plugin-persistedstate 状态持久化解决方案基于…

视频接入协议之MIPI

MIPI&#xff08;Mobile Industry Processor Interface&#xff09;是一种用于移动设备的串行接口标准&#xff0c;旨在提供高速、低功耗、低成本的接口解决方案。MIPI联盟是一个全球性的组织&#xff0c;致力于开发、推广和管理MIPI标准。 MIPI接口包括了多种协议和规范&…

typescript typeof操作符

ts typeof 操作符简介 在TypeScript中&#xff0c;typeof是一个操作符&#xff0c;用于获取一个值的类型。它可以与任何值一起使用&#xff0c;并返回一个描述该值类型的字符串。 typeof操作符在TypeScript中的用法与JavaScript中的用法非常相似。 如下,众所周知,在js中提供…

day03-股票数据报表与导出

day03-股票数据表报与导出 目标 理解涨幅榜业务需求;理解涨停跌停概念&#xff0c;并涨停跌停基本实现;理解涨停跌停SQL分析流程&#xff0c;并根据接口文档自定义实现;理解echarts基本使用;掌握easyExcel基本使用,并实现涨幅榜数据导出功能; 第一章 股票涨幅统计 1、涨幅榜…

【C++关键字】auto以及指针空值nullpr

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 前言1.类型别名思考2…

SQL110 插入记录(一)(插入和interval关键字的用法)

代码 insert into exam_record(uid,exam_id,start_time,submit_time,score) values(1001,9001,2021-09-01 22:11:12,2021-09-01 22:11:12interval 50 minute,90), (1002,9002,2021-09-04 07:01:02,null,null)知识点 interval关键字的用法 INTERVAL关键字一般使用格式为&#x…

牛客网 HJ76 尼克彻斯定理

题目&#xff1a; 思路&#xff1a; 主要是要找规律&#xff0c;即m^3&#xff08;m*m-m1j&#xff09;……&#xff0c;j从0每次递增2 然后就是注意输出格式 答案&#xff1a; #include <stdio.h> int main() {int m0; again:scanf("%d",&m);if(m<…

基于Springboot+Vue的在线考试系统源码

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着现代教育和职业培…