当前位置: 首页 > news >正文

AVL-树

AVL

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

一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是 AVL 树
  • 左右子树高度之差 (简称平衡因子) 的绝对值不超过 1 (-1/0/1)

AVL 树的性能

AVL 树是绝对平衡的二叉搜索树,要求每个节点左右子树高度差绝对值不超 1,能保证查询时高效的时间复杂度,即log2​(N) 。不过对其做结构修改操作性能低下,插入时为维护平衡旋转次数多,删除时甚至可能让旋转持续到根节点。因此,若需查询高效且有序,数据个数静态(不会改变)的数据结构,可考虑 AVL 树;若结构经常修改,则不太适合 。

AVL 树适用于对数据查找、插入和删除操作性能要求较高,且数据动态变化的场景。以下是几个实用的例子:

  • 数据库索引:数据库系统经常需要快速查找、插入和更新数据。例如,在一个存储用户信息的数据库表中,以用户 ID 作为关键字建立 AVL 树索引。当查询某个特定用户的信息时,能通过 AVL 树快速定位到对应的记录,时间复杂度为O(logn)。而且,当有新用户注册或用户信息发生变更时,AVL 树可以高效地进行插入和更新操作,并通过自动调整保持平衡,确保索引的性能稳定。
  • 编译器符号表管理:在编译器中,符号表用于存储程序中定义的变量、函数等符号的信息。在编译过程中,需要频繁地查找符号以检查其是否已定义、获取其相关属性,同时在声明新符号时要插入到符号表中。使用 AVL 树来管理符号表,可以快速实现这些操作。例如,当编译一段包含大量变量定义和引用的代码时,AVL 树能够高效地处理符号的查找和插入,帮助编译器快速完成语法分析和语义检查等工作。
  • 内存管理系统:在操作系统的内存管理中,需要跟踪内存块的分配和释放情况。可以使用 AVL 树来维护一个空闲内存块的列表,以内存块的地址或大小作为关键字。当进程请求内存时,能快速在 AVL 树中找到合适的空闲内存块进行分配;当进程释放内存时,又能将释放的内存块插入到 AVL 树中,并通过调整保持树的平衡。这样可以有效地提高内存分配和回收的效率,避免内存碎片的产生。
  • 搜索引擎:在搜索引擎的索引结构中,AVL 树可以用于存储单词到文档的映射关系。例如,对于每个单词,将包含该单词的文档 ID 作为节点值构建 AVL 树。当用户输入查询关键词时,搜索引擎可以迅速在 AVL 树中查找相关单词,并获取包含该单词的文档列表,实现快速的信息检索。同时,当有新文档被索引或者文档中的单词发生变化时,AVL 树能够方便地进行插入和更新操作,保证索引的准确性和时效性。

完整代码:


#include<iostream>
using namespace std;
#include<assert.h>

template<class K,class V>
struct AVLnode
{
	AVLnode<K, V>* _left;
	AVLnode<K, V>* _right;
	AVLnode<K, V>* _parent;

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

template<class K,class V>
class AVLtree
{
	typedef AVLnode<K, V> node;
public:
	bool insert(pair<K, V> kv)
	{
		if (_root == nullptr)
		{
			node* newnode = new node(kv);
			return true;
		}
		node* cur = _root;
		node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new node(kv);
		(parent->_kv.first > kv.first) ? (parent->_left = cur) : (parent->_right = cur);
		//不同于二叉搜索树的是avl是相当于双向的,所以在这个完成后还要进行一个反向的指向
		cur->_parent = parent;
		//关于这里面用parent是因为平衡因子的调整是向上调整的,
		//当parent走到的头结点,头结点再往上没有parent了直接为空此时就算是调整完成
		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;
			}
			else//这个else就要涉及到旋转调整问题!
			{
				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)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;

			}
		}


		return true;


	}
	int _Height(node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

	}
	bool find(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		node* cur = _root;
		while (cur)
		{
			if(cur->_kv.first>key)
			{
				cur = cur->_left;
			}
			else if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	void inorder()
	{
		cout << "中序遍历:" << " ";
		_inorder(_root);
	}
	size_t size()
	{
		return _size(_root);
	}


private:
	size_t _size(node* root)
	{
		if (root == nullptr)
			return nullptr;
		return _size(root->_left) + _size(root->_right) + 1;
	}
	void _inorder(node* root)
	{
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << "first: " << root->_kv.first << " : " << root->_kv.second << " ";
		_inorder(root->_right);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.firs << "平衡因子异常" << endl;
			return false;
		}
		return abs(rightHeight - leftHeight) < 2&&_IsBalance(root->_left)&&_IsBalance(root->_right);
	}
	void RotateL(node* parent)
	{

		node* p_parent = parent->_parent;
		node* subR = parent->_right;
		node* subRL = subR->_right;
		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (p_parent->_left == parent)//记住因为此时原树并没有被摧毁,parent节点也还没有更新,并不是说树的指针只能有两个指向
			{
				p_parent->_left = subR;
			}
			else
			{
				p_parent->_right = subR;
			}
			subR->_parent = p_parent;
		}
		parent->_bf = subR->_bf = 0;
		
	}

	void RotateR(node* parent)
	{
		node* subL = parent->_left;
		node* subLR = subL->_right;
		node* p_parent = parent->_parent;
		subL->_right = parent;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (p_parent->_left == parent)
			{
				p_parent->_left = subL;

			}
			else if (p_parent->_right == parent)
			{
				p_parent->_right = subL;
			}
			subL->_parent = p_parent;

		}
		parent->_bf = subL->_bf = 0;
	}
	// 左右旋操作
	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 = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1) {
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else {
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
	}
	// 右左旋操作
	void RotateRL(node* parent)
	{
		node* subR = parent->_right;
		node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);

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

	}
	
	
	node* _root;


};

相关文章:

  • 数据结构——第五章:树与二叉树
  • 建筑安全员考试:“高效记忆” 关键词引领的学习捷径
  • LLM - 重排序(Rerank)
  • 【2025 深圳大学-腾讯云程序设计竞赛(热身赛)】题解
  • Minine源码设计逻辑解析
  • Cursor从小白到专家
  • DSP数字信号处理
  • 聊聊NAT:从内网到外网的数据旅程
  • HTML字符实体笔记
  • SSE流式FastAPI
  • STM32入门笔记(03): ADC 电阻分压的方式测量电池电压方案(避免浮点运算,改为整数运算)(SPL库函数版)(3)
  • vue3 获取当前路由信息失败问题
  • OpenCV DNN 模块使用指南
  • ToDesk云电脑各类鼠标有什么区别?虚拟/3D/游戏鼠标等各有利
  • 100道C#高频经典面试题及答案解析:C#程序员面试题库分类总结
  • pfsense部署三(snort各版块使用)
  • 探秘海螺 AI 视频与计算机视觉算法的奇妙融合
  • 95 计费 5% 时间窗口的利用
  • Java 双端队列实战 实现滑动窗口 用LinkedList的基类双端队列Deque实现 洛谷[P1886]
  • 在线运行vscode
  • 美联合健康集团高管枪杀案嫌疑人对谋杀指控不认罪
  • 欢迎回家!日本和歌山县4只大熊猫将于6月底送返中国
  • 一季度公募管理规模出炉:44家实现增长,4家规模环比翻倍
  • 王沪宁会见越共中央委员、越南祖国阵线中央副主席兼秘书长阮氏秋荷
  • 广东东莞调整普通住宅价格标准:一类镇街上浮300余元/平方米
  • 上海汽车贸易有限公司原总经理王璟接受监察调查