【数据结构】——二叉树的基础知识

news2024/11/17 22:47:21

数概念及结构

数的分类

二叉树、多叉树

数的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做树的原因是它看起来像一颗倒挂的树,也就是说它是跟朝上,而叶朝下的。

  • 有一个特殊的节点,称为根节点,这个节点没有前驱节点。
  • 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<=i<=m)又是一颗结构与树类似的子树。每颗子树的根节点有且只有一个前驱,可以有0个或多个后继节点。
  • 数是递归定义的。
  • 子树是不相交的;
    什么是递归:
    大问题->类似子问题->类似子问题

数的相关概念

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

数的高度一般都是从1开始,从0开始也可以。
扩展:
数组下标为什么要从0开始,因为这符合偏移量。
当前元素到第一个元素的偏移量,第一个元素的下标自然就是0,第二个元素的下标为1,第n个元素的下标为n-1。
并查集里面就是多棵树。

数的存储

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存节点和节点之间的关系,实际中树有很多的表示方式如:双亲表示法、孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里简单的了解其中最常见的孩子兄弟表示法

struct TreeNode
{
	int val;
	struct TreeNode* firstchild;
	struct TreeNode*nextbrother;
}

数在实际中的运用(表示文件系统的目录结构)

image.png
文件系统就是一个树形结构。

还有一种双亲表示法:只存储双亲的下标或指针。
两个结点在不在同一棵树,如何判断?
找根,根相同就是在同一棵树。

二叉树概念及结构

概念

一颗二叉树是结点得一个有限集合,该集合:
1、或者为空
2、由一个根结点加上两颗别称为左子树和右子树得二叉树组成。
注意:
1、二叉树最多两个孩子。
2、二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
注意:
空树、只有根结点、只有左子树、只有右子树、左右子树均存在。

特殊的二叉树

满二叉树

每一层都是2^(i-1)个结点,每一层都是满的就是满二叉树。
高度为h的满二叉树有多少个节点:
F(h)=20+21+…+2(h-2)+2^(h-1)
使用错位相减法可以得到下面的结果
F(h)=2^h-1
image.png

完全二叉树

假设他的高度是h,前h-1层是满的,最后一层不一定满,从左到右是连续的。
image.png

二叉树的性质

1、若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有2^(i-1)个节点。
2、若规定根节点的层数为1,则深度为h的二叉树的最大节点数是2^k-1.
3、在任何二叉树中,度为0的叶子节点永远比度为2的多一个,只有一个节点时 n0=n2+1。
完全二叉树特点:
度为1的节点只有1个或者0个。如果节点的个数是偶数那么度为1的节点个数等于1,反之则为0。

二叉树的存储结构

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

顺序存储

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

leftchild=parent * 2+1;
rightchild=parent*2+2;

parent=(child-1)/2;

任意位置通过下标可以找到父亲或者孩子。
总结:满二叉树和完全二叉树适合使用数组储存。

链式存储

二叉树的链式存储结构是指,用链表来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是每个链表中每个节点由三个域组成,数据域和左右指针域,左右指针分别用来表示给出该节点左孩子和右孩子所在的链节点的存储地址。
image.png

结构如下
typedef int BTDataType;
struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode*right;
	BTDataType data;
}

二叉树顺序结构及实现

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

堆的概念

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或者小堆根。
堆:非线性结构,完全二叉树
小堆:树中任意一个父亲都<=孩子
大堆:树中任意一个父亲都>=孩子

底层:物理结构,数组
逻辑结构,完全二叉树
小堆,底层数组是否升序呢?不一定。
小堆的根是整棵树的最小值。可以解决的问题:
1、topk问题
2、堆排序

堆的实现

堆的插入

插入数据之后还要保证数据是堆。

堆的定义

底层就是一个顺序表,可以使用数组来存储。

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

堆的初始化HeapInit

1)断言
2)有两种方式第一种直接置空,第二种开一个空间。

//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

打印HeapPrint

1)断言
2)使用循环打印

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

释放空间HeapDestroy

1)断言
2)先释放数组

//释放空间
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

向上调整AdjustUp

1)先算出父节点
2)使用child等于0来结束循环
3)判断孩子节点是否小于父节点,小于的话就交换,同时要修改child和parent的大小
4)反之,则直接退出循环。

void AdjustUp(HPDataType* a, int child)
{
	//找到父节点
	int parent = (child - 1) / 2;
	//当下标小于等于零的时候结束循环
	while (child>0)
	{
		if (a[child]<a[parent])
		{
			//交换两个数
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			//直接跳出循环
			break;
		}
	}
}

交换函数Swap

需要的参数就是两个指针。
根据传过来的指针,直接交换就好。

void Swap(HPDataType* child, HPDataType* parent)
{
	int temp = *child;
	*child = *parent;
	*parent = temp;
}

插入数据HeapPush

1)断言
2)扩容
3)使用realloc在堆上开辟空间。
4)向上调整,向上调整的时候是需要数组的指针和最后一个数的下标

//插入数据
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//扩容
	if (php->size==php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (temp==NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		//把创建的空间分配给a
		php->a = temp;
		//定义容器的大小
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a,php->size-1);
}

向下调整AdjustDown

需要的参数指向数组的指针,元素的个数,跟节点的下标。
1)先找到孩子
使用循环来找孩子,循环的结束条件就是孩子的下标小于元素个数
2)比较两个孩子谁小一点
注意右孩子可能出现越界的情况这需要考虑
3)再比较孩子节点和父节点上的值
如果比父节点上的值小那就交换,再继续向下调整

void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (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;
		}
	}
}

删除数据HeapPop

删除根比较有意义。
如果挪动覆盖第一个位置根,关系全乱了。
1)先交换 根和最后一个值交换,再删除。
需要注意的是要控制size的大小因该控制在大于0
交换完之后size需要减1
2)向下调整
传参数的时候要传3个参数,数组,元素个数,根节点。
元素个数用来控制节点的最大下标。

//删除数据
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);
}

堆顶的数据HeapTop

只需要堆的指针
1)断言
是否为空指针,是否为空堆
2)返回根

//取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}

初始化数组HeapInitArray

需要的参数堆的指针,数组指针,开的空间大小
1)断言
判断两个指针是否为空指针。
2)给数组开空间
并判断空间是否开辟完成
3)使用memcpy函数进行复制
4)建堆

//初始化数组
void HeapInitArray(HP* php, int* a, int n)
{
	assert(php);
	assert(a);
	php->a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	if (php->a==NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	php->size = n;
	php->capacity = n;
	memcpy(php->a, a, sizeof(HPDataType) * n);
	
	for (int i = 0; i < n; i++)
	{
		AdjustUp(php->a, i);
	}
}

判空HeapEmpty

只需要堆的指针
1)断言
看指针是否为空
2)返回size是否等于0

//判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

使用堆排序HeapSort

堆排序的特点:就是数组有序
缺点:需要写有一个堆。
向上调整。
排升序,建大堆。
堆顶跟最后一个位置交换,最大的数据就排好了,剩下数据向下调整,选出次大的,代价是logN。合计是:N* logN

//void HeapSort(int* a, int n)
//{
//	HP hp;
//	//初始化
//	HeapInit(&hp);
//	
//	for (int i = 0; i < n; i++)
//	{
//		HeapPush(&hp, a[i]);
//	}
//	
//	int i = 0;
//	while (!HeapEmpty(&hp))
//	{
//		printf("%d ", HeapTop(&hp));
//		a[i++] = HeapTop(&hp);
//		HeapPop(&hp);
//	}
//
//	HeapDestroy(&hp);
//}

void HeapSort(int* a, int n)
{
	// 建堆 (大堆)or  (小堆)
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
	 
}
void test2()
{
	int a[] = { 2,3,5,7,4,6,8 };
	HeapSort(a, sizeof(a) / sizeof(int));
}

向下调整建堆

image.png
image.png
image.png

向上调整

image.png

TopK问题

假设10亿个数据,内存存不下,数据在文件中找出最大的前k个。
1、读取文件的前100个数据,在内存中建立小堆
2、再依次读取剩下数据,跟堆顶数据比较,大于堆顶,就替换他进堆,向下调整
3、所有的数据读完,堆里面数据就是最大的前100个
核心:
建立小堆、时间复杂度:O(N* logK)、空间复杂度:O(K)

void PrintTopK(const char* filename, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	FILE* fout = fopen(filename, "r");
	if (fout==NULL)
	{
		perror("fopen fail");
		return;
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap==NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}

	//前k个数建小堆
	for (int i = (k-2)/2; i >=0 ; i--)
	{
		AdjustDown(minheap, k, i);
	}
	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int x = 0;
	while (fscanf(fout,"%d",&x)!=EOF)
	{
		if (x>minheap[0])
		{
			//替换进堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

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

//创建文件
void CreateNData()
{
	//造数据
	int n = 1000000;
	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) % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

int main()
{
	//CreateNData();
	PrintTopK("data.txt", 5);
	return 0;
}

二叉树链式结构及实现

二叉树的遍历(递归遍历)

image.png
普通的链式二叉树没有意义。
1、更复杂二叉树搜索树->AVL 红黑树(以后打基础)
2、很多二叉树的题,是出这一块
image.png
这就是搜索二叉树,左边比根小右边比根大。
搜索二叉树走中序就是排序。

前序遍历PrevOrder

1、先手动构建二叉树

typedef struct BinaryTreeNode
{
	struct BinaryTreeNode*left;
	struct BinaryTreeNode*right;
	int val;
}BTNode;

BTNode*BuyNode(int x)
{
	BTNode*node=(BTNode*)malloc(sizeof(BTNode));
	if(node==NULL)
	{
		perror("malloc fail");
		return;
	}
	node->val=x;
	node->left=NULL;
	node->right=NUll;
	return node;
}

2、手动链接

int main()
{
	BTNode* node1=BuyNode(1);
	BTNode* node2=BuyNode(2);
	BTNode* node3=BuyNode(3);
	BTNode* node4=BuyNode(4);
	BTNOde* node5=BuyNode(5);
	BTNode* node6=BuyNode(6);

	node1->left=node2;
	node1->right=node4;
	node2->left=node3;
	node4->left=node5;
	node4->right=node6;
	return 0;
}

3、根 左子树 右子树

void PrevOrder(BTNode*root)
{
	if(root==NULL)
	{
		printf("NULL ");
		return ;
	}
	printf("%d ",root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

中序遍历InOrder

先左子树 根 右子树

void InOrder(BTNode*root)
{
	if(root==NULL)
	{
		printf("NULL ");
		return ;
	}
	InOrder(root->left);
	printf("%d ",root->val);
	InOrder(root->right);
}

image.png

后序遍历PostOrder

先左子树 右子树 根

void PostOrder(BTNode*root)
{
	if(root==NULL)
	{
		printf("NULL");
		return ;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ",root->val);
}

二叉树节点个数TreeSize

方法一:简单的方式就是遍历一遍。
方法二:当前节点的个数等于左子树+右子树+自己
1、当前为空的时候返回0
2、反之就返回左子树+右子树+1

int TreeSize(BTNode* root)
{
	return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
}

叶子节点个数TreeLeafSize

1、空->0
2、叶->1
3、左子树+右子树

int TreeLeafSize(BTNode*root)
{
	if(root==NULL)
	{
		return 0;
	}
	if(root->left==NULL&&root->right==NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left)+TreeLeafSize(root->right);
}

第k层的节点个数TreeLevel

1、当前树的第k层=左子树的第k-1层+右子树的第k-1层

int TreeLevel(BTNode*root,int k)
{
	if(root==NULL)
	{
		return 0;
	}
	if(k==1)
	{
		return 1;
	}
	return TreeLevel(root->left,k-1)+TreeLevel(root->right,k-1);
}

二叉树的销毁TreeDestroy

1、子问题
2、返回条件(最小规模的子问题)
3、使用后序遍历的思想进行销毁

void TreeDestroy(BTNode* root)
{
	if (root==NULL)
	{
		return;
	}
	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

二叉树查找值为X的节点TreeFind

BTNode* TreeFind(BTNode* root, int x)
{
	//如果是空者返回空指针
	if (root==NULL)
	{
		return NULL;
	}
	//如果找到了就返回当前节点的指针
	if (root->val==x)
	{
		return root;
	}
	//定义一个指针来接受返回的地址
	BTNode* ret = NULL;
	//返回左边
	ret=TreeFind(root->left, x);
	//看左边是否为空,不为空就说明找到了,直接返回给上一级地址
	if (ret)
	{
		return ret;
	}
	
	ret = TreeFind(root->right, x);
	//看右边是否为空,不为空就说明找到了,直接返回给上一级地址
	if (ret)
	{
		return ret;
	}
	//要是走到这个位置也没有返回说明没有找到值
	return NULL;
}

层序遍历(LevelOrder)

上一层带下一层
先进先出。

void LevelOrder(BTNode* root)
{
	//首先初始化
	Que q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		//取头的数据
		BTNode* front = QueueFront(&q);
		printf("%d ", front->val);
		//带入左右子树
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
		QueuePop(&q);
	}
	printf("\n");
}

优先遍历

深度优先遍历(DFS)

前序遍历

广度优先遍历(BFS)

层序遍历,配合队列

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

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

相关文章

小红书达人怎么对接,博主沟通流程汇总!

想要在小红书平台进行宣推&#xff0c;就离不开博主的帮助。一般来说&#xff0c;与小红书博主沟通&#xff0c;分为意向沟通、下单沟通、内容沟通和数据沟通方面。今天为大家分享下小红书达人怎么对接&#xff0c;博主沟通流程汇总&#xff01; 一、意向沟通 意向沟通是小红书…

MyBatis中怎样查看执行的sql语句日志?

MyBatis中怎样查看执行的sql语句日志&#xff1f; 在mybatis中我们执行sql语句是通过动态代理接口执行的&#xff0c;我们并不能看到内部的sql的执行情况&#xff0c;所以你执行完一个接口之后&#xff0c;你也不知道sql到底有没有执行&#xff1f;以及sql执行的对不对&#x…

【试题016】逗号表达式例题

1.题目&#xff1a;设int a,b;,则表达式(b2,a5,b,ab)的值是 &#xff1f; 2.代码分析&#xff1a; #include <stdio.h> int main() {//设int a,b;,则表达式(b2,a5,b,ab)的值是 ()int a, b;printf("%d\n", (b 2, a 5, b, a b));//分析&#xff1a;逗号表达…

基于Java+Springboot+Vue前后端分离的商品进销存管管理系统

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

dubbogo-02 将服务注册到nacos

增加dubbo配置 dubbo:application:name: gotestmodule: gotestgroup: daoorganization: qiudaozhangowner: qiudaozhangversion: 0.1environment: proregistries:nacosWithCustomGroup:protocol: nacos # 注册中心选择 nacosaddress: 127.0.0.1:8848 # nacos ipgroup: RIDE # …

3、Kafka Broker

4.1 Kafka Broker 工作流程 4.1.1 Zookeeper 存储的 Kafka 信息 &#xff08;1&#xff09;启动 Zookeeper 客户端。 [hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh&#xff08;2&#xff09;通过 ls 命令可以查看 kafka 相关信息。 [zk: localhost:2181(CONNECTED) 2] ls /kaf…

最爱问的高频ConcurrentHashMap原理,你会了吗?

1、ConcurrentHashMap 是什么&#xff1f;它的工作原理是什么&#xff1f; ConcurrentHashMap 是Java中的一个线程安全的散列表实现。与HashMap不同&#xff0c;ConcurrentHashMap支持多个线程同时访问和修改散列表&#xff0c;而不需要任何额外的同步机制。 ConcurrentHashM…

C++ vector类模拟实现

目录 一、成员变量 二、构造函数 1.默认构造 2.拷贝构造 3.迭代器构造 4.使用n个值构造 5.赋值拷贝 三、析构函数 四、vector重要成员函数 1.size和capacity函数 2.reserve函数 3.resize函数 4.push_back函数 5.insert函数 6.erase函数 7.重载operator[] 一、成…

html 按钮点击倒计时,限制不可点击

html 按钮点击倒计时&#xff0c;限制不可点击 e94cbabd25cfc7f3f53a50a235734c22.jpg <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title></title></head&…

Ubuntu20.4 设置代理

主要是涉及2个代理 涉及apt 可以在、/etc/apt/apt.conf 中进行修改 在系统全局可以在/etc/profile中进行修改

2023年最新全国各省行政区划数据(省-市-区县-乡镇-村)

背景 现实情况&#xff0c;在信息系统开发、电子商务平台、app等等相关软件开发&#xff0c;都会设计到行政区数据联动&#xff0c;这里已经爬好全国各省行政区划数据可供下载。 数据来源 内容为2023年全国统计用区划代码&#xff08;12位&#xff09;和城乡分类代码&#xff…

跨境电商:产业带的深度赋能

近年来&#xff0c;中国跨境电商平台崭露头角&#xff0c;成为推动国内产业带转型升级和出海的新引擎。这一充满活力的领域不仅让中国制造走向世界&#xff0c;也为国内众多产业提供了数字化升级的机会&#xff0c;实现了“小单快反”和按需供应。 专业跨境电商平台如SHEIN和阿…

RabbitMQ整理

MQ(Message Queue)&#xff1a;是队列&#xff0c;也是跨进程的通信机制&#xff0c;用于上下游传递信息 FIFO(First In First Out)&#xff1a;先进先出 RabbitMQ访问&#xff1a;http://127.0.0.1:15672/ 默认账号密码&#xff1a;guest 优势&#xff1a;流量削峰&#x…

计网----使用代码实现C/S模型的数据通信,IP地址分类,子网掩码,网关,广播地址,非默认子网掩码,子网划分的常见问题

计网----使用代码实现C/S模型的数据通信,IP地址分类,子网掩码,网关,广播地址,非默认子网掩码,子网划分的常见问题 一.使用代码实现C/S模型的数据通信&#xff08;七层网络系统的博客https://blog.csdn.net/m0_73483024/article/details/133916201?spm1001.2014.3001.5502的后…

completablefuture的使用

CompletableFuture使用详解 【Java异常】Variable used in lambda expression should be final or effectively final CompletableFuture原理与实践-外卖商家端API的异步化 项目描述 项目接口需要从下游多个接口获取数据&#xff0c;并且下游的网络不稳定还会涉及到循环调用…

DC电源模块的模拟电源对比数字电源的优势有哪些?

BOSHIDA DC电源模块的模拟电源对比数字电源的优势有哪些&#xff1f; DC电源模块是一种电子元件&#xff0c;用于将交流电转换为直流电&#xff0c;以供电路板、集成电路等电子设备使用。在直流电源模块中&#xff0c;有模拟电源和数字电源两种类型。 模拟电源是一种传统的电源…

如何使双核心的ESP32开启双核功能同时执行多任务

如何使双核心的ESP32开启双核功能同时执行多任务 简介查看ESP32当前哪一个内核在执行任务双核同时执行任务总结 简介 ESP32-WROOM-32模组内置两个低功耗 Xtensa 32-bit LX6 MCU&#xff0c;两个 CPU 核&#xff08;core 0与core 1&#xff09;可以被单独控制。可以在两个内核上…

PHP数据加密传输和存储问题

PHP数据加密的类型 md5()&#xff0c;sha1()&#xff0c;crypt() 双md5加密加盐

2、Kafka 生产者

3.1 生产者消息发送流程 3.1.1 发送原理 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。在 main 线程 中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c; Sender 线程不断从 RecordAccumulator 中…