要学习红黑树,首先我们要先了解二叉查找树,二叉查找树有如下特性
1.左子树上所有结点的值均小于它的根结点的值。
2.右子树上所有结点的值均大于它的根结点的值。
3.左、右子树也分别为二叉查找树。
下面我们来看一个典型的二叉查找树,查找值为10的节点。
图1
首先根节点9小于10,所以查看右子树节点13。
图2
节点13大于10,所以我们再查看左子树节点11。
图3
由于11大于10,所以我们再查看左子树节点,结果成功查找到值为10的节点。
图4
这种方式正是二分查找的思想,查找所需的最大次数等同于二叉查找树的高度。在插入节点的时候,也是采用类似的方法,通过一层一层比较大小,找到新节点适合插入的位置。但是插入或者删除后要保持树的平衡,是一件麻烦事。比如图5初始的二叉查找树只有三个节点,根节点值为9,左孩子值为8,右孩子值为12;我们依次插入如下五个节点:7,6,5,4,3。由每次插入的节点都比最小值要小,最终所有的节点就连成了一条链,如图6所示,此时深度为n,查找的时间复杂度就是O(n)了。
图5
图6
为了解决这个问题,有人发明了平衡二叉树,又被称为AVL树,具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是,平衡二叉树也有一定的局限。对于平衡二叉树来说,每个节点的子树的高度差不能超过1,一旦出现违反的情况,就需要进行左旋或者右旋操作,从而调整二叉树的高度。但是,在插入和删除节点的过程中,平衡度超过1是经常发生的事情,这也就导致了调整操作频繁地发生,从而提高时间复杂度。也就是说,这种追求绝对平衡的行为,并不是一个好的做法。
这就引出了我们今天的主角红黑树(RB_Tree),红黑树是一种自平衡的二叉查找树。除了符合二叉查找树的基本特性外,它还需要满足如下规则:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
下面这棵树,就是一个典型的红黑树
图7
这五条规则单独分开来看都很好理解,但是放在一起就有点迷惑了,为什么是这五规则,它们之间有什么关系?其实这些复杂的定义和规则都是为了保证树的平衡性,因为保证树的平衡性才能降低树的高度。树的查找性能取决于树的高度,所以树的高度越低搜索的效率越高!这也是为什么存在二叉树、搜索二叉树等,各类树的原因。
当插入或删除节点的时候,红黑树的规则可能被打破,这时候需要我们进行调整来维持这些规则。下面举个例子。
我们向刚才图5的红黑树插入值为14的新节点。
再插入值为21的节点。
图9
由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。
那需要怎么调整呢?一般有两种方法,变色和旋转,而旋转又分为两种形式,左旋转和右旋转。
首先来看变色:
为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色。
下图所表示的是刚才红黑树的一部分,需要注意节点25并非根节点。因为节点21和节点22连续出现了红色,不符合规则4,所以把节点22从红色变成黑色:
但这样并不算完,因为凭空多出的黑色节点打破了规则5,所以发生连锁反应,需要继续把节点25从黑色变成红色:
图11
此时仍然没有结束,因为节点25和节点27又形成了两个连续的红色节点,需要继续把节点27从红色变成黑色:
图12
看完变色,再来看看旋转。
左旋转
逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子。
右旋转
顺时针旋转红黑树的两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子。
继续刚才的例子,经过刚才调整后红黑树变成了下图所示:
图15
此时节点17和节点25是连续的两个红色节点,那么把节点17变成黑色节点?恐怕不合适。这样一来打破了规则4,而且根据规则2(根节点是黑色),也不可能把节点13变成红色节点。
这时我们可以把节点13看做X,把节点17看做Y,像刚才的示意图那样进行左旋转:
图17
图18
由于根节点必须是黑色节点,所以需要变色,变色结果如下:
图19
这样就结束了吗?并没有。因为其中两条路径(17 -> 8 -> 6 -> NIL)的黑色节点个数是4,其他路径的黑色节点个数是3,不符合规则5。
这时候我们需要把节点13看做X,节点8看做Y,像刚才的示意图那样进行右旋转:
图20
图21
图22
最后根据规则来进行变色:
图23
如此一来,我们的红黑树变得重新符合规则。关于红黑树自平衡的调整,插入和删除节点的时候会涉及到很多种Case,都会采取不同的操作,但是核心思想都是为了维持五条规则。
此外,红黑树还有一条定理,即红黑树从根到叶子的最长路径不会超过最短路径的两倍。这条定理是怎么来的呢?它是通过红黑树的五条规则推导而来。我们看上述规则5可知,从根节点到任意一个叶子节点,经过的黑色节点数目是相同的。而规则4又规定,不能有两个红色节点连在一起。于是我们这样考虑,假设从根节点到每个叶子节点,都是经过3个黑色节点,那么怎样才能让路径尽可能长?答案当然是尽量在黑色节点中插入红色节点,用红色节点充数。但是由于规则4,所以红色节点不能多放。于是,理论上的最长路径,一定是一个黑节点,连着一个红节点,再连着一个黑节点,以此类推。我们之前假设每条路径都是3个黑节点,再加上规则2说根节点一定是黑色,所以从根节点到子节点最多只能有6个节点,三黑三红交叉连接。那理论上的最短路径又是如何?当然是不包含任何一个红色节点,也就是只有3个黑节点。也就是说,理论上,最长路径,最多是最短路径的两倍。这个结论就限制了红黑树的最大平衡差,也就保证了不会出现图6的情况。
红黑树的应用有很多,jdk的集合类TreeMap和TreeSet底层都是就是用红黑树实现的,在java8中,HashMap也用到了红黑树。
与b树和b+树对比看,红黑树适合小数据范围内的快速查找(比如map当量的容器)(查询时间复杂度是O(logN))。b树适合大范围数据查找(db场合)。
而与完全二叉树相比,红黑树的查找、插入和删除的时间复杂度都是O(log n),而完全二叉树的查找时间复杂度也是O(log n),但插入和删除的时间复杂度为O(n)。因此,在实际应用中,红黑树通常比完全二叉树更受欢迎,因为它在维护有序性的同时,具有较好的平衡性能。
引用:https://blog.csdn.net/qq_36610462/article/details/83277524 原文讲的非常好,建议大家学习。
https://www.cnblogs.com/tuyang1129/p/12556519.html
网友评论