AVL树浅谈

news2024/12/26 21:00:03

前言

大家好,我是jiantaoyab,本篇文章给大家介绍AVL树。

基本概念

AVL树(Adelson-Velsky和Landis树)是一种自平衡的二叉搜索树,得名于其发明者G. M. Adelson-Velsky和E. M. Landis。在AVL树中,任何节点的两个子树的高度最大差别为1,因此它也被称为高度平衡树。

AVL树特点

  1. 二叉搜索树性质:AVL树本质上是一棵二叉搜索树,即每个节点的左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。
  2. 平衡条件:AVL树的每个节点的左子树和右子树的高度差之绝对值不超过1。这是AVL树与其他二叉搜索树的主要区别,保证了树的平衡性,从而优化了查找、插入和删除操作的性能。
  3. 平衡因子:每个节点都有一个平衡因子,定义为该节点的右子树高度减去左子树高度。在AVL树中,平衡因子的取值只能是-1、0或1。

模拟实现

AVL树节点定义

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V> *_left;
	AVLTreeNode<K, V> *_right;
	AVLTreeNode<K, V> *_parent;
	pair<K,V> _kv;  //pair是将2个数据组合成一组数据

	int _bf; //balance factor

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

};

插入操作

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){
				parent = cur;
				cur = cur->_right;
			}

			else if (cur->_kv.first>kv.first){
				parent = cur;
				cur = cur->_left;
			}

			else{
				return false;
			}
		}

			cur = new Node(kv);

			if (parent->_kv.first < kv.first){
				parent->_right = cur;
				cur->_parent = parent;
			}

			else{
				parent->_left = cur;
				cur->_parent = parent;
			}

		
		//控制平衡
		//1、更新平衡因子 
		//2、异常,旋转平衡处理
		//只会影响这条路径,最坏更新到根
		while (parent)
		{
			if (cur == parent->_left){
				parent->_bf--;
			}

			else{
				parent->_bf++;
			}

			if (parent->_bf == 0){
				break;
			}
			//继续更新
			else if (parent->_bf == 1 || parent->_bf == -1){
				cur = parent;
				parent = parent->_parent;
			}

			//旋转处理 
			//1:树平衡了
			//2:树的整体高度降了1
			else if (parent->_bf == 2 || parent->_bf == -2){

				//右单旋
				if (parent->_bf == -2 && cur->_bf == -1){
					RotateR(parent);
				}

				//左单旋
				else if (parent->_bf == 2 && cur->_bf == 1){
					RotateL(parent);
				}

				//双旋左右
				else if (parent->_bf == -2 && cur->_bf == 1){
					RotateLR(parent);
				}
				//双旋右左
				else if (parent->_bf == 2 && cur->_bf == -1){
					RotateRL(parent);
				}

				else{
					assert(false);
				}
				//旋转完之后不影响上面的,可以直接退出
				break;	

			}

			//插入之前,平衡因子有问题
			else{
				assert(false);
			}

		}

		return true;
}

旋转操作

右单旋

image-20240506110209062

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;

		//更新父节点
		if (subLR)
			subLR->_parent = parent;
		Node* parentParent = parent->_parent;
		parent->_parent = subL;

		//如果原来是根
		if (parent == _root){
			_root = subL;
			_root->_parent = nullptr;
		}
		//如果是别人的子树
		else{
			if (parentParent->_left == parent)
				parentParent->_left = subL;
			else
				parentParent->_right = subL;

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

左单旋

void RotateL(Node* parent)
	{

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//原来是根
		if (_root == parent){
			_root = subR;
			subR->_parent = nullptr;
		}
		//原来是别人的子树
		else{
			if (parentParent->_left == parent)
				parentParent->_left = subR;
			else
				parentParent->_right = subR;
			subR->_parent = parentParent;
		}

		subR->_bf = parent->_bf = 0;
	}

左右双旋

image-20240506123437496

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

			RotateL(parent->_left);
			RotateR(parent);
			if (bf == -1)
			{
				parent->_bf = 1;
				subL->_bf = 0;
			}

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

右左双旋

void RotateRL(Node *parent)	
	{
		//   30 (parent)
		//  /   \
		//  a   90(subR)
		//      /   \     
		//    60(subRL)   d
		//   b  c
	
		Node *subR = parent->_right;
		Node *subRL = subR->_left;

		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		//这里看图 变化后的图
		if (bf == 1){
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}

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

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

	}

判断是不是AVL树

int Height(Node *root)
	{
		if (root == NULL)
			return 0;

		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//1.检查是不是搜索二叉树
	//2.检查每一课子树是不是AVL树
	bool _IsBalance(Node *root)
	{
		if (root == NULL)
			return true;

		//当前树检查
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		//平衡因子出问题
		if (rightHeight - leftHeight != root->_bf){
			cout << root->_kv.first << "NOW::" << root->_bf << endl;
			cout << root->_kv.first << "CORRECT::" << rightHeight - leftHeight << endl;
			return false;
		}

		//左右高度差不能超过2
		return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

AVL树性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log n)。

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

AVL树使用场景

  1. 数据库和文件系统的索引
    • 数据库系统经常需要快速检索、插入和删除记录。AVL树作为索引结构,可以加速这些操作,确保查询效率不会因为数据的不均匀分布而降低。
  2. 内存数据库
    • 对于需要快速响应的内存数据库,如Redis,使用AVL树可以确保数据访问的高效性。
  3. 字典或词汇查找
    • 在自然语言处理或文本编辑器中实现自动补全、拼写检查或同义词查找等功能时,AVL树可以提供快速的词汇查找和插入。
  4. 路由表查找
    • 在计算机网络中,路由器需要根据路由表快速查找最佳路径。AVL树可以确保路由查找的高效性。
  5. 搜索引擎
    • 搜索引擎在处理大量网页索引时需要快速检索和更新索引。AVL树可以帮助优化这些操作。
  6. 缓存系统
    • 在实现缓存替换策略(如LRU,即最近最少使用策略)时,AVL树可以帮助维护一个有序的缓存项列表,从而快速确定哪些项应该被替换。
  7. 事件处理系统
    • 在需要按时间顺序处理事件的系统(如日历应用或任务调度器)中,AVL树可以用于维护一个有序的事件列表。
  8. 科学计算和模拟
    • 在科学计算和模拟中,经常需要快速查找、插入或删除数据点。AVL树可以提供一个高效的数据结构来支持这些操作。
  9. 金融交易系统
    • 实现自动补全、拼写检查或同义词查找等功能时,AVL树可以提供快速的词汇查找和插入。

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

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

相关文章

【云原生】Pod 的生命周期(一)

【云原生】Pod 的生命周期&#xff08;一&#xff09;【云原生】Pod 的生命周期&#xff08;二&#xff09; Pod 的生命周期&#xff08;一&#xff09; 1.Pod 生命期2.Pod 阶段3.容器状态3.1 Waiting &#xff08;等待&#xff09;3.2 Running&#xff08;运行中&#xff09;3…

蓝牙连接手机播放音乐的同时传输少量数据,那些蓝牙芯片可以实现呢

简介 蓝牙连接手机播放音乐的同时连接另一蓝牙芯片传输少量数据&#xff0c;那些蓝牙芯片可以实现呢&#xff1f; 这个需求&#xff0c;其实就是双模的需求 简单描述就是:播放音乐的同时&#xff0c;还可以连接ble&#xff0c;进行数据的传输。二者同时进行&#xff0c;互不…

Apache.commons.lang3 的 isNumber 将会在 lang 4 的时候丢弃

在判断输入的字符串是不是一个数字的时候&#xff0c;我们通常用的最多的方法就是 &#xff1a; NumberUtils.isNumber("12"); 但是这个方法将会在 Lang 4.0 版本中被丢弃。 可以使用的替代方法为&#xff1a;isCreatable(String) 通过查看源代码&#xff0c;我们…

ASP.NET网上图书预约系统的设计

摘 要 《网上图书预约系统的设计》是以为读者提供便利为前提而开发的一个信息管理系统&#xff0c;它不仅要求建立数据的一致性和完整性&#xff0c;而且还需要应用程序功能的完备、易用等特点。系统主要采用VB.NET作为前端的应用开发工具&#xff0c;利用SQL Server2000数据…

7zip如何只压缩文件不带上级目录?

在使用7zip进行文件压缩的时候&#xff0c;如果直接选择要压缩的文件进行压缩&#xff0c;得到的压缩包则会多包含一层顶层目录&#xff0c;解压缩之后需要点击两次才能进入到实际目录中&#xff0c;为了解决这个问题&#xff0c;本文根据探索找到了一种解决办法。 如下是一个演…

【星海随笔】windows 上跑MySQL

step one 使用 WSL 在 Windows 上安装 Linux wsl官方文档 在管理员模式下打开 PowerShell windows上安装wsl wsl --install查看哪些是可用的 wsl --list --onlineC:\Windows\System32\drivers\hosts docker-desktop下载官网&#xff1a;Install Docker Desktop on Windows …

Java苍穹外卖04-

一、缓存菜品 1.问题说明 2.实现思路 就是点击到这个分类的时候就可以展示相应的菜品数据 3.代码实现 在user的菜品的contoller中&#xff1a;增加判断redis中是否存在所需数据&#xff0c;不存在添加&#xff0c;存在直接取得 这里注意&#xff1a;你放进去用的是List<Di…

数码管的显示

静态数码管显示 数码管有两种一种的负电压促发,一种是正电压促发,上图是单数码管的引脚 上图是数码管模组的引脚,采用了引脚复用技术 咱们这个单片机由8个单数码管,所以要用上38译码器,如下图 74138使能端,单片机上电直接就默认接通了 74HC245的作用是稳定输入输出,数据缓冲作…

memory consistency

memory consistency model 定义了对于programmer和implementor来说&#xff0c;访问shared memory system的行为&#xff1b; 对于programmer而言&#xff0c;他知道期望值是什么&#xff0c; 知道会返回什么样的数据&#xff1b;&#xff1b; 对于implementro而言&#xff0c;…

npy文件如何追加数据?

.npy 文件是 NumPy 库用于存储数组数据的二进制格式&#xff0c;它包含一个描述数组结构的头部信息和实际的数据部分。直接追加数据到现有的 .npy 文件并不像文本文件那样直接&#xff0c;因为需要手动修改文件头部以反映新增数据后的数组尺寸&#xff0c;并且要确保数据正确地…

电子信息工程专业就业前景怎么样

电子信息工程专业的就业前景十分广阔&#xff0c;主要得益于现代社会对信息技术的依赖不断加深以及科技的快速发展&#xff0c;以下是上大学网&#xff08;www.sdaxue.com&#xff09;对该专业就业前景的具体分析&#xff0c;供大家参考&#xff01; 行业需求广泛&#xff1a;随…

Partisia Blockchain 生态zk跨链DEX上线,加密资产将无缝转移

在 5 月 1 日&#xff0c;由 Partisia Blockchain 与 zkCross 创建合作推出的 Partisia zkCrossDEX 在 Partisia Blockchain 生态正式上线。Partisia zkCrossDEX 是 Partisia Blockchain 上重要的互操作枢纽&#xff0c;其融合了 zkCross 的 zk 技术跨链互操作方案&#xff0c;…

vue2实现生成二维码和复制保存图片功能(复制的同时会给图片加文字)

<template><divstyle"display: flex;justify-content: center;align-items: center;width: 100vw;height: 100vh;"><div><!-- 生成二维码按钮和输入二维码的输入框 --><input v-model"url" placeholder"输入链接" ty…

重写muduo之EPollPoller

1、EPollPoller.h EPollPoller的主要实现&#xff1a;作为poller的派生类&#xff0c;把基类给派生类保留的这些纯虚函数的接口实现出来。 override表示在派生类里面&#xff0c;这些方法是覆盖方法。必须由编译器来保证在基类里面一定有这些函数的接口的声明。在派生类要重写…

[蓝桥杯2024]-PWN:ezheap解析(堆glibc2.31,glibc2.31下的double free)

查看保护 查看ida 大致就是只能创建0x60大小的堆块&#xff0c;并且uaf只能利用一次 完整exp&#xff1a; from pwn import* #context(log_leveldebug) pprocess(./ezheap2.31)def alloc(content):p.sendlineafter(b4.exit,b1)p.send(content) def free(index):p.sendlineaft…

代码随想录算法训练营DAY46|C++动态规划Part8|139.单词拆分、多重背包理论基础、背包问题总结篇

文章目录 139.单词拆分思路CPP代码 多重背包理论基础处理输入把所有个数大于1的物品展开成1个开始迭代&#xff0c;计算dp数组代码优化 背包问题总结篇 139.单词拆分 力扣题目链接 文章讲解&#xff1a;139.单词拆分 视频讲解&#xff1a;你的背包如何装满&#xff1f;| LeetCo…

体育老师工资高吗,奖金有吗

教师的薪资水平与多种因素相关&#xff0c;包括教育经验、工作地点、学校类型以及个人的教学成果等。在讨论体育教师的工资问题时&#xff0c;不能仅仅关注数字&#xff0c;更应了解教育价值和个人发展。 初中体育教师的工资水平受多种因素影响。根据网络统计的数据&#xff0c…

STM32F4xx开发学习—GPIO

GPIO 学习使用STM32F407VET6GPIO外设 寄存器和标准外设库 1. 寄存器 存储器映射 存储器本身是不具有地址的&#xff0c;是一块具有特定功能的内存单元&#xff0c;它的地址是由芯片厂商或用户分配&#xff0c;给存储器分配地址的过程就叫做存储区映射。给内存单元分配地址之后…

页面多开、谷歌浏览器解决不能批量打开问题、批量打开被限制

目录 问题原因谷歌浏览器解决办法来看效果 问题 我们使用批量打开页面的时候 只能打开第一个页面 原因 这种问题是因为 浏览器限制了浏览器的弹出 并不是人家页面功能不能用 谷歌浏览器解决办法 在浏览器输入这个路径 chrome://settings/content/popups?search%E9%87%…

Vue MVVM这一篇就够啦!

Vue vs React 相似之处: 它们都有使用 Virtual DOM虚拟DOM-CSDN博客&#xff1b;提供了响应式&#xff08;Reactive&#xff09;和组件化&#xff08;Composable&#xff09;的视图组件。将注意力集中保持在核心库&#xff0c;而将其他功能如路由和全局状态管理交给相关的库。R…