前言分析
二叉搜索树弊端
如果按照7、4、9、2、5、8、11的顺序添加节点
graph TD
7 --> 4 --> 2
4 --> 5
7 --> 9 --> 8
9 --> 11
但是如果按照从小到大添加节点
graph TD
2 --> 4 --> 5 --> 7 --> 8 --> 9 --> 11
很明显这样的话二叉搜索树直接退化变成了链表,这时时间复杂度将由退化为,当n比较大的时候性能会出现较大的差异
同样,在删除节点时也会出现二叉搜索树退化变成链表的情况,所以必须要避免这种情况的发生,要让添加、删除、搜索的时间复杂度维持在级别
平衡(Balance)
平衡:当节点数量固定时,左右子树高度越接近,这棵二叉树就越平衡(高度越低)
如图从左至右就越来越平衡
理想平衡
最理想的平衡就是像完全二叉树和满二叉树那样高度是最小的
改进二叉搜索树
- 首先,节点的添加、删除顺序是无法限制的,可以认为是随机的
- 所以,改进的方案应当是在节点的添加或者删除后,尽可能让二叉搜索树恢复平衡(减小树的高度)
- 如果接着调整节点的位置,完全可以达到理想平衡,但是付出的代价会比较大(调整的次数过多,反而增加了时间复杂度)
- 总结来说:用尽量少的调整次数达到适度平衡即可 一棵达到适度平衡的二叉搜索树可以称为:平衡二叉搜索树
平衡二叉搜索树(Balanced Binary Search Tree)
英文简称:BBST
常见的平衡二叉搜索树:
-
AVL树(Windows NT内核中广泛使用)
-
红黑树
- C++ STL(比如 map、set )
- Java 的 TreeMap、TreeSet、HashMap、HashSet
- Linux 的进程调度
- Ngix 的 timer 管理
一般也称它们为:自平衡的二叉搜索树(Self-balancing Binary Search Tree)
AVL树
平衡因子:某节点左右子树的高度差
AVL树特点:
- 每个节点的平衡因子只能是
-1、0或者1 - 每个节点左右子树高度差不超过
1 - 搜索、添加、删除的时间复杂度是
添加导致的失衡
示例:往下面这棵子树中添加13
◼ 最坏情况:可能会导致所有祖先节点都失衡
◼ 父节点、非祖先节点,都不可能失衡
失衡后的调整根据失衡节点和导致失衡的原因分为四种:LL、LR、RL、RR
LL-右旋转(单旋)
在添加完红色节点后直接导致g失衡,且导致失衡的方向是g左节点的左节点,方向上是LL
修改方案:
g.left=p.rightp.right=gp称为这棵子树的根节点- 这棵子树仍旧是一棵二叉搜索树
注意维护的内容:
T2、p、g的parent属性- 先后更新
g、p的高度
RR-坐旋转(单旋)
在添加完红色节点后直接导致g失衡,且导致失衡的方向是g左节点的右节点,方向上是RR
修改方案:
g.right=p.leftp.left=gp称为这棵子树的根节点- 这棵子树仍旧是一棵二叉搜索树
注意维护的内容:
T1、p、g的parent属性- 先后更新
g、p的高度
LR-RR左旋转,LL右旋转(双旋)
先对p左旋转,再对g右旋转
RL-LL右旋转,RR左旋转
先对p右旋转再对g左旋转
代码实现
分为四种不同情况的代码实现
思路:
-
调整一定是要在添加之后,所以在父类
二叉搜索树中定义子类实现的调整方法afterAdd -
因为失衡只会发生在祖先节点上,故循环遍历增加节点的父节点、祖父节点一直向上
-
判断节点是否失衡
-
如果未失衡,只需要更新节点高度
-
如果失衡,需要恢复平衡并且退出循环,调整第一个失衡节点后,失衡子树的高度不会变化,所以向上的节点仍然平衡
-
恢复平衡思路:
-
根据上面分析,所有失衡的情况都可以归纳为四类:
LL、RR、LR、RL -
根据图中找出
g、p、n节点-
grand节点就是失衡节点 -
parent节点就是grand的左右节点中高度最高的节点 -
同理
node节点就是parent的左右节点中高度最高的节点
-
-
其实无论是四种情况中的哪一种都可以归类为
左旋或者右旋的单独操作或者结合,所以旋转方法只分为左旋和右旋即可 -
左旋或者右旋的操作中就是维护旋转后节点之间的连线,以及最后要更新g和p的高度
统一所有旋转操作
对于上面分析来说,总共分为四种情况去做了不同的操作,还是比较麻烦,现在将其归类为一种
还是同样是四种情况,但是根据顺序a<b<c<d<e<f<g,虽然是四种不同的情况,但是调整平衡之后他们的相对顺序是一样的,那么我们只需要把不同情况的a、b、c、d、e、f、g给分别找出来即可
对比这两幅图可以很快确定a、b、c、d、e、f、g的对应关系
删除导致的失衡
添加会导致失衡,同样删除也会导致失衡
例如下图删除子树中的16可能会导致父节点或者祖父节点失衡(只有一个节点失衡),其它节点都不可能失衡
同样也分四种情况来分析
LL-右旋转(单旋)
RR-左旋转(单旋)
LR – RR左旋转,LL右旋转(双旋)
RL – LL右旋转,RR左旋转(双旋)
对于上面四种情况来说,都有一些共同点:
- 如果绿色节点不存在,更高层的祖先节点可能也会失衡,需要再次恢复平衡,然后又可能导致更高层的祖先节点失衡
- 极端情况下,所有祖先节点都需要进行恢复平衡的操作,共 次调整
删除之后的恢复平衡代码
总结
-
添加
- 可能会导致所有祖先节点都失衡
- 只要让高度最低的失衡节点恢复平衡,整棵树就恢复平衡(仅需级别的调整)
-
删除
- 可能会导致父节点或祖先节点失衡(只有1个节点会失衡)
- 恢复平衡后,可能会导致更高层的祖先节点失衡(最多需要 次调整)
-
平均时间复杂度
- 搜索:
- 添加:,仅需 O(1) 次的旋转操作
- 删除:,最多需要次的旋转操作