map和set(二)——AVL树的简单实现

引入

二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。简单来说就是之前二叉搜索树由于可能在某一个节点上一直深入,按照最坏情况算这的时间复杂度就高了起来,而AVL树这其中之一的平衡树

1.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度

AVL树又称高度平衡二叉搜索树

任何AVL树都满足一下条件:

  • 它的左右子树都是AVL树
  • 任何树及其子树的高度差(也就是平衡因子)的绝对值不超过1

 比如:

当然平衡因子不一定是必须的,它只是一种控制方式(让我们更便捷地控制这棵树) 

为何是不超过1,而不是0呢?0不是更加平衡吗?

由于树的节点是一个个插入的,无法保证绝对的平衡(有些情况无法满足高度差为0);因此高度差不超过1

2.AVL树节点的定义

和二叉搜索树类似,只不过多了个平衡因子

//AVL树的节点
template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>* _left;
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K,V>* _parent;
	int _bf = 0;//平衡因子
	pair<K, V> _kv;

	//构造
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
		, _kv(kv)
	{}

};

3.AVL树的插入

3.1插入

AVL树就是在二叉搜索树的基础上引进了平衡因子(bf),因此AVL树也可以看做二叉搜索树

AVL树的插入过程可以分为两步:


  1. 以二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

二叉搜索树的方式插入 

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 (kv.first > cur->_kv.first)//插入的值比遍历的值大
			{
				parent = cur;//cur往下遍历时,父节点同时要往下走
				cur = cur->_right;//往右走
			}
			else if (kv.first < cur->_kv.first)//插入的值比遍历的值小
			{
				parent = cur;
				cur = cur->_left;//往左走
			}
			else//说明插入的值已经存在,return false
			{
				return false;
			}

		}
		//走到这说明已经找到可以插入的地方 
		//创建一个新节点
		cur = new Node(kv);
		//判断插入的节点该连接到父节点的左还是右
		if (cur->_kv.first > parent->_kv.first)//cur 的值大于父节点的值,连右
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		//接下来就是判断平衡因子的时候了
		return true;
	}

 判断平衡因子

平衡因子 = 右子树高度-左子树高度

插入节点会影响哪些节点的平衡因子呢?新增节点的部分祖先

更新原则:

若cur是此父节点的左子树(节点)那么父节点的平衡因子-- ,是右子树(节点)那么父节点平衡因子++

是否继续更新取决于父节点的高度是否变化,是否会影响爷爷节点
一直往上走直至parent为nullptr(根节点的父节点为空)或者平衡因子为0或者平衡因子为2(需要旋转)

当cur(新增节点插入后),有三种情况

情况1:

更新后 父节点(parent)的平衡因子(bf)为 0 ,parent所在子树高度不变,不会影响爷爷;说明更新前parent的bf为1或-1,往父节点矮的那边插入节点,左右均衡,parent所在子树的高度不变


情况2:

更新后 父节点的平衡因子为1或-1,parent所在子树高度改变了,会影响爷爷,继续往上更新;说明更新前parent的bf为0(本身平衡了),往p的任意一边插入,使父节点变得不均衡,但不违反规则


情况3:

更新后父节点的平衡因子为2或-2,说明父节点所在的子树违反了平衡规则,需要旋转处理

		//接下来就是判断平衡因子的时候了
		cur->_parent = parent;
		while (parent)
		{
			if (cur == parent->_left)//cur在父左 父bf--
			{
				parent->_bf--;
			}
			else//cur在父右 父bf++
			{
				parent->_bf++;
			}
			if (parent->_bf == 0)//父bf==0 break
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)//父bf为1 or -1继续往上
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//平衡因子为2 只能旋转了
				//右高左低 左单旋
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//左高右低 右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//两边都高左右双旋
				{
					RotateLR(parent);
				}
				else//右左双旋
				{
					RotateRL(parent);
				}
			}
			else
			{

				assert(false);
			}
		}

合起来

	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 (kv.first > cur->_kv.first)//插入的值比遍历的值大
			{
				parent = cur;//cur往下遍历时,父节点同时要往下走
				cur = cur->_right;//往右走
			}
			else if (kv.first < cur->_kv.first)//插入的值比遍历的值小
			{
				parent = cur;
				cur = cur->_left;//往左走
			}
			else//说明插入的值已经存在,return false
			{
				return false;
			}

		}
		//走到这说明已经找到可以插入的地方 
		//创建一个新节点
		cur = new Node(kv);
		//判断插入的节点该连接到父节点的左还是右
		if (cur->_kv.first > parent->_kv.first)//cur 的值大于父节点的值,连右
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		//接下来就是判断平衡因子的时候了
		cur->_parent = parent;
		//若cur是此父节点的左子树(节点)那么父节点的平衡因子-- ,是右子树(节点)那么父节点平衡因子++
		//一直往上走直至parent为nullptr(根节点的父节点为空)或者平衡因子为0或者平衡因子为2(需要旋转)
		while (parent)
		{
			if (cur == parent->_left)//cur在父左 父bf--
			{
				parent->_bf--;
			}
			else//cur在父右 父bf++
			{
				parent->_bf++;
			}
			if (parent->_bf == 0)//父bf==0 break
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)//父bf为1 or -1继续往上
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//平衡因子为2 只能旋转了
				//右高左低 左单旋
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//左高右低 右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//两边都高左右双旋
				{
					RotateLR(parent);
				}
				else//右左双旋
				{
					RotateRL(parent);
				}
			}
			else
			{

				assert(false);
			}
		}
		return true;
	}

3.2旋转

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

左单旋——新节点插入较高右子树的右侧---右右

void RotateL(Node* parent)//左单旋
	{
		//sub是parent ,subR是parent的右节点,subRL是subR的左节点
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;//父节点的右节点指向subRL
		//subRL可能为空节点,也就是这颗树(子树)只有sub(parent节点)、subR、以及新增节点这三个节点 如果subRL为空,就不用将其父节点指向sub了
		if (subRL)
		{
			subRL->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		parent->_parent = subR;//把父节点的父节点指向subR
		//父节点(sub)有可能是一颗树(子树)的根节点 
		//如果(sub)是一颗树的根节点的话 subR直接为根节点,subR的parent直接是空
		if (parent = _root)
		{
			subR = _root;
			subR->_parent = nullptr;
		}
		else//sub是一颗子树的根节点 
		{
			//得看sub是其父节点的左节点还是右节点
			//然后subR连接sub的父节点
			if (parent == ppnode->_left)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
		//sub和subR平衡因子置为0
		subR->_bf = 0;
		parent->_bf = 0;

	}

右单旋——新节点插入较高左子树的左侧---左左

//右单旋思路和左单旋差不多
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			subL = _root;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == ppnode->_left)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
		subL->_bf = 0;
		parent->_bf = 0;
	}

左右双旋—— 新节点插入较高左子树的右侧---左右(先左单旋再右单旋)

左右两边都高单旋解决不了问题

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//subLR的平衡因子不同时对其它节点的平衡因子的改变也不同
		int bf = subLR->_bf;

		RotateL(parent->_left);//先走左单旋
		RotateR(parent);//再走右单旋

		if (bf == -1)//若subLR的bf为-1,则新增节点是subLR的左子树
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)//若subLR的bf为1,则新增节点是subLR的右子树
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0) //若subLR的bf为0,那么subLR其本身就是新增节点
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋——新节点插入较高右子树的左侧---右左(先右单旋再左单旋)

void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(parent);
		if (bf == -1)
		{
			subRL->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

 4.判断是否为AVL树

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

1. 验证其为二叉搜索树 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << "[" << root->_bf << "]" << endl;
		_Inorder(root->_right);
	}

	void Inorder()
	{
		_Inorder(_root);
	}

2. 验证其为平衡树 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确

	int _Height(Node* root)
	{
		if (root == nullptr)
			return;
		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);
		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;

	}
	int Height()
	{
		return _Height(_root);
	}

	bool _IsAVLTree(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	
	int leftHeight = Height(root->_left);

	int	rightHeight = Height(root->_right);

	if (abs(leftHeight - rightHeight) >= 2)
	{
		cout << "不平衡" << endl;
		return false;
	}
	if (rightHeight - leftHeight != root->_bf)
	{
		cout << root->_kv.first << "平衡因子异常" << endl;
		return false;
	}
	return _IsAVLTree(root->_left)&&_IsAVLTree(root->_right);
}
	bool IsAVLTree()
	{
		return _IsAVLTree(_root);
	}

如果走前序递归的话计算高度和前序递归会存在大量重复,所以还是走后序的同时求高度

后序先走左子树判断平衡返回高度;再走右子树判断平衡返回高度

bool _IsAVLTree(Node* root, int& height)
	{
		if (root == nullptr)
		{
			height = 0;
			return true;
		}
		//走个后序
		int leftHeight, rightHeight = 0;
		if (!_IsAVLTree(root->_left, leftHeight) || !_IsAVLTree(root->_right, rightHeight))
		{
			return false;
		}
		if (abs(leftHeight - rightHeight) >= 2)
		{
			cout << "不平衡" << endl;
			return false;
		}
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

		return true;
	}
	bool IsAVLTree()
	{
		int height = 0;
		return _IsAVLTree(_root, height);
	}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

可免费使用的AI平台汇总 + 常用赋能科研的AI工具推荐

赋能科研&#xff0c;AI工具助你飞跃学术巅峰&#xff01;(推荐收藏) 文章目录 赋能科研&#xff0c;AI工具助你飞跃学术巅峰&#xff01;(推荐收藏)一、可免费使用的AI平台汇总1. ChatGPT2. New Bing3. Slack4. POE5. Vercel6. 其他平台7. 特定功能平台8. 学术资源平台9. 中文…

14 OpenCv边缘处理

文章目录 卷积边界问题边缘处理copyMakeBorder 算子代码 卷积边界问题 图像卷积的时候边界像素&#xff0c;不能被卷积操作&#xff0c;原因在于边界像素没有完全跟kernel重叠&#xff0c;所以当3x3滤波时候有1个像素的边缘没有被处理&#xff0c;5x5滤波的时候有2个像素的边缘…

华为OD机试C卷“跳步-数组”Java解答

描述 示例 算法思路1 不断移动数组将元素删去&#xff08;并未彻底删除&#xff0c;而是将数字元素前移实现“伪删除”&#xff09;这样删除元素的位置就呈现一定规律&#xff0c;详细见下图&#xff08;潦草的画&#xff09; 答案1 import java.util.*;public class Main {…

蓝桥杯刷题5--GCD和LCM

目录 1. GCD 1.1 性质 1.2 代码实现 2. LCM 2.1 代码实现 3. 习题 3.1 等差数列 3.2 Hankson的趣味题 3.3 最大比例 3.4 GCD 1. GCD 整数a和b的最大公约数是能同时整除a和b的最大整数&#xff0c;记为gcd(a, b) 1.1 性质 GCD有关的题目一般会考核GCD的性质。   …

国家医保局开通异地就医备案办理功能,哪些人群适用?

2022年6月30日&#xff0c;国家医保局会同财政部印发《关于进一步做好跨省异地就医基本医疗保险直接结算工作的通知》&#xff08;民保发〔2022〕30号&#xff09;。 22号文&#xff08;以下简称《通知》&#xff09;。 《通知》明确&#xff0c;长期跨省异地居住或临时跨省外出…

PostgreSQL数据优化——死元组清理

最近遇到一个奇怪的问题&#xff0c;一个百万级的PostgreSQL表&#xff0c;只有3个索引。但是每次执行insert或update语句就要几百ms以上。经过查询发现是一个狠简单的问题&#xff0c;数据库表死元组太多了&#xff0c;需要手动清理。 在 PG 中&#xff0c;update/delete 语句…

Axure原型设计项目效果 全国职业院校技能大赛物联网应用开发赛项项目原型设计题目

目录 前言 一、2022年任务书3效果图 二、2022年任务书5效果图 三、2022年国赛正式赛卷 四、2023年国赛第一套样题 五、2023年国赛第二套样题 六、2023年国赛第三套样题 七、2023年国赛第四套样题 八、2023年国赛第七套样题 九、2023年国赛正式赛题&#xff08;第八套…

点赞功能真的有必要上 Redis 吗?(Mongo、MySQL、Redis、MQ 实测性能对比)

目录 一、你会怎么设计一个点赞功能&#xff1f; 1.1、点赞实现思路 1.2、点赞功能设计 1.2.1、MySQL 单表 1.2.2、单表 MySQL 关联表 1.2.3、MySQL 关联表 mq 1.2.4、redis mq 1.2.5、mongodb 关联文档 二、性能测试 2.1、前置说明 2.2、10 万数据准备 一、你会…

PyTorch完整的神经网络模型训练(使用GPU训练)

1.什么是CUDA&#xff1a; CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的一种并行计算平台和编程模型。它允许开发者在NVIDIA GPU上进行通用目的的并行计算&#xff0c;包括深度学习、科学计算、图形处理和加密等任务。 CUDA通过提供一组…

vulhub中Weblogic WLS Core Components 反序列化命令执行漏洞复现(CVE-2018-2628)

Oracle 2018年4月补丁中&#xff0c;修复了Weblogic Server WLS Core Components中出现的一个反序列化漏洞&#xff08;CVE-2018-2628&#xff09;&#xff0c;该漏洞通过t3协议触发&#xff0c;可导致未授权的用户在远程服务器执行任意命令。 访问http://your-ip:7001/consol…

人工智能:探索智慧的未来

目录 前言1 人工智能的简介1.1 人工智能的定义1.2 任务范围1.3 模拟人类认知 2 人工智能发展2.1 起步阶段2.2 发展阶段2.3 繁荣阶段 3 弱人工智能和强人工智能3.1 弱人工智能&#xff08;ANI&#xff09;3.2 强人工智能&#xff08;AGI&#xff09; 4 人工智能主要技术4.1 机器…

【C++11】包装器和bind

文章目录 一. 为什么要有包装器&#xff1f;二. 什么是包装器&#xff1f;三. 包装器的使用四. bind 函数模板1. 为什么要有 bind &#xff1f;2. 什么是 bind ?3. bind 的使用场景 一. 为什么要有包装器&#xff1f; function 包装器&#xff0c;也叫作适配器。C 中的 funct…

Elastic Stack--06--JavaAPI----索引(创建-查询- 删除)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 环境准备添加依赖&#xff1a;HelloElasticsearch JavaAPI-索引1.创建2.查询3.删除 环境准备 添加依赖&#xff1a; <dependencies><dependency><g…

第G3周:CGAN入门|生成手势图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、前置知识 CGAN&#xff08;条件生成对抗网络&#xff09;的原理是在原始GAN的基础上&#xff0c;为生成器和判别器提供 额外的条件信息…

vue3 ref获取子组件显示 __v_skip : true 获取不到组件的方法 怎么回事怎么解决

看代码 问题出现了 当我想要获取这个组件上的方法时 为什么获取不到这个组件上的方法呢 原來&#xff1a; __v_skip: true 是 Vue 3 中的一个特殊属性&#xff0c;用于跳过某些组件的渲染。当一个组件被标记为 __v_skip: true 时&#xff0c;Vue 将不会对该组件进行渲染&am…

ABAP接口-RFC连接(ABAP TO ABAP)

目录 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09;创建ABAP连接RFC函数的调用 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09; 创建ABAP连接 事务代码&#xff1a;SM59 点击创建&#xff0c;填写目标名称&#xff0c;选择连接类型&#xff1a; 填写主机名…

打卡--MySQL8.0 一(单机部署)

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; MySQL 8.0 简介 MySQL 8.0与5.7的区别主要体现在&#xff1a;1、性能提升&#xff1b;2、新的默认…

02-app端文章查看,静态化freemarker,分布式文件系统minIO

app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO 1)文章列表加载 1.1)需求分析 文章布局展示 1.2)表结构分析 ap_article 文章基本信息表 ap_article_config 文章配置表 ap_article_content 文章内容表 三张表关系分析 1.3)导入文章数据库 1.3.1)导入数据…

【vue.js】文档解读【day 2】 | 响应式基础

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 响应式基础声明响应式状态(属性)响应式代理 vs 原始值声明方法深层响应性DOM 更新时机有状态方法 响应式…

电脑数据安全新防线:文件备份的终极指南

一、数据守护者的使命&#xff1a;文件备份的重要性 在数字化日益普及的今天&#xff0c;电脑已成为我们日常生活和工作的必备工具&#xff0c;文件作为我们储存、交流和处理信息的主要载体&#xff0c;其重要性不言而喻。然而&#xff0c;无论是由于硬件故障、软件崩溃&#…