1 AVL 树
1.1 概念
AVL树是最先发明的自平衡二叉查找树。
在AVL树中任何节点的两个子树的高度最大差别为1,也被称为高度平衡树。
增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它
AVL 树 = Adelson-Velsky Landis Tree
1.2 AVL树的特点
- 本身首先是一棵二叉搜索树。
- 带有平衡条件:每个结点的左右子树的高度之差的绝对值即平衡因子 <= 1
AVL树,本质上是带了自动平衡功能的二叉查找树
1.3 AVL 旋转
共有4种旋转:
- 右旋
- 左旋
- 双向旋转
- 先左后右
- 先右后转
单向右旋平衡处理LL
在*T的左子树的左子树上插入结点,T的平衡因子由1增至2,致使以T为根的子树失去平衡,则需进行一次右旋转操作
T向右旋转成为L的右结点,同时,Y放到T的左孩子上
单向左旋平衡处理RR
在*T的右子树的右子树上插入结点,T的平衡因子由1增至2,致使以T为根的子树失去平衡,则需进行一次左旋转操作
T向左旋转成为R的左结点,同时,Y放到T的右孩子上
双向旋转(先左后右)平衡处理LR
先达到左旋的条件,旋转后又达到右旋的条件
双向旋转(先右后左)平衡处理RL
先达到右旋的条件,旋转后又达到左旋的条件
1.4 节点删除
AVL树删除节点的过程是,先找到该节点,然后进行删除。
由于删除节点的位置不同,导致删除后节点进行移动的方式不同。
删除节点的位置分为以下4类:
- 删除叶子结点。操作:直接删除,然后依次向上调整为AVL树。
- 删除非叶子节点,该节点只有左孩子。操作:该节点的值替换为左孩子节点的值,然后删除左孩子节点。
- 左孩子节点为叶子结点,所以删除左孩子节点的情况为第1种情况。
- 为什么左孩子节点为叶子节点?,因为删除节点前,该树是AVL树,由AVL树的定义知,每个节点的左右子树的高度差的绝对值<=1,由于该节点只有左孩子,没有右孩子,如果左孩子还有子节点,那么将不满足每个节点的左右子树的高度差的绝对值<=1,所以左孩子节点为叶子结点
- 删除非叶子节点,该节点只有右孩子。操作:该节点的值替换为右孩子节点的值,然后删除右孩子节点。
- 右孩子节点为叶子结点,所以删除右孩子节点的情况为第1种情况。
- 为什么右孩子节点为叶子节点?答案和第二种情况一样】
- 删除非叶子节点,该节点既有左孩子,又有右孩子。操作:该节点的值替换为该节点的前驱节点(或者后继节点),然后删除前驱节点(或者后继节点)。
- 前驱结点:在中序遍历中,一个节点的前驱结点,先找到该节点的左孩子节点,再找左孩子节点的最后一个右孩子节点。向左走一步,然后向右走到头。最后一个右孩子节点即为前驱节点
- 后继节点:在中序遍历中,一个节点的后继结点,先找到该节点的右孩子节点,再找右孩子节点的最后一个左孩子节点。向右走一步,然后向左走到头。最后一个左孩子节点即为前驱节点
总结:对于非叶子节点的删除,最终都将转化为对叶子节点的删除。
删除叶子节点
删除非叶子节点,该节点只有左孩子
删除非叶子节点,该节点只有右孩子
删除非叶子节点(既有左孩子,又有右孩子)
2 源码示例
感兴趣的同学,可以直接参考如下源码学习
2.1 scala中的实现
在scala 2.11.x版本中,在AVLTree 类中实现了AVL树
但表示即将从标准库中删除,理由是和红黑树的差异不明显
AVLTree and its related classes are being removed from the standard library since they’re not different enough from RedBlackTree to justify keeping them
2.2 common-math3中的实现
在common-math3库中实现了AVLTree, 具体实现类是:org.apache.commons.math3.geometry.partitioning.utilities.AVLTree
目前标记为@Deprecated
org.apache.commons
commons-math3
3.6.1
2.3 Guava中的实现
在guava库中实现了AVLTree,具体实现类是:com.google.common.collect.TreeMultiset中的一个静态内部类 AvlNode
com.google.guava
guava
18.0