二叉平衡树
二叉搜索树
二叉搜索树又称二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树:
(1):对于它的任意一个结点而言,若该结点的左子树不为空,则左子树上所有结点的值小于该结点的值.。
(1):对于它的任意一个结点而言,若该结点的右子树不为空,则右子树上所有结点的值大于该结点的值.。
即对于他的任意一个结点而言,该结点的左右子树也分别为二叉搜索树
二叉搜索树的查找
既然将其称之为二叉搜索树,因此这棵树最主要的作用是进行查询,而且其查询原理特别简单,具体如下:
若根节点不为空:
若根节点key == 查找key,返回true
若根节点key大于查找key,在其左子树查找
若根节点key小于查找key,在其右子树查找
若最终仍未找到,返回false
插入和删除操作,也都是建立在查找的基础之上的,理想情况下,二叉搜索树的查找时间复杂度为O(log(n)),但是,在最坏的情况下(对于任意一个节点,都只有左孩子或者右孩子的情况下),他的时间复杂度为O(n),进而导致插入和删除的时间复杂度也变为O(n)
为了解决上述问题,引入了平衡树的概念,平衡树是指任意结点,左右子树的高度差都小于等于 1 ,AVL 树就是一个二叉平衡搜索树
AVL树
由上述我们就知道,AVL树是在二叉搜索树的基础上要求,任意结点左右子树的高度差不超过 1,并将高度差命名为平衡因子(Balance Factor),二叉树的结点代码如下:
class Node {
private K key;
private V value; // K-V模型带着value,纯key模型可以不带value
Node<K,V> left;
Node<K,V> right;
int bf; //平衡因子
Node<K,V> parent; // 保存自己的双亲结点,如果是跟,则为null
}
我们具体讲AVL树插入的过程:
1.按照普通搜索树的方式进行插入,若 key 重复,则放弃插入
2.插入后依次往上修改平衡因子,修改的原则为:
如果插入的地方为该结点的左孩子,则平衡因子 - 1
如果插入的地方为该结点的右孩子,则平衡因子 + 1
因此,在插入前,AVL树的平衡因子的取值范围为 [ -1, 0, 1 ],插入后,因为平衡因子被修改,所以,平衡因子的取值范围变为 [ -2,-1, 0, 1 ,2],
如果被修复后,平衡因子为 0 ,则表示这棵树的高度插入前后未改变
因为该结点的的平衡因子为 0 ,,高度未改变,不会继续影响该结点的的父结点的平衡因子,所以插入过程结束,整棵树仍是AVL树
如果修改后平衡因子 == -1 / 1,则说明高度发生了变化,增加了 1
所以该结点的父节点的平衡因子也需要被调整
也就是说,只要平衡因子调节完的结果是 -1/1,则调整平衡因子的过程会向上蔓延
如果被修改后的平衡因子的值为 -2 或者 2 ,则说明AVL树的特征被改变了,那么我们就需要利用规则进行修复。在修复之前,任意时刻,一个AVL树最多只有一个结点的平衡因子不满足特征,修复子树的过程会保证这颗子树的高度不变,这样不至于影响到该子树父结点的平衡因子。
AVL树把破坏规则的情况称为失衡(失去平衡),失衡的情况分为 4 类;
1. 左左失衡: parent 的失衡是因为在 parent 的左孩子的左子树中插入导致的失衡,如下:
针对左左失衡的情况,只需要对parent(失衡的结点)进行右旋即可
2.左右失衡:parent 的失衡是因为在 parent 的左孩子的右子树中插入导致的失衡
针对左右失衡的情况,先对 cur 进行左旋,再对 parent 进行右旋
3.右右失衡:parent 的失衡是因为在 parent 的右孩子的右子树中插入导致的失衡
针对右右失衡的情况,与左左失衡类似,只需要对parent(失衡的结点)进行左旋即可
2.右左失衡:parent 的失衡是因为在 parent 的右孩子的左子树中插入导致的失衡
针对右左失衡的情况,先对 cur 进行右旋,再对 parent 进行左旋
AVL树性能分析
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log(n))。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。