大家好,我是苏貝,本篇博客带大家了解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树,但一个结构经常修改,就不太适合。
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️