2024年【高阶数据结构】AVL树(动图详解)_avl tree(3),已开源

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

};


注意:平衡因子不是必须的,只是我们实现高度平衡的一种方式,不用平衡因子也是可以实现的


### 三. AVL树的插入


插入节点有**三个步骤**


1. 按照二叉搜索树的原理,找到待插入的位置
2. 判断待插入的节点是在parent的左还是右,插入节点
3. 更新平衡因子,如果发现不平衡,则要**旋转**


🔥因为AVL树本身就是一颗二叉搜索树,插入规则(**比较节点大小即可**):


* 插入的节点key值 `>` 当前位置的key值,**插入到右子树**
* 插入的节点key值 `<` 当前位置的key值,**插入到左子树**
* 插入的节点key值**等于**当前位置的key值,**插入失败**


🌈那判断完插入成功与否,是不是就要判断平衡因子的更新了



> 
> 平衡因子是否更新取决于:该结点的**左右子树的高度是否发生了变化**,因此插入一个结点后,该结点的 **祖先结点的平衡因子**可能需要更新
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/ab32d914a78e44e1907612b63ac93381.png)


🌏**更新平衡因子的规则:**


* 新增在右,parent -> `bf++`;新增在左,parent -> `bf --`;


每更新完一个结点的平衡因子后,都需要进行以下判断:


1. 如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子
2. 如果parent的平衡因子等于0;表明无需往上更新平衡因子
3. 如果parent的平衡因子等于-2或者2;就已经不平衡了,需要**旋转**处理
4. 如果parent的平衡因子大于2或者小于-2;就说明之前插入的就不是AVL树了,赶紧去检查💥




| 更新后的平衡因子 | 分析 |
| --- | --- |
| -1 or 1 | 说明parent插入前的平衡因子是0;左右子树高度相等,插入后有一边高,**parent高度变了**,则需要往上继续更新 |
| 0 | 说明parent插入前的平衡因子是 -1 or 1;左右子树一边高一边低,插入后两边相等,插入的填上了矮的那一边,**parent的高度不变**,不需要继续往上更新 |
| -2 or 2 | 说明parent插入前的平衡因子是 -1 or 1;已经是平衡的临界值了;插入后变成-2 or 2 ;**打破了平衡**,parent所在的子树需要旋转处理 |


最坏的情况如下:一路更新到root根节点


![在这里插入图片描述](https://img-blog.csdnimg.cn/ac8a700f09b547a181568cf0a1fd2722.png)


那么我们更新平衡因子时第一个更新的就是parent结点的平衡因子,更新完parent结点的平衡因子后,若是需要继续往上进行平衡因子的更新,向上递归,直到parent为空的情况,以下逻辑是必须的



cur = parent;
parent = parent->_parent; 

当平衡因子出现了2/-2的情况,要对子树进行旋转处理,但也要遵守原则


* 旋转成平衡树
* 保持搜索树的规则


而旋转有四种大情况,对此我们要进行分类:


1. 当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋
2. 当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋
3. 当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋
4. 当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋


注意:旋转过后无需再往上更新平衡因子了,因为高度已经没有发生变化了,也就不会影响父节点的平衡因子了



//插入
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)//插入节点值大于当前节点的key
		{
			parent = cur;
			cur = cur->_right;//往右走
		}
		else if (cur->_kv.first > kv.first)//插入节点值小于当前节点的key
		{
			parent = cur;
			cur = cur->_left;//往左走
		}
		else
		{
			//插入的节点key值等于当前位置的key值,插入失败
			return false;
		}
	}
	//开始插入
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else if (parent->_kv.first < kv.first)
	{
		parent->_left = cur;
	}

	//连接parent
	cur->_parent = parent;

	//控制平衡
	//1、更新平衡因子

	while (parent)
	{
		if (cur == parent->right)
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}

		if (parent->_bf == -1 || parent->_bf == 1)//也可以用abs
		{
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 0)
		{
			break;
		}
		else if (parent->_bf == -2 || parent->_bf == 2)
		{
			//说明parent所在的子树已经不平衡了,需要旋转
			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);//左右双旋
			}

			break;
		}
		else
		{
			assert(false);//在插入前树的平衡因子就有问题
		}
	}
	return true;
}

### 四. AVL树的旋转


#### 🥑左单旋


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


**⚡动图展示:**


![请添加图片描述](https://img-blog.csdnimg.cn/f09faf584324495ebdeb4381ac5cd5aa.gif)


**抽象图过程解析:**


![在这里插入图片描述](https://img-blog.csdnimg.cn/743926ee76914427a748853353380c46.png)  
 其中h可以等于0、1、2等等,不过都可以归纳到这种大情况,处理情况都一样,都是引发parent 等于2,cur等于1


**左单旋旋转步骤:**


1. subRL变成parent的右子树(`subL和parent`的关系,要注意**🔥subL可能为空**)
2. parent成为subR的左子树(`parent和subLR`的关系)
3. subR成为根节点(`ppNode 和 subL`关系,也要注意**🔥parent是子树的根还是整棵树的根**)
4. 最后更新平衡因子


![在这里插入图片描述](https://img-blog.csdnimg.cn/91edc096487e4555baa7cbbabc736afd.png)


为什么要这样旋转?**要符合二叉搜索树规则**


* subR的左子树的值本身就比parent的值要大,所以可以作为parent的右子树
* parent及其左子树当中结点的值本身就比subR的值小,所以可以作为subR的左子树


平衡因子更新:


![在这里插入图片描述](https://img-blog.csdnimg.cn/3c70c6e52e23445385fcfc2ee129c00d.png)


可以看见,左单旋后树的高度就平衡了,也就无需继续向上更新平衡因子了


代码实现如下:(详细注释)



void RotateL(Node\* parent)
{
    //三叉链
	Node\* subR = parent->_right;
	Node\* subLR = subR->_left;
	Node\* ppNode = parent->_parent;

	//subR 和 parent之间的关系
	subR->_left = parent;
	parent->_parent = subR;

	//subRL 和 parent之间的关系
	subRL = parent->_right;
	if (subRL)
		subRL->parent = parent;

	//ppNode 和 subR的关系
	if (ppNode == nullptr)
	{
		_root = subR;
		subR->_parent = nullptr;//没有父节点,所以为空
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;
	}
	
	//更新平衡因子
	subR->_bf = parent->_bf = 0;
}

#### 🥑右单旋(和左单旋高度相似)


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


**动图演示:**


![请添加图片描述](https://img-blog.csdnimg.cn/52b22cfc05b04656a4e6ee326f1cf976.gif)**抽象图过程解析:**


![在这里插入图片描述](https://img-blog.csdnimg.cn/87daa1e4a0b6478a9f7c89bc49d831fe.png)


**右单旋旋转步骤:**


与左单旋雷同,看上面就行


同样**也要满足二叉搜索树的性质**:也是和左单旋雷同,看上面就行


平衡因子更新如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/5b2e723e7bef494cb48562a89de56f95.png)同样右单旋后,parent的平衡因子为0,左右子树高度相等,也就无需继续往上更新平衡因子了


话不多说上代码:



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

//subL 和 parent的关系
subL->_right = parent;
parent->_parent = subL;

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

//ppNode 和 subL的关系
if (ppNode == nullptr)
{
	_root = subL;
	subL->_parent = nullptr;
}
else
{
	if (ppNode->_left == parent)
	{
		ppNode->_left == subL;
	}
	else
	{
		ppNode->_right == subL;
	}
	subL->_parent = ppNode;
}
	
//更新平衡因子
subL->_bf = parent->_bf = 0;

}


#### 🔥左右单旋


新节点**插入较高左子树的右侧**—左右:**先左单旋再右单旋**、


**动图演示:**  
 ![请添加图片描述](https://img-blog.csdnimg.cn/2224678a2b9f4e04a66a4f8a787515d4.gif)


在b树或者c树中新增节点,均会引发左右双旋


**旋转示意图如下:**


1、插入新节点  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/745e255748264247b9e949aa5b29f217.png)


2、以30为旋转点进行**左单旋**


![在这里插入图片描述](https://img-blog.csdnimg.cn/18e6a3de2e234adf83005217d6eabc59.png)


3、以90为旋转点进行**右单旋**


![在这里插入图片描述](https://img-blog.csdnimg.cn/fc27fa454a3f49af9ea3302505fac256.png)


**左右单旋的步骤如下:**


1. 以subL为节点左单旋
2. 以parent为节点右单旋
3. **更新平衡因子**(这才是重点)


**左右双旋后满足二叉搜索树的性质:**


实际上就是把subLR的左子树和右子树,分别作为subL和parent的右子树和左子树,再让subL和parent分别作为subLR的左右子树,最后让subLR作为整个子树的根(看图理解)


* subLR左子树的节点值比subL的值大,所以可以作为subL的右子树
* subLR右子树的节点值比parent的值小,因此可以作为parent的左子树
* 前两个步骤之后,subL以及子树的值,和parent的值均符合,所以可以当subLR的左右子树


重点来了:(**以subLR为突破口**)  
 左右双旋后,平衡因子的更新随着**subLR原始平衡因子**的不同分为以下三种情况:


1. 当subLR原始平衡因子是-1时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0


![在这里插入图片描述](https://img-blog.csdnimg.cn/4dad546fb0c3414aae0c123918006885.png)


2. 当subLR原始平衡因子是1时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0


![在这里插入图片描述](https://img-blog.csdnimg.cn/b37311abc905492e8d38cd2c542eb7bd.png)


3. 当subLR原始平衡因子是0时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0


![在这里插入图片描述](https://img-blog.csdnimg.cn/0f66691730604d1e9a67250dd0c4fbe7.png)  
 经过左右双旋后,即树的高度没有发生变化,所以无需继续往上更新平衡因子


话不多说,代码实现一下吧:



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

	//subL节点左单旋
	RotateL(subL);

	//parent节点进行右单旋
	RotateR(parent);

	//更新平衡因子
	if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if(bf == 0)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);//旋转前的平衡因子就出错了
	}
}

#### 🔥右左单旋


**动图演示:**


![请添加图片描述](https://img-blog.csdnimg.cn/ba7ccbe8276047fdb3484204ca55178d.gif)


**旋转图演示过程:**


1、插入新节点


![在这里插入图片描述](https://img-blog.csdnimg.cn/e9215ad6a5164b669ff5a15400a89178.png)


2、以subR的节点进行右单旋


![在这里插入图片描述](https://img-blog.csdnimg.cn/d6bcd434c26a44f3a80ab47f96c565fd.png)


3、以parent的节点进行右单旋


![在这里插入图片描述](https://img-blog.csdnimg.cn/77b7e30aa9e54d319a933960057b63f8.png)


旋转步骤和左右双旋雷同


重点来了:(**以subRL为突破口**)  
 左右双旋后,平衡因子的更新随着**subRL 原始平衡因子**的不同分为三种情况分别对应`subRL` = 0、1、2情况,此处就不多赘述了,详细可以浏览左右双旋的,情况一样


![img](https://img-blog.csdnimg.cn/img_convert/a9f7f512c6084825124c1a606a754924.png)
![img](https://img-blog.csdnimg.cn/img_convert/4b228cb9708c566f236f2f3cd3d91f94.png)
![img](https://img-blog.csdnimg.cn/img_convert/27b3126c1f3cfa6b26d5da4f7f03095a.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

log.csdnimg.cn/77b7e30aa9e54d319a933960057b63f8.png)


旋转步骤和左右双旋雷同


重点来了:(**以subRL为突破口**)  
 左右双旋后,平衡因子的更新随着**subRL 原始平衡因子**的不同分为三种情况分别对应`subRL` = 0、1、2情况,此处就不多赘述了,详细可以浏览左右双旋的,情况一样


[外链图片转存中...(img-Bshf4oTL-1715235408127)]
[外链图片转存中...(img-Y3gxEweh-1715235408127)]
[外链图片转存中...(img-MXp8CdDB-1715235408127)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值