【C++】AVL树

2023-09-21 23:29:15


1. AVL树的概念

AVL树是一棵二叉搜索树,但它的每个节点的左右子树的高度差的绝对值不超过1,且它的子树也是平衡二叉树。左右子树的高度差也叫平衡因子,平衡因子 = 右子树叶的高度 - 左子树的高度。

在这里插入图片描述
将AVL树与满二叉树对比,看看AVL的效率如何?
在这里插入图片描述


2. AVL树的实现

2.1 节点的定义

//节点
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)
	{}
};

2.2 插入

  1. AVL树是二叉搜索树,所以首先按照二叉搜索树的规矩插入。插入后再考虑插入节点后,AVL树是否平衡。
  2. 有个例子

(1)更新后parent的平衡因子如果是1或者-1,说明parent所在子树的高度发生变化,会影响祖先,需要沿着到root的路径往上更新。
在这里插入图片描述

(2)更新后parent的平衡因子如果是0,说明parent所在子树的高度不变,不用继续沿着到root的路径往上更新。
在这里插入图片描述

(3)更新后parent的平衡因子如果是2或者-2,说明parent所在子树的高度变化且不平衡,对parent的子树进行旋转,使其平衡。
在这里插入图片描述
(4)如果parent是头节点,对parent进行旋转后,记得更新根节点。

  1. 旋转的原理

节点的插入可以分为以下几种情况
(1)左单旋:新节点插入在较高右子树的右侧
在这里插入图片描述
(2)右单旋:新节点插入较高左子树的左侧
在这里插入图片描述

(3)双旋:新节点插入较高右子树的左侧
在这里插入图片描述

(4)双旋:新节点插入较高左子树的右侧
在这里插入图片描述
代码

template<class K, class V>
class AVLTree
{
public:
	typedef AVLTreeNode<K, V> Node;
		bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = cur;
		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 < cur->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//更新平衡因子
		while (parent)
		{
			if (parent->_left == cur)
			{
				--parent->_bf;
			}
			else
			{
				++parent->_bf;
			}
			//如果更新完平衡因子为0,说明其左右子树等高,已经平衡
			if (parent->_bf == 0)
			{
				break;
			}
			//不等高,继续往上更新平衡因子
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			//不平衡,分为四种情况
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//新节点插入较高右子树的右侧,需要将parent左旋
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				//新节点插入较高左子树的左侧,需要将parent右旋
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				//新节点插入较高右子树的左侧,需要先将cur右旋,再将parent左旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				//新节点插入较高左子树的右侧,需要先将cur左旋,再将parent右旋
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}

	//左旋
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;
		//不要忘记父节点的链接
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		//要考虑parent的parent是否存在
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		else
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		//平衡因子置为0
		parent->_bf = cur->_bf = 0;
	}
	//右旋
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;

		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = pparent;
		}
		else
		{
			_root = cur;
			cur->_parent = nullptr;
		}

		parent->_bf = cur->_bf = 0;
	}
	//双旋:新节点插入较高右子树的左侧
	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		//先右旋,再左旋
		RotateR(cur);
		RotateL(parent);
		if (curleft->_bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
		}
		else if (curleft->_bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
		}
		else if (curleft->_bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
		}
	}
	//双旋:新节点插入较高左子树的右侧
	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		//先左旋再右旋
		RotateL(cur);
		RotateR(parent);
		//更新平衡因子
		if (curright->_bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
		}
		else if (curright->_bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
		}
		else if (curright->_bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
		}
	}
private:
	Node* _root = nullptr;
};

2.3 是否是AVL树

如果一棵AVL树不平衡,那么它的左右子树的高度差的绝对值超过2,旋转出现问题。如果要判断一棵树是否是AVL树是否平衡,不能通过平衡因子判断,因为旋转出现问题,那么平衡因子也会出现问题,所以只能通过高度来判断。
代码

// 根据AVL树的概念验证pRoot是否为有效的AVL树
	bool IsAVLTree()
	{
		return IsAVLTree(_root);
	}
	bool IsAVLTree(Node* pRoot)
	{
		//不能依赖平衡因子,容易监守自盗。如果旋转出现问题,平衡因子也会有问题
		//所以直接通过高度来判断
		if (pRoot == nullptr)
		{
			return true;
		}
		int leftHeight = Height(pRoot->_left);
		int rightHeight = Height(pRoot->_right);
		//abs返回参数的绝对值
		return abs(leftHeight - rightHeight) < 2
			&& IsAVLTree(pRoot->_left) && IsAVLTree(pRoot->_right);
	}
	int Height()
	{
		return Height(_root);
	}
	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;
	}

3. AVL树与红黑树

插入时要维护其绝对平衡,旋转的次数比较多,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比AVL树更优

  1. AVL树和红黑树都是平衡二叉树。在查询效率方面,AVL树追求绝对平衡,红黑树要求最长路径不超过最短路径的两倍,AVL查询数据效率比红黑树高,但是对于CPU而言,都是属于logN这个量级的。
  2. AVL树追求控制严格平衡是需要付出代价的,插入和删除已需要进行大量的旋转。红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在插入和删除方面红黑树效率更高。
  3. 综上,红黑书更优,实际运用的多。
更多推荐

利用Socks5代理IP加强跨界电商爬虫的网络安全

随着跨界电商的兴起,爬虫技术在这个领域变得越来越重要。然而,网络安全一直是一个值得关注的问题。在本文中,我们将讨论如何利用代理IP和Socks5代理来增强跨界电商爬虫的网络安全,确保稳定和可靠的数据采集,同时避免封禁和风险。背景跨界电商是一个竞争激烈的领域,市场上的商品信息和价格常常会变动。为了保持竞争力,电商企业需要

python写代码过程中的坑230915

1.解释代码IndentationError:expectedanindentedblock这个错误通常是由于代码缩进错误导致的。在Python中,代码块(如循环、条件语句、函数等)通常使用缩进来表示。因此,如果你在期望缩进的位置没有正确缩进代码,就会出现"IndentationError:expectedaninde

计算机竞赛 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录0前言1课题背景2具体实现3数据收集和处理3卷积神经网络2.1卷积层2.2池化层2.3激活函数:2.4全连接层2.5使用tensorflow中keras模块实现卷积神经网络4MobileNetV2网络5损失函数softmax交叉熵5.1softmax函数5.2交叉熵损失函数6优化器SGD7学习率衰减策略6最后0

lv5 嵌入式开发-2 exec函数族

目录1进程–exec函数族1.1exec函数族特点1.2进程–execl/execlp使用方法1.3进程–execv/execvp2进程–system3exec族要点演示掌握:exec函数族、system1进程–exec函数族执行程序,通孔ps-elf发现,父进程是bash。这意味着该进程是由一个bashshell中启

Stable Diffusion AI绘图使用记录

1、下载安装使用官方网站https://github.com/AUTOMATIC1111/stable-diffusion-webui跟着一步步安装就行(英文版的)2、真人转二次元下载控制插件ControlnetGitHub-Mikubill/sd-webui-controlnet:WebUIextensionforC

HTML5 游戏开发实战 | 五子棋

五子棋是一种家喻户晓的棋类游戏,它的多变吸引了无数的玩家。本章首先实现单机五子棋游戏(两人轮流下),而后改进为人机对战版。整个游戏棋盘格数为15×15,单击鼠标落子,黑子先落。在每次下棋子前,程序先判断该处有无棋子,有则不能落子,超出边界不能落子。任何一方有横向、竖向、斜向、反斜向连到5个棋子则胜利。五子棋游戏的运行界

python学习之【with语句】

前言上一篇文章​​python学习之【文件读写】​​​中我们学习了python当中的文件读写,这篇文章接着学习python中文件读写的with语句。了解with语句在很多场景中,通过使用with语句可以让我们可以更好地来管理资源和简化代码,它可以看做是对try…finally模式的简化。with语句的语法格式witho

Baichuan2大模型本地部署

作为今年九月份开源的一个中午大语言模型,Baichuan2已经在各个维度上取得了亮眼的结果,效果已经超过了当前火热的ChatGLM2-6B,可以通过自然语言交互的方式为你提供以下服务:提供知识:我可以回答各领域的问题,并提供准确的信息和知识,帮你解决问题或获取所需要的信息文本生成:我可以创作不同体裁的内容,激发你的灵感

Spring 中经典的 9 种设计模式

1、简单工厂BeanFactory。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。2、工厂方法FactoryBean接口。3、单例模式Spring依赖注入Bean实例默认是单例的。4、适配器模式S

华为云云耀云服务器L实例评测|拉取创建canal镜像配置相关参数 & 搭建canal连接MySQL数据库 & spring项目应用canal初步

前言最近华为云云耀云服务器L实例上新,也搞了一台来玩,本篇博客介绍如何在华为云上部署canal的docker镜像,以及在spring项目中的初步应用。其他相关的华为云云耀云服务器L实例评测文章列表如下:初始化配置SSH连接&安装MySQL的docker镜像&安装redis以及主从搭建&7.2版本redis.conf配置

uniapp后台播放音频功能制作

在UniApp中,你可以使用uni.getRecorderManager()方法来创建一个录音管理器实例。但是,请注意,录音管理器并不直接用于后台音频播放功能,而是用于录制音频。如果想要在后台播放音频,你需要使用uni.getBackgroundAudioManager()。以下是一个示例,演示了如何在UniApp中使

热文推荐