【C++】AVL树学习 + 模拟实现

本文介绍了AVL树,一种改进的二叉搜索树,通过平衡因子确保树的平衡性。作者详细讲述了插入操作中可能遇到的4种情况及对应的左旋和右旋处理方法,展示了如何通过旋转保持树的高效查找性能。
摘要由CSDN通过智能技术生成

前言

[!NOTE]- 前言(废话环节 点击展开)
又到了我每次最喜欢写的前言环节,在前言环节,我可以将我最近的生活的一些感受分享出来,不是说给某个具体的人听。而是和某个抽象的,概括性的“读者”在对话,当然这个读者背后可能是在看这段文字的你,我不认识你,不了解的你人生与喜怒哀乐,但是我却在这里和你分享我的思考,我的喜怒哀乐,这种感觉很奇妙。但是我又不是光和你在对话,除了你还有许多其他人也许也会看到这篇文章,这些“你s”汇合在一起,组成了专属于我的“读者”这一形象。让我能够满足我个人的表达欲。
人都是有表达欲的,虽然在日常生活中,与我有接触的人也许都会觉得,我是个没什么表达欲的人,我很少发朋友圈,也很少在社交媒体上与人互动。但其实不是,只是我厌倦那种低效的,没什么营养的交流和表达。我认为这一点其实算是我这个人的某种特质,难以更改,这甚至影响到了我与我对象的关系,因为她总是希望我能时时刻刻地陪伴她,但是对于这种日常生活中的琐事我实在提不起深聊的兴趣。这也许是我的童年经历带来的,我小学的时候很喜欢读书,尤其是历史故事之类的,常常一个人跑去新华书店看各种故事书,通过看这些书籍,给我带来了许多思考,所以我往往比同龄人要成熟一些。包括我妈都说,养我是最轻松的,没什么叛逆期,相反我有个亲弟,现在读高中,天天和家里人唱反调,我妈经常被他气到。正是因为这些经历,塑造了我现在的性格。但是,不喜欢聊生活琐事和网络上一些明星那些鸡毛蒜皮不代表我这个人是个沉默寡言内向的i人,正相反,我是个外向的e人。只不过我更喜欢看一些能引起我思考,或者扩宽我眼界的东西,而现在社会人与人之间的关系比较疏远,各种APP上的信息茧房又让我十分厌倦(头条算法干的好事),朋友圈这种平台又不适合长篇累牍,得不到真正的思维上的交流与碰撞,只会收获“思考哥”这样的调侃。而我现在又经常写博客发文,正好可以借这个机会说一说我对生活的思考,满足一下我的表达欲。在这里我不会影响任何人,因为这是我的博客,哪怕没人来看,我也有一个虚拟的“读者”在与我对话,这种感觉十分好,就好像伯牙遇到了钟子期,虽然这个钟子期not real,但是这不重要。对于伯牙来说,有个人能认真听他的弦歌,就已经够了。


AVL树介绍

AVL树我在B站找到了一个很好的动画讲解的视频,给大家推荐一下:
平衡二叉树-Bilibili视频

所谓AVL树是在二叉树基础上改进而来的树,本质上是一颗二叉搜索树,但比二叉搜索树多了一个特性——平衡性

[[【C++】 二叉搜索树复习+模拟实现]|二叉搜索树]] 有个很大问题是,如果插入的元素单调递增,那么二叉搜索树就会退化成链表,查询的时间复杂度就会退化成O(N),为了解决这个问题,我们给每个二叉搜索树添加平衡因子,根据平衡因子对不平衡的树进行处理,使其满足二叉搜索树性质的同时,成为一颗平衡的树。

[!INFO] 什么是平衡
二叉树的平衡因子是记录其每一个节点的左子树深度 - 右子树深度的绝对值,当这个绝对值 <= 1时,我们称这颗树是平衡的


如何实现

插入操作的4种情况

二叉搜索树的不平衡性,可以根据其平衡因子的不同情况进行左旋和右旋。其中,插入时可能会遇到4种不同的情况,分别是

  1. 在左子树的左孩子添加节点
  2. 在右子树的右孩子添加节点
  3. 在左子树的右孩子添加节点
  4. 在右子树的左孩子添加节点
    这4种情况,我们将其简写为LL(左子树左孩子),RR(右子树右孩子)依次类推。

右旋(以LL为例)

我们那其中简单的为例,讲一下如何解决。以左子树的左孩子添加节点为例,如下图:

![[X2B[HNE6)BV{MLH3ROPNT}8_tmb.png]]

可以看到,根节点A的左子树深度原来为2,右子树深度原来为1,平衡因子为 2 - 1 = 1。我们在左子树左孩子添加节点后,平衡因子就变成了 3 - 1 = 2,出现了失衡状况。这种失衡,可以用==右旋==来解决,通过右旋根节点A,完成平衡,见下图

![[_UBW)0@I2ST$PNHWN%[OPZB_tmb.png]]

对比两幅图我们可以发现,右旋的操作涉及了3个节点,分别是根节点A,A的左孩子B,B的右孩子E。其操作的逻辑大致如下:

  1. 断开A与B之间的指针
  2. 断开B与E之间的指针
  3. 将E变成A的左孩子
  4. 将A变成B的右孩子
    通过这4步操作,就完成了右旋,左旋同理。

四种不同情况的的左右旋选择

根据左旋右旋的原理,针对插入的4种情况: LL, RR, LR, RL我们采取以下4种方式

失衡情况处理方式
LL右旋失衡节点
RR左旋失衡节点
LR左旋失衡节点左孩子,再自身右旋
RL右旋失衡节点右孩子,再自身左旋

找到失衡节点的方法是:根据插入节点的位置在失衡节点的左子树/右子树的左孩子/右孩子判断具体情况


模拟实现

节点定义

// 节点定义
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;//balance factor

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

插入节点

插入节点时,除了节点以外,还要更新对应节点的平衡因子,并根据平衡因子异常进行旋转

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

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

		// 处理平衡因子
		while (parent)
		{
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else
			{
				// rotate
				break;
			}
		}
		return true;
	}
	

 private:
	AVLTreeNode _root;
};

旋转

根据之前的知识点,旋转又分为LL,LR,RR,RL,所以需要四个旋转函数,应对不同的失衡场景

LL

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

		//处理subLR和parent的关系
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//处理subL和parent的关系
		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		if (ppNode == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)	
				ppNode->_left = subL;
			else			
				ppNode->_right = subL;
			
			ppNode->_parent = subL;
		}

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

RR

	// 插入右子树的右孩子,左旋
	void rotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		// 处理subRL和parent的关系
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		// 处理subR和Parent的关系
		Node* ppNode = parent->_parent;
		parent->_parent = subR;
		subR->_left = parent;

		if (ppNode == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else 
			{
				ppNode->_right = subR;
			}
			
			subR->_parent = ppNode;
		}

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

LR

// 在左子树的右孩子上添加节点,左旋失衡节点左孩子,右旋失衡节点
void rorateLR(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;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

RL

// 在右子树的左孩子上添加节点,右旋旋失衡节点右孩子,左旋失衡节点
void RotateRL(Node* parent)
{
	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
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
}

结束语

晕了,这篇文章写了我一下午,从12点写到6点,主要是我最近在学习vim,编码效率直线下滑,希望以后熟练了能把速度提上来

  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值