【C++】AVL树

大家好,我是苏貝,本篇博客带大家了解C++的AVL树,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


1. AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1、 它的左右子树都是AVL树
2、 左右子树高度之差的绝对值不超过1(-1/0/1)

为了直接知道当前节点的左右子树的高度差,我们使用平衡因子(平衡因子只是知道高度差的一种方式,也有其它方式,但我们只了解这一种),平衡因子的值=右子树发高度-左子树的高度,或者平衡因子的值=左子树发高度-右子树的高度,下面我们采用第一种
在这里插入图片描述

在这里插入图片描述

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(log_2 n),搜索时间复杂度O(log_2 n)。

下面我们来自主实现一个AVL树

2. AVL树节点的定义

在这里插入图片描述

注意:AVL树和下面介绍的红黑树相比于二叉搜索树,节点中多了一个指向双亲结点的指针。_bf就是平衡因子

3. AVL树的查找

在这里插入图片描述

4. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:
1、 按照二叉搜索树的方式插入新节点
2、 调整节点的平衡因子

在这里插入图片描述

第1步,按照搜索树规则插入节点,这和二叉搜索树的插入基本类似,只多了让cur的双亲结点指针指向parent

在这里插入图片描述
在这里插入图片描述

第二步,更新平衡因子
如果插入节点后,cur是parent的左节点,parent的平衡因子–,反之++
parent的平衡因子== 0,表示父亲的左右子树的高度不变,插入结束
parent的平衡因子==1/-1,表示父亲的左右子树的高度差改变了,继续向上更新

从下图我们可以看出,当parent==nullptr时,退出循环

在这里插入图片描述
在这里插入图片描述

parent的平衡因子==2/-2,表示父亲的左右子树已经不平衡了,需要旋转处理

(A) 新节点插入在较高左子树的左侧—左左:右单旋(parent->_bf==-2,cur->_bf==-1)

在这里插入图片描述

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30的左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。

上图是抽象图,下面有具体图

在旋转过程中,有以下几种情况需要考虑:
1、30节点的右孩子可能存在,也可能不存在
2、60可能是根节点,也可能是子树
如果是根节点,旋转完成后,要更新根节点
如果是子树,可能是某个节点的左子树,也可能是右子树

注意:subL指向的节点的平衡因子一定为0,为什么?因为如果不为0,那么根本不需要进行旋转,直接在parent== 0时就插入结束了,或者是subL指向的节点的平衡因子==-2/2,从这里就要旋转而非到parent再旋转

在这里插入图片描述

当h==1时

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(B) 新节点插入较高右子树的右侧—右右:左单旋(parent->_bf== 2,cur->_bf==1)

在这里插入图片描述

实现及情况考虑可参考右单旋。同样的,subR指向的节点的平衡因子一定为0

实现:
1、 将subRL做parent的右孩子
2、 parent做subR的左孩子

在这里插入图片描述
在这里插入图片描述

© 新节点插入较高左子树的右侧—左右:先左单旋再右单旋(parent->_bf==-2,cur->_bf==1)

在这里插入图片描述

同样的,这里的subL和subLR的平衡因子都为0

这里的插入有3种情况:
1、 h== 0,所以上图的60就是新插入的节点,因此60的平衡因子== 0
2、 h>0,新插入的节点在60的左边,此时60的平衡因子==-1
3、 h>0,新插入的节点在60的右边,此时60的平衡因子==1

这里只画了情况2,其它的自己试试吧

这3种情况的相同点:都先以30为旋转点左旋,再以90为旋转点右旋
不同点:情况1,最后parent和subL和subLR的平衡因子都为0
情况2,parent的平衡因子为1,subL和subLR的平衡因子都为0
情况3,parent和subLR的平衡因子为0,subL的平衡因子都为-1

在这里插入图片描述

(D) 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

在这里插入图片描述

同样的,这里的subR和subRL的平衡因子都为0

实现及情况考虑可参考左右双旋

这里的插入也有3种情况:
4、 h== 0,所以上图的60就是新插入的节点,因此60的平衡因子== 0
5、 h>0,新插入的节点在60的左边,此时60的平衡因子==-1
6、 h>0,新插入的节点在60的右边,此时60的平衡因子==1

这里只画了情况3,其它的自己试试吧

这3种情况的相同点:都先以90为旋转点左旋,再以30为旋转点右旋
不同点:情况1,最后parent和subR和subRL的平衡因子都为0
情况2,subR的平衡因子为1,parent和subRL的平衡因子都为0
情况3,parent的平衡因子为-1,subR和subRL的平衡因子都为0

在这里插入图片描述

5. AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1、 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2、 验证其为平衡树
每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子),节点的平衡因子是否计算正确

在这里插入图片描述

验证用例

在这里插入图片描述

6. AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的是,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

7. AVL树的性能

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

先来插入10万个数据(有冗余)
在这里插入图片描述

结果:成功插入了66642个数据,该AVL树只有19层,插入只花了0.037秒,查找花了0.035秒,这是在debug版本下,在release版本下时间更短

在这里插入图片描述

再来插入100万个数据(有冗余),结果:成功插入了63万多个数据,该AVL树相较于上面的只增加了3层,但数据却是增加了57万多。插入和查找的时间也都非常短
在这里插入图片描述

AVL查找数据的效率非常高,但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值