AVL树的实现

一. AVL的概念

AVL树是最先发明的⾃平衡⼆叉查找树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树:它的 左右⼦树都是AVL树,且左右⼦树的⾼度差的绝对值不超过1。AVL树是⼀颗⾼度平衡搜索⼆叉树,通过控制⾼度差去控制平衡。

AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis是两个前苏联的科学家,他们在1962年的论⽂《An algorithm for the organization of information》中发表了它。

AVL树实现这⾥我们引⼊⼀个平衡因⼦(balance factor)的概念,每个结点都有⼀个平衡因⼦,任何 结点的平衡因⼦等于右⼦树的⾼度减去左⼦树的⾼度,也就是说任何结点的平衡因⼦等于0/1/-1, AVL树并不是必须要平衡因⼦,但是有了平衡因⼦可以更⽅便我们去进⾏观察和控制树是否平衡,就像⼀个⻛向标⼀样。

思考⼀下为什么AVL树是⾼度平衡搜索⼆叉树,要求⾼度差不超过1,⽽不是⾼度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,⽽是有些情况是做不到⾼度差是0的。⽐如⼀棵树是2个结点,4个结点等情况下,⾼度差最好就是1,⽆法做到⾼度差是0

AVL树整体结点数量和分布和完全⼆叉树类似,⾼度可以控制在 ,那么增删查改的效率也可

以控制在 O(logN),相⽐⼆叉搜索树有了本质的提升。

这颗树是平衡的。

此时插入了一个13,此时这棵树就失衡了,失衡点是10。

二. AVL树的实现

2.1 AVL树的结构

这是我们实现的树结构。

2.2 AVL树的插⼊

2.2.1 AVL树插⼊⼀个值的⼤概过程

  1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊。

  2. 新增结点以后,只会影响祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新 从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可 以停⽌了,具体情况我们下⾯再详细分析。

  3. 更新平衡因⼦过程中没有出现问题,则插⼊结束

  4. 更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后本质调平衡的同时,本质降低了⼦树的⾼度,不会再影响上⼀层,所以插⼊结束。

2.2.2 平衡因⼦更新

更新原则:

平衡因⼦ = 右⼦树⾼度-左⼦树⾼度

只有⼦树⾼度变化才会影响当前结点平衡因⼦。

插⼊结点,会增加⾼度,所以新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在

parent的左⼦树,parent平衡因⼦--

parent所在⼦树的⾼度是否变化决定了是否会继续往上更新

更新停⽌条件:

更新后parent的平衡因⼦等于0,更新中parent的平衡因⼦变化为-1->0 或者 1->0,说明更新前

parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会

影响parent的⽗亲结点的平衡因⼦,更新结束。

更新后parent的平衡因⼦等于1 或 -1,更新前更新中parent的平衡因⼦变化为0->1 或者 0->-1,说

明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所

在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的⽗亲结点的平衡因⼦,所以要继续向

上更新。

更新后parent的平衡因⼦等于2 或 -2,更新前更新中parent的平衡因⼦变化为1->2 或者 -1->-2,说

明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼

了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把

parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不

需要继续往上更新,插⼊结束。

不断更新,更新到根,跟的平衡因⼦是1或-1也停⽌了。

我们了解了上面的之后,可以再来看一下是怎么完成++操作的。

我们先完成了bf的++/--操作,接下来我们就要完成旋转操作了。

2.3 旋转

2.3.1 旋转的原则

  1. 保持搜索树的规则

  2. 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度

旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

说明:下⾯的图中,有些结点我们给的是具体值,如10和5等结点,这⾥是为了⽅便讲解,实际中是什么值都可以,只要⼤⼩关系符合搜索树的性质即可。

2.3.2 右单旋

本图1展⽰的是10为根的树,有a/b/c抽象为三棵⾼度为h的⼦树(h>=0),a/b/c均符合AVL树的要

求。10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根。这⾥a/b/c是⾼度为h的⼦树,

是⼀种概括抽象表⽰,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体图2/图3/图4/

图5进⾏了详细描述。

在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h+1,不断向上更新平衡因⼦,导致10的平

衡因⼦从-1变成-2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树左边太⾼了,需要

往右边旋转,控制两棵树的平衡。

旋转核⼼步骤,因为5 < b⼦树的值 < 10,将b变成10的左⼦树,10变成5的右⼦树,5变成这棵树新 的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h+2,符合旋转原

则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。

图 1

图 2

图 3

如果感觉上面太抽象的话,可以看图二和图三,我来讲解一下。

我们可以发现图二失衡了,此时我们就需要旋转了,旋转的要求就是还是保持搜索二叉树的,此时是失衡节点的左子树的左子树,是LL型的,此时我们需要右旋,右旋就是让10变成5的右孩子,如果5本身就有右孩子的话,就是图三的情况,我们需要让10变成5的右孩子,再让5的原本的右孩子变成10的左孩子,此时还是满足搜索二叉树的,其他的节点不要动,此时就完成了平衡的操作。

我们下面用代码写一下右旋的操作吧。

我们通过插入函数中的这个if语句先找到失衡的节点,随后再看它的失衡类型,此时是右旋,我们可以通过图二看到,失衡节点的平衡因子是-2,它的左子树的平衡因子是-1,此时是LL型的,需要右旋,下面我们来完成一下这个右旋操作,首先我们先来看一下右旋之后的情况吧。

我们可以拿这个树来理一下思路。

首先我们右旋成下面这样。

我们来看一下它都需要改变哪些指向,我们的parent指向失衡节点,cur指向失衡节点的左孩子,我们先来看一下,首先就是我们的cur->right=parent,让parent变成了cur的右孩子,我们知道让parent->left=cur->right,图中cur没有右孩子,此时parent->left原来是指向cur的,此时就指向空了,但是我们的cur是可能有右孩子的,所以这里需要判断一下,存在我们还得改变这个右孩子的指向,就是cur->right->parent=parent了,这样就完成了一部分操作,还有就是parent->parent=cur,此时5变成10的父母了,所以要改变,最后就是5的父母了,我们知道5是有可能有父母的,所以这里还得判断一下5是否是根节点如果是就指向空,不是就指向10原来的父母了,下面是代码的书写。

这样我们就完成了右旋操作了。

2.3.3 左单旋

我们了解了右旋转之后,此时就要看一下左旋转了,我们直接看一个例子吧。

此时我们发现上图这棵树失衡了,失衡节点是5,此时是失衡节点的右子树的右子树失衡的,是RR型。此时需要左旋了,左旋就是让5作为10的左孩子,10原来的左孩子成为5的右孩子,此时还是保持搜索二叉树的,此时也完成了平衡。

旋转完就是这样了,这个和右旋转的思路是一样的只需要注意是左孩子还是右孩子即可,下面我们直接来看代码。

就是这样的,大家可以好好看看思考一下。

2.3.4 左右旋转

这个也很好理解,就是失衡节点的左子树的右子树导致的失衡,我们来看一个例子。

这个就是左右旋转了,我们可以看到是失衡节点的左子树的右子树导致的,此时我们通过单旋是无法完成的,此时就需要用左右旋转了,我直接来说是怎么旋转的吧,我也教大家一个方法记一下,左子树的右子树导致失衡的,就是LR型的,L是左吗,所以就是先让失衡节点的左孩子左旋,再让失衡节点右旋即可。

我们来看一下。

先左旋。

画的不好请见谅,左旋完就成这样了,此时就变成LL型了,再以失衡结点右旋一下就行了。

此时就是这样了,我们发现完成了平衡。

直接复用上面的代码即可。

这样我们只是完成了平衡,但是平衡因子我们并没有给到它该有的值,因为我们的LR型是有多种情况的,下面我们来看一下。

这是我们的第一种,此时我们通过旋转,我们的旋转的这三个的平衡因子都是0,此时是可以的。

这就是我们通过两次旋转得到的树,此时我们只需要看我们旋转的这几个点的平衡因子即可,我们发现10的平衡因子不对,也就是我们原来的parent,所以还需要分情况讨论一下。

第三种情况

这是我们旋转两次得到的平衡二叉树,我们发现它的平衡因子也是不对的。

综合上面三种情况,我们发现我们可以通过8的平衡因子来判断是哪种情况。

下面我们来补全一下我们的代码。

就是这样的,大家可以好好看看理解一下。

2.3.5 右左双旋

上面我们说了有左右双旋,那么肯定也是存在右左双旋的,我们下面举个例子来看一下吧。

这个就是一个右左双旋的例子,我们发现它是失衡结点的右子树的左子树导致的失衡,所以它是RL型的。

直接旋转就行了,先让失衡结点的右孩子右旋再让失衡结点左旋即可完成平衡操作。

就是这样的,代码也是复用上面的代码。

这个同理也是三种情况,也是需要补充代码的,我就直接给结果了,大家可以模仿上面的自己试一下。

这样就没有问题了,不好理解了,大家那张纸画画图就理解了。

三.测试

在测试之前先完成一些必要的函数。

3.1中序遍历

3.2 Height和Size

3.3 Find

这些函数实现起来都很简单,不过多说明了。

3.4 测试函数

这就是我们的测试函数。

测试一下,如果程序没崩溃,说明代码没问题。

我们发现是没有问题的。

四.结束语

感谢大家的查看,希望可以帮助到大家,做的不是太好还请见谅,其中有什么不懂的可以留言询问,我都会一一回答。 感谢大家的一键三连。

相关推荐
问商十三载12 小时前
2026法律行业AI引擎生成式优化怎么优化?三层专业加固提排名,零成本提33%引用率附适配表
java·前端·人工智能·算法
zander25812 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态12 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面13 小时前
栈在递归中的应用
数据结构·算法
8K超高清13 小时前
博冠获中国电影电视技术学会科技进步奖
人工智能·科技·算法·安全·接口隔离原则·智能硬件
微石科技13 小时前
长期慢病如何做好居家管理?宁波微石科技星梦云康改善周期数据零散短板
大数据·科技·物联网·算法
微露清风14 小时前
快速排序算法学习记录
学习·算法·排序算法
梓䈑19 小时前
【算法题攻略】BFS 解决FloodFill 算法、最短路问题、多源BFS 和 拓扑排序
c++·算法·宽度优先
Adios79421 小时前
设置交集大小至少为2
数据结构·算法·leetcode