图解AVL树的旋转操作

news2024/11/19 13:30:43

目录

AVL树的概念

AVL树结点的定义

AVL的插入

AVL树的旋转

左单旋

右单旋

左右双旋

右左双旋

AVL树的查找


 

AVL树的概念

二叉搜索树的缺点:

        当构建二叉搜索树的数据有序或接近有序时二叉搜索树会退化为单链表。例如,当插入数据1,2,3,4,5,6,7,10,9,8时。会的到以下搜索二叉树。

在此二叉树中查找数据就相当于在单链表中查找数据,效率非常低下。

为了解决当构建二叉搜索树的数据有序或接近有序时二叉搜索树会退化为单链表的问题,在1962年两位俄罗斯数学家G.M. Adelson-Velsky和E.M. Landis提出通过旋转操作来使树保持二叉树的任何节点的两棵子树的高度最大差别为1,即任意节点的左右子树高度差的绝对值不超过1。

并命名为AVL树。AVL树是一种自平衡二叉搜索树。它的特点是为了保持这个平衡特性,在插入或删除节点时,AVL树会通过旋转操作来使树平衡。这种自平衡机制使得AVL树的查找、插入、删除等操作的时间复杂度始终保持在O(log n)级别。

AVL树是在搜索二叉树中增加特性,它仍然满足搜索二叉树性质。    

对平衡因子(balance  factor)的理解:

AVL树可以是一颗空树也可以是一棵具有以下性质的一颗二叉搜索树:

1.树的左右子树都是AVL树。

2.树的左右子树的高度差(平衡因子)的绝对值不超过1(-1/0/1)。

距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。

平衡二叉树的实现原理:

平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,更新平衡因子,检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的连接关系,进行相应的旋转,使之成为新的平衡子树。

总结:

1、插入新结点。

2、更新平衡因子。

3、判断是否平衡。

4、不平衡则进行旋转操作,再次更新平衡因子。

AVL树结点的定义

我们将树的结点定义为三叉链结构,方便实现回溯更新结点的平衡因子,并定义平衡因子为右子树高度-左子树高度。

template<class K,class V>
struct AVLTreeNode
{
	//三叉链结构
	AVLTreeNode<K, V> _left;//左结点
	AVLTreeNode<K, V> _right;//右结点
	AVLTreeNode<K, V> _parent;//父结点


	//存储的键值
	pair<K, V> _KV;
	
	//平衡因子
	int _bf;//右子树的高度-左子树的高度
	AVLTreeNode(const pair<K,V>&kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}

AVL的插入

AVL树的插入与搜索二叉树的插入一样,从根结点开始按照以下方法找到待插入位置。有以下三个步骤:

待插入结点的key值比当前结点小就插入到该结点的左子树。
待插入结点的key值比当前结点大就插入到该结点的右子树。
待插入结点的key值与当前结点的key值相等就插入失败。

如此进行下去,直到找到与待插入结点的key值相同的结点判定为插入失败,或者最终走到空树位置进行结点插入。
找到待插入位置后,将待插入结点插入到树中。

代码:

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)//空树
		{
			_root = new Node(kv);//新增结点为根结点
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)//新增结点key值大于当前结点的key值往左子树去找待插入的位置
			{
				parent = cur;
				cur = cur->_left;//往左子树去找待插入的位置
			}
			else if (cur->_kv.first > kv.first)//新增结点key值小与当前结点的key值往右子树去找待插入的位置
			{
				parent = cur;
				cur = cur->_right;//往右子树去找待插入的位置
			}
			else//key值冗余
			{
				return false;
			}
		}
		//待插入结点的key值大于parent的key值
		//则往右插入
		//待插入结点的key值小于parent的key值
		//则往左插入
		cur = new Node(kv);
		if (parent->_kv.first>cur->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;//将新增结点与父结点关联
		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)//新增结点插入父结点的右边 
			{
				parent->bf++;
			}
			else//新增结点插入父结点的左边 
			{
				parent--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)//继续往上判断更新
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 0)//左右子树一样高  不再更新
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//平衡因子的绝对值超过1  需要进行旋转处理
			{
				if (parent->_bf == 2 && cur->_bf == 1)//左单旋处理
				{
					//将以parent结点为根结点的最小不平衡子树进行左单旋处理
					RotateL(parent);
				}
				if (parent->_bf == -2 && cur->_bf == -1)//右单旋处理
				{
					//将以parent结点为根结点的最小不平衡子树进行右单旋处理
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋处理
				{
					//将以parent结点为根结点的最小不平衡子树进行左旋处理
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//右左双旋处理
				{
					//将以parent结点为根结点的最小不平衡子树进行左旋处理
					RotateLR(parent);
				}
				else//出现其他情况  就是在插入新增结点前就出现错误
				{
					assert(false);
				}
			}



		}

		return true;

	}

平衡因子的更新

若结点插入成功后,新增结点只会的对祖先的平衡因子有影响,但一个结点的平衡因子是否要更新,与该结点的左右子树的高度有关,所以并不是每次新增结点后,所有的祖先都要更新平衡因子。

如图:

新增结点使结点7的右子树增高1,所以结点7的平衡因子更新为1-0=1;

新增结点使结点8的左子树增高1,所以结点8的平衡因子更新为2-2=0;

新增结点并没有使结点6的左右子树增高,所以结点6的平衡因子不更新;

所以我们插入结点后需要倒着往上更新平衡因子,更新规则如下:

新增结点在parent的右边,parent的平衡因子++。
新增结点在parent的左边,parent的平衡因子−− 。


每更新完一个结点的平衡因子后,都需要进行以下判断:

如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子,直到根结点。

若parent更新后的平衡因子为-1或1,则只有0经过−−/++操作后会变成-1/1,说明新结点的插入使得parent的左子树或右子树增高了,即改变了以parent为根结点的子树的高度,从而会影响parent的父结点的平衡因子,因此需要继续往上更新平衡因子。

如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了。

若parent更新后的平衡因子为0,只有-1/1经过++/−−操作后会变成0,说明新结点插入到了parent左右子树当中高度较矮的一棵子树,插入后使得parent左右子树的高度相等了,此操作并没有改变以parent为根结点的子树的高度,从而不会影响parent的父结点的平衡因子,因此无需继续往上更新平衡因子。


如果parent更新后的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。

此时parent结点的左右子树高度之差的绝对值已经超过1了,不满足AVL树的要求,因此需要进行旋转处理。
 

注意:我们将树的结点定义为三叉链结构是为了方便通过cur=parent;parent=parent->_parent;进行回溯更新平衡因子。当parent的平衡因子为2/-2时,那么上一次更新的结点的平衡因子一定不为0,因为如果为0的话就会在上一次更新完成后停止更新,而新增结点的父结点的平衡因子更新后一定为-1/0/1,而不可能是2/-2,因为新增结点只会插入到一个空树中去,在新增结点插入前其父结点的状态有以下几种可能:

1、父结点是一个左右子树均为空的叶子结点,其平衡因子是0,新增结点插入后其平衡因子更新为-1/1。
2、父结点是一个左子树为空的结点,其平衡因子是1,新增结点插入到其父结点的空子树当中,使得其父结点左右子树当中较矮的一棵子树增高了,新增结点后其平衡因子更新为0。

3、父结点是一个右子树为空的结点,其平衡因子是-1,新增结点插入到其父结点的空子树当中,使得其父结点左右子树当中较矮的一棵子树增高了,新增结点后其平衡因子更新为0。

综上所述,当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。

AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

所以我们可以将旋转处理分为以下四类:
1、当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋。


2、当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋。


3、当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋。


4、当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋。

左单旋

在插入前,AVL树是平衡的,新节点插入到60的右子树(注意:此处不是右孩子)中,60右子树增加了一层,导致以30为根的二叉树不平衡,要让30平衡,只能将30右子树的高度减少一层,左子树增加一层,即将右子树往上提,这样30转下来,因为30比60小,只能将其放在60的左子树,而如果60有左子树,左子树根的值一定大于30,小于60,只能将其放在30的右子树,旋转完成。

更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

 1. 60节点的左孩子可能存在,也可能不存在。

2. 30可能是根节点,也可能是子树,如果是根节点,旋转完成后,要更新根节点; 如果是子树,可能是某个节点的左子树,也可能是右子树。

操作步骤:

1、将subRL链接称为parent的右子树。

2、让parent成为subR的左子树。

3、让subR成为这颗最小不平衡子树的根。

4、更新平衡因子。

左单旋代码如下:

void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;
        

       // 1、将subRL链接称为parent的右子树。
		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}
        //2、让parent成为subR的左子树。
		subR->_left = parent;
		parent->_parent = subR;
        //3、让subR成为这颗最小不平衡子树的根。
		if (pparent == nullptr)//parent为根结点
		{
			_root = subR;//更新subR为根结点
			_root->_parent = nullptr;
		}

		else//parent不为根结点
		{
			if (pparent->_left == parent)
			{
				subR->_parent==pparent;
				pparent->_left = subR;
			}
			else
			{

				subR->_parent == pparent;
				pparent->_right = subR;
			}
			
		}
        //4、更新平衡因子。
		subL->_bf = parent->_bf = 0;

	}

右单旋

在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成。

更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

 1. 30节点的右孩子可能存在,也可能不存在。

2. 60可能是根节点,也可能是子树,如果是根节点,旋转完成后,要更新根节点; 如果是子树,可能是某个节点的左子树,也可能是右子树。

操作步骤:

1、将subRL链接称为parent的右子树。

2、让parent成为subR的左子树。

3、让subR成为这颗最小不平衡子树的根。

4、更新平衡因子。


	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;
        //1、将subRL链接称为parent的右子树。

		parent->_left = subLR;
		if (subLR)
		{
			subRL->_parent = parent;
		}

        //2、让parent成为subR的左子树。

		subL->_right = parent;
		parent->_parent = subL;

        //3、让subR成为这颗最小不平衡子树的根。

		if (pparent==nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
				subL->_parent = pparent;
			}
			if (pparent->_right == pparent)
			{
				pparent->_right = subL;
				subL->_parent = pparent;
			}
		}
        //4、更新平衡因子。
		subL->_bf = parent->_bf = 0;
	}
左右双旋

右左双旋的步骤:

1、将以subL为根结点的子树进行左单旋。

2、将以parent为根结点的不平衡子树进行右单旋。

3、更新平衡因子。

经过左右双旋转,需要更新平衡因子,平衡因子的更新需要根据新增结点的插入的为位置情况进行区分:

1、当新增结点插入subLR的右子树,subLR原始平衡因子是1时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0。

2、当新增结点插入subLR的左子树时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0。

3、当subLR就是新增结点时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0。

 左右双旋的代码:

void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(subL);
		RotateR(parent);

		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf == 1;
			subLR->_bf = 0;
			subL->_bf=0
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0
		}
		else
		{
			assert(false);
		}
	}

右左双旋

右左双旋的步骤:

1、将以subR为根结点的子树进行右单旋。

2、将以parent为根结点的不平衡子树进行左单旋。

3、更新平衡因子。

经过右左双旋转,需要更新平衡因子,平衡因子的更新需要根据新增结点的插入的为位置情况进行区分:

1、当新增结点插入subRL的右子树,subRL原始平衡因子是1时,左右双旋后parent、subR、subRL的平衡因子分别更新为-1、0、0。

2、当新增结点插入subRL的左子树,subRL原始平衡因子是-1时,左右双旋后parent、subR、subRL的平衡因子分别更新为0、-1、0。

 3、当subRL就是新增结点时,左右双旋后parent、subL、subRL的平衡因子分别更新为0、0、0。

右左双旋代码: 

	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL-> = subR->_left;
		int bf = subRL->_bf;
		
		// 1、将以subR为根结点的子树进行右单旋。
		RotateRL(subR);
		//2、将以parent为根结点的不平衡子树进行左单旋。
		RotateRL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

AVL树的查找

与二叉搜索树的查找方式一样AVL树的查找逻辑如下:
从根结点开始,若树为空树,则树中没有key值查找失败,返回nullptr。
若key值小于当前结点的值,则往当前结点的左子树当中进行查找。
若key值大于当前结点的值,则往当前结点的右子树当中进行查找。
若key值等于当前结点的值,则查找成功,返回对应结点。

//AVL树的查找函数
	Node* Find(const K& key)
	{
		//从根结点开始,若树为空树,
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_kv.first) //若key值小于当前结点的值,则往当前结点的左子树当中进行查找。
			{
				cur = cur->_left; //在该结点的左子树当中查找
			}
			else if (key > cur->_kv.first) //若key值大于当前结点的值,则往当前结点的右子树当中进行查找。
			{
				cur = cur->_right; //在该结点的右子树当中查找
			}
			else //若key值等于当前结点的值,则查找成功,返回对应结点。
			{
				return cur; //返回该结点
			}
		}
		return nullptr; //树中没有key值查找失败,返回nullptr。
	}

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

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

相关文章

Image resize, resample,rescale傻傻搞不懂看这里,大白话

Image resize, resample&#xff0c;rescale傻傻搞不懂 大白话 大家好&#xff0c;在学习图像处理的时候我们可能都对rescale&#xff0c;resize&#xff0c;resample感到困惑&#xff0c;在这里让我们进行学习一下。首先让我们现了解一下他们的大白话。resize就是改变大小&am…

项目部署之Jenkins

1. Jenkins介绍 Jenkins 是一款流行的开源持续集成&#xff08;Continuous Integration&#xff09;工具&#xff0c;广泛用于项目开发&#xff0c;具有自动化构建、测试和部署等功能。官网&#xff1a; http://jenkins-ci.org/。 Jenkins的特征&#xff1a; 开源的 Java语言…

ELF 文件介绍

前言 如果需要了解 动态加载,需要先了解需要动态加载的目标(对象):ELF 文件 ELF 文件用于 Linux、Unix等系统平台,与 Windows 上的 exe (PE)或者 DLL 文件格式不一样 ELF 简介 全称:Executable and Linking Format 中文:可执行和链接格式 ELF 文件格式是 Linux/Unix 通…

第一次微生物学实验

第一次微生物学实验 文章目录 前言一、显微镜油镜的使用以及细菌的简单染色法1.1. 实验目的1.2. 实验原理1.3. 实验材料1.4.实验步骤1.5. 实验结果及讨论1.5.1 实验结果1.5.2. 讨论 二、细菌的革兰氏染色以及芽孢染色法2.1. 实验目的2.2. 实验原理2.2.1. 革兰氏染色原理2.2.2. …

进程互斥的软件实现方法,硬件实现方法以及互斥锁

1.进程互斥的软件实现方法 1.单标志法 1.算法思想: 两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。 也就是说每个进程进入临界区的权限只能被另一个进程赋予。 2.例子 因此&#xff0c;该算法可以实现“同一时刻最多只允许一个进程访问临界区”。 3.主要…

网络初识必知会

局域网&#xff1a;把一些设备通过交换机/路由器连接起来 广域网&#xff1a;把更多的局域网也相互连接&#xff0c;当网络规模足够大的 交换机&#xff1a;组网过程中的重要设备&#xff01; 路由器&#xff1a;组网过程中的重要设备&#xff01; IP地址&#xff1a;描述一…

网络相关的基础知识整理

一、历史 1.1 早期阿帕网特点⭐⭐⭐ 没有纠错功能不能互联不同类型的计算机和不同类型的操作系统 1. 2 TCP/IP协议 点击【此处】跳转&#x1f517; TCP&#xff1a;用来检测网络传输中差错的传输控制协议IP&#xff1a;专门负责对不同网络进行互联的互联网协议&#xff08…

枚举,进制转换,char*,补码,算法,链表,位运算,NULL的含义

目录 什么是枚举 进制转换 字符指针 补码 算法 链表 算法 位运算符 ​编辑NULL的含义 什么是枚举 进制转换 4个二进制位组合才能有16个状态 字符指针 补码 编码就是解决这个东西到底用哪个二进制表示 不够位数指的是比如32位&#xff0c;前面都补1 #include <iostre…

Linux网络编程5-epoll模型

Linux网络编程5-epoll模型 1.epoll相关函数2.epoll服务器流程3.epoll服务器代码实现4.LT模式与ET模式5.ET模式一次性读完数据的实现6.epoll反应堆思想7.epoll反应堆代码实现8.epoll反应堆代码分析 1.epoll相关函数 #include <sys/epoll.h>int epoll_create(int size); /…

Bentley STAAD.Pro 2023 中文版 安装交流怎么安装?含解答

产品介绍 STAAD.Pro 是一个集结构建模、结构分析和结构设计于 一体的通用结构有限元程序。不仅具有常见的图形表格窗口 界面&#xff0c;而且有一个保存和修改建模分析设计命令的命令 编辑器。 另外&#xff0c;内置了几何建模向导和异形截面向导两个工具&#xff0c;为建模带来…

Gitlab+Jenkins自动化部署,解放双手

项目打包 ​ 在部署项目前需要对源码进行打包&#xff0c;一个简单的SpringBoot项目默认是打包为jar包&#xff0c;也就是在pom.xml中的<packaging>jar</packaging>方式&#xff0c;当然也会有一些打包成war包方式&#xff0c;使用外置的Tomcat应用服务器部署war包…

凉鞋的 Godot 笔记 106. 第二轮循环2D 场景视图Label

从这一篇开始&#xff0c;我们开始进行第二轮循环。 这次我们至少能够在游戏运行窗口能看到一些东西。 首先还是在场景窗口进行编辑&#xff0c;先创建一个节点: 在弹出的窗口&#xff0c;我们找到 Control/Label &#xff0c;如下所示: 点击创建&#xff0c;然后我们在 2D 的…

【dp】背包问题

背包问题 一、背包问题概述二、01背包问题&#xff08;1&#xff09;求这个背包至多能装多大价值的物品&#xff1f;&#xff08;2&#xff09;若背包恰好装满&#xff0c;求至多能装多大价值的物品&#xff1f; 三、完全背包问题&#xff08;1&#xff09;求这个背包至多能装多…

有点奇葩的消息——CEO 2 个月之前也离职了

这个消息还是有点奇葩的。 在找工作一段时间后&#xff0c;有原公司的同事联系了下互相通报了下各自的情况。 如有看过&#xff1a;北美 2023 被裁员的感悟 这篇文章的大致都知道公司在4个月前&#xff0c;也就是2023年6月份的时候进行了大规模的裁员&#xff0c;公司也只保留…

多源蒸馏域适应

方法 D是域判别器&#xff0c;C是分类器。阶段3选择更接近目标的源训练样本用来微调C。阶段4对于每个源域&#xff0c;基于阶段2学到的目标编码器提取图像特征。接着结合每个源分类器的不同预测获得最终预测Result( x T x_T xT​) ∑ i 1 N w i C i ′ ( F i T ( x T ) ) \sum…

C++-封装unordered

本期我们来封装实现unordered系列&#xff0c;需要前置知识&#xff0c;没有看过哈希的建议先看看哈希&#xff0c;而且哈希的代码都在这里面&#xff0c;一会要用到 C-哈希Hash-CSDN博客 目录 代码实现 迭代器 const迭代器 全部代码 代码实现 首先我们要把V改为T&#xff…

【回顾一下Docker的基本用法】

文章目录 回顾一下Docker的基本用法1.初识Docker1.1.什么是Docker1.1.1.应用部署的环境问题1.1.2.Docker解决依赖兼容问题1.1.3.Docker解决操作系统环境差异1.1.4.小结 1.2.Docker和虚拟机的区别1.3.Docker架构1.3.1.镜像和容器1.3.2.DockerHub1.3.3.Docker架构1.3.4.小结 1.4.…

计算摄像技术02 - 颜色空间

一些计算摄像技术知识内容的整理&#xff1a;颜色视觉与感知特性、颜色空间和基于彩色滤镜阵列的彩色感知。 文章目录 一、颜色视觉与感知特性 &#xff08;1&#xff09;色调 &#xff08;2&#xff09;饱和度 &#xff08;3&#xff09;明度 二、颜色空间 &#xff08;1&…

2023版 STM32实战7 通用同步/异步收发器(串口)F103/F407

串口简介和习惯 -1-通用同步异步收发器 (USART) 能够灵活地与外部设备进行全双工数据交换&#xff0c;满足外部设备对工业标准 NRZ 异步串行数据格式的要求。 -2-硬件流控制一般是关闭的 -3-波特率指单位时间传输bit个数 -4-数据位一般是8位 -5-一般无校验位 编写代码思路 -1-参…

位图/布隆过滤器

一、位图 1.1位图的概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常是用来判断某个数据存不存在的。 1.2位图的实现 template<size_t N>class bitset{public:bitset(){//需要N个比特位&#xff0c;…