二叉平衡搜索树、红黑树和二三树
引言
我们学习数据结构和算法,要学习它的由来、特性、适用的场景,以及它能解决的问题。
由来
我们知道二叉搜索树有很好的查询,插入和删除特性,它的查找速度和它的高度有关。但是如果一个二叉树它左右子树非常的不平衡极端情况下退化成一个链表,那么它的查询效率就会从log(n)退化成o(n)的时间复杂度。为了避免这种情况的发生二叉平衡搜索树应运而生。
定义
- 任何一个节点的左子树的高度 和 右子树的高度差不能超过1
- 这个二叉树是一颗二叉搜索树(即任意一个节点,它的左子树的值都小于这个节点,它的右子树的值都大于这个节点)
下面分别介绍一种平衡二叉搜索树AVL树 和 一种近似二叉平衡搜索树红黑树
AVL树
-
每个节点存入一个平衡因子:
是它的左子树的高度减去它的右子树的高度(有时候相反)。
balance factor = {-1,0,1} 这样就可以保证左子树的高度和右子树的高度差不超过1 -
通过四种旋转操作(左旋,右旋,左右旋,右左旋)来实现平衡
下面是一颗AVL树的示例:
接下来我们先介绍,为了维护平衡的四种基本的旋转操作,然后通过举一个例子的方式来帮助更好的理解。
四种旋转操作
- 左旋
- 右旋
- 左右旋
- 右左旋
右右子树->左旋
左左子树->右旋
左右子树 ->左右旋
右左子树 ->右左旋
注意:这四种基本的旋转操作一定要非常熟悉
下面举一个实例,练习一下上面所说的旋转操作,给下面的平衡二叉树添加3,它会如何调整:
结果如下图所示:
删除
从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。
AVL总结
- 平衡二叉搜索树
- 每个节点存储 balance factor = {-1,0,1}
- 四种旋转操作
不足:结点需要存储额外信息、并且调整次数非常频繁。
2-3树
红黑树的实现思想有一种就是2-3树,如果想了解红黑树为什么会有红黑两种颜色的节点,以及他的旋转操作和颜色变换是为什么那就要先理解二三树。
这里附上两个讲2-3树很好的链接:
红黑树
发明平衡二叉查找树这类数据结构的初衷是,解决普通二叉查找树在频繁的插入、删除等动态更新的情况下,出现时间复杂度退化的问题。
**所以,平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些。**所以,如果我们现在设计一个新的平衡二叉查找树,只要树的高度不比 log2n 大很多(比如树的高度仍然是对数量级的),尽管它不符合我们前面讲的严格的平衡二叉查找树的定义,但我们仍然可以说,这是一个合格的平衡二叉查找树。
通过上面的讲解我们可以看到平衡二叉树虽然性能很稳定,查询性能很稳定,但是同样的它维护树的平衡的成本也很高,往往插入一个数据就要进行旋转操作,很显然这种调整操作也是很浪费时间的。所以我们就想,能不能退而求其次,就是调整次数没有那么频繁,也不是严格的平衡二叉树,但是查询性能也不会下降太多,随着这样的想法,红黑树就产生了。
定义
红黑树是一种近似平衡二叉搜索树,它能够确保任何一个结点的左右子树的高度差小于两倍。具体来说,红黑树是满足如下条件的二叉搜索树:(这五个特点要记住)
- 节点要么是黑色,要么是红色
- 根节点是黑色
- 每个叶子结点(NIL,空结点)是黑色的
- 不能有相领接的两个红色结点
- 从任何一个节点到其每个叶子结点的所有路径都包含相同的黑色结点
红黑树的旋转与注意事项
红黑树的另一种定义是含有红黑链接并满足下列条件的二叉查找树:
- 红链接均为左链接
- 没有任何一个节点同时和两条红链接相连
- 该树是完美的黑色平衡的,即任意空链接到根节点的路径上的黑链接数量相同
注意:红黑树的实现是多种多样的,只要是能满足红黑树的5点性质就是一颗符合要求的红黑树。但是通过2-3树来实现红黑树是维持红黑树平衡的最好的一种变换规则,也是最常用的规则 ,所以要理解
具体理解参见上面给出的两个博客
在java的众多集合类中,仔细研究大家可能会发现,TreeMap,TreeSet,都是红黑树的实现。本质上TreeMap是真正的红黑树的实现,TreeSet是对TreeMap的二次封装。还有一个重要的集合,HashMap,对是他是他就是他,几乎每次面试面试官都会死磕一遍HashMap,除此之外他也是我们日常开发工作中最常用的集合之一。感兴趣的可以研究一下HashMap。在HashMap中当一个索引位置维护的链表长度超过8即转换为红黑树,小于6从红黑树转换为链表。为什么是6,8而不是8,8主要是一个复杂度震荡的问题。