红黑树
备注:
-
满二叉树:每一层都满节点
-
完全二叉树:只有最后一层不满,且空缺只能在右边
-
二叉查找树:左子树的键值小于根的键值,右子树的键值大于根的键值。每层不一定满
-
平衡二叉树(AVL):二叉查找树基础上,任何节点的两个子树的高度最大差为1
红黑树是一种自平衡,节点额外带个颜色属性的二叉查找树:
-
每个节点要么是红色,要么是黑色;
-
根节点永远是黑色;
-
所有的叶子节点都是是黑色的(下图中的 NULL 节点);
-
红色节点的子节点一定是黑色的;(不看叶子结点,奇数层颜色黑,偶数层红)
-
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
为什么不用二叉树?
二叉树是最基本的树结构,每个节点最多有两个子节点,但是二叉树容易出现极端情况,比如插入的数据是有序的,那么二叉树就会退化成链表,查询效率就会变成 O(n)。
为什么不用平衡二叉树?
平衡二叉树比红黑树的要求更高,每个节点的左右子树的高度最多相差 1,这种高度的平衡保证了极佳的查找效率,但在进行插入和删除操作时,可能需要频繁地进行旋转来维持树的平衡,这在某些情况下可能导致更高的维护成本。
红黑树是一种折中的方案,它在保证了树平衡的同时,插入和删除操作的性能也得到了保证,查询效率是 O(logn)。
红黑树的优势
红黑树是一种自平衡的二叉搜索树,具有以下优势:
一、高效的查找性能
-
时间复杂度低:
-
红黑树的查找操作与普通二叉搜索树类似,时间复杂度为,其中是树中节点的数量。这意味着在红黑树中查找一个特定的值所需的时间与树的大小的对数成正比,即使在树包含大量节点的情况下,查找操作也能相对快速地完成。
-
例如,在一个包含 1000 万个节点的红黑树中,查找一个特定的值最多只需要大约 20 次比较操作(log210000000 ≈ 23)。
-
-
二叉搜索树特性:
-
红黑树保持了二叉搜索树的特性,即对于树中的每个节点,其左子树中的所有节点的值都小于该节点的值,右子树中的所有节点的值都大于该节点的值。这使得在红黑树中进行查找时,可以通过比较目标值与当前节点的值,确定下一步在左子树还是右子树中继续查找,从而快速缩小搜索范围。
-
例如,要在红黑树中查找值为 50 的节点,从根节点开始,如果根节点的值大于 50,则知道应该在左子树中继续查找;如果根节点的值小于 50,则在右子树中查找。
-
二、高效的插入和删除性能
-
自平衡特性:
-
红黑树能够在插入和删除节点后自动调整树的结构,以保持平衡。这意味着即使在进行多次插入和删除操作后,红黑树的高度仍然相对较低,从而保证了查找、插入和删除操作的高效性。
-
例如,在普通的二叉搜索树中,如果依次插入一组有序的数据,可能会导致树退化为一条链,此时查找、插入和删除操作的时间复杂度会退化为。而红黑树在插入节点后,会通过旋转和重新着色等操作来保持树的平衡,避免了这种情况的发生。
-
-
旋转和重新着色操作:
-
当插入或删除节点时,红黑树可能会违反红黑树的性质(如红黑节点的数量规则、从根节点到叶节点的路径上黑色节点数量相同等)。为了恢复这些性质,红黑树会进行旋转和重新着色操作。这些操作相对简单且高效,能够在较短的时间内完成树的调整。
-
例如,当插入一个新节点后,如果新节点的父节点和叔叔节点都是红色,就需要进行重新着色操作,将父节点和叔叔节点变为黑色,祖父节点变为红色,然后再从祖父节点开始继续调整树的结构。如果调整过程中出现不平衡的情况,可以通过旋转操作来恢复平衡。
-
三、适用性广泛
-
动态数据结构:
-
红黑树适用于动态数据集合,即数据集合中的元素会频繁地进行插入、删除和查找操作。由于红黑树能够在这些操作后快速地恢复平衡,保持高效的性能,因此在许多需要动态维护数据结构的场景中得到广泛应用。
-
例如,在数据库索引、内存管理、文件系统等领域,红黑树被用作高效的数据结构来管理动态变化的数据。
-
-
多种编程语言支持:
-
许多编程语言都提供了红黑树的实现或者可以通过库来使用红黑树。这使得开发人员可以方便地在各种项目中使用红黑树,无需自己实现复杂的平衡二叉搜索树算法。
-
例如,在 Java 中,
java.util.TreeMap
和java.util.TreeSet
内部就是使用红黑树实现的;在 C++ 中,std::map
和std::set
通常也是基于红黑树实现的。
-
总之,红黑树以其高效的查找、插入和删除性能,以及自平衡特性和广泛的适用性,成为了一种非常重要的数据结构。
平衡原理:
向红黑树中插入节点14(一般默认插入节点是红色的)。
向红黑树中插入节点20(一般默认插入节点是红色的)
可以看到,插入以后树已经不是一个平衡的二叉树,而且并不满足红黑树的要求,因为20和21均为红色,这种情况下就需要对红黑树进行变色,21需要变为黑色,22就会变成红色,如果22变成红色,则需要17和25都变成黑色。
而17变成黑色显然是不成立的,因为如果17变为黑色,那么13就会变为红色,不满足二叉树的规则,因此此处需要进行另一个操作---------左旋操作。
左旋: 下图就是一个左旋的例子,一般情况下,如果左子树深度过深,那么便需要进行左旋操作以保证左右子树深度差变小
对于上图由于右子树中17变为黑色以后需要把13变成红色,因此进行一次左旋,将17放在根节点,这样既可保证13为红色,左旋后结果:
而后根据红黑树的要求进行颜色的修改。
进行左旋后,发现从根节点17,到1左子树的叶子节点经过了两个黑节点,而到6的左叶子节点或者右叶子节点要经历3个黑节点,很显然也不满足红黑树,因此还需要进行下一步操作,需要进行右旋操作。
右旋: 与左旋正好相反。
由于是从13节点出现的不平衡,因此对13节点进行右旋,得到结果。
而后再对其节点进行变色,得到结果。
这便是红黑树的一个变换,它主要用途有很多,例如java中的TreeMap以及JDK1.8以后的HashMap在当个节点中链表长度大于8时都会用到。
总结:
(1) 当出现新的节点时默认为红色插入,如果其父节点为红色,则对其递归向上换色,如果根节点由此变为红色,则对根节点进行左旋(右侧过深)或右旋(左侧过深),之后从根节点向下修改颜色.
(2)从根节点检查红色节点是否符合路径上的黑色节点数量一致,如果不一致,对该节点进行左旋(右侧黑色节点数量更多)或右旋(左侧黑色节点数量更多),并变换颜色,重复2操作直到符合红黑树规则。
参考链接: