简单的语言讲完红黑树要点
作者:互联网
目录
为什么需要红黑树
C++的std::map就是通过红黑树实现的。map相对于list的优点是,取出任何一个元素时,map的效率是O(log n),而list要遍历,其效率是低于map的。
map的高效正是得益于红黑树的特性。
来看一个例子。吴军在其著作《浪潮之巅》中,举了一个猜测世界杯冠军的例子:把32强从1-32编号,竞猜者随意从中挑一个号码:“是第X号吗?比冠军的编号高了还是低了?”假如高了,则从0-X之间取中间数,再猜;假如低了,就从X-32之间挑出中间数再猜。如此往复。这是用二分法快速查询的典型例子,其复杂度是。
红黑树的查找与之类似。假如要查找值X,从根节点触发,每到一个节点,判断被查值X与结点之间是大于、等于还是小于。根据判断结果决定下一步的操作:(走入左分支、返回结果、走入右分支)。其效率也是。
红黑树的特性
为了实现高效率,红黑树被约定了如下的特性:
1 根节点是黑色
2 每个节点有两个子节点,但叶子节点(叶子节点是不具备实际意义的节点)没有子节点。且红色节点的子节点只能是黑色
3 红黑树的末梢节点只能是黑色--这些节点通常是不具备实际意义的节点(叶子节点)
4 从一个节点出发,逐层下行,直到任何一个末梢结束,经历的黑色节点个数都相等。
性质2决定了,在任何一条支路上,红色节点的数目不可能超过黑色节点。再考虑到性质4,可以推论最长的支路节点数最多是最短支路节点数的2倍,不会再多了。
这条推论意义重大。既然希望红黑树的查找效率高于链表,树的根节点就应处于所有元素的中间值附近。远远偏离中间值的红黑树将退化为链表:
图片来源 https://www.cnblogs.com/Mufasa/p/11581141.html
红黑树的性质2,4限制了根节点的位置,不会过度偏离中间值。
补充一条,红黑树每个节点的分叉,都是方向一致的:所有左边分叉的值都小于分叉节点,右边分叉的值都大于分叉节点;或者反过来。否则二分查找是不可能的。
如何插入新节点
对红黑树的操作无外乎查询、插入、删除三种。查询在第一部分已经描述了。
最简单的情形:插入的是第一个元素
将节点染成黑色即可。根节点下面增加两个没有实际意义的叶子节点。
图片来源 https://www.youtube.com/watch?v=JwgeECkckRo
一般的情形:插入的不是根节点
图片来源 https://www.youtube.com/watch?v=JwgeECkckRo
1)跟查询类似,从根节点开始,逐步判断新节点5与各个节点的大小关系:假如小于节点,左转;反之右转。逐层走下去,直到叶子节点。
2)到达叶子节点后,将新节点染色为红色,代替原有的叶子节点,并在其下面增加两个黑色叶子。
3)接下来要顾虑到性质2--红节点的子节点不能是红色。这一步是整个文章的重点。我分出单独一节来讲。
如何用一个红色节点代替叶子节点
1)假如叶子的父节点是黑色。这时不用调整。红黑树所有的性质仍然被满足。
2)假如父节点是红色,此时为了保证性质2,必须调整某些节点的颜色。
这时无外乎3种情况:
情况A 父节点是红色、叔节点是黑色、祖父节点(必然)是黑色(因为父节点已经是红色)。另外,新节点插入在祖节点分支的最外侧(即最左或者最右)。
情况B 父节点是红色、叔节点是黑色、祖父节点(必然)是黑色(因为父节点已经是红色)。另外,新节点插入在祖节点分支的内侧(即父节点在祖节点左边,但子节点在父节点右边;或者父节点在祖节点右边,但子节点在父节点左边)。
情况C 父节点是红色、叔节点是红色、祖父节点(必然)是黑色(因为父节点已经是红色)
2.1)情况A。对付这种情况的套路可以称之为“换位”
2.1.1)将父节点变成黑色,满足性质2.但是性质4被破坏,因为父节点这条支路上的黑节点个数增加了。
2.1.2)换位--把父节点移动到祖结点的位置上,这样父节点这一支黑节点个数减一。把祖结点变成父节点的子节点,这样叔节点这一支的黑节点个数加一。性质4仍不满足.
2.1.3)但是此时父-祖-叔的三个节点全是黑色。可以将祖结点变成红色,既满足了性质4,又不破坏性质2.
2.2)情况B。这时2.1)的套路不好用了,因为祖节点和新节点在父节点的同侧。祖节点和父节点换位之后,父节点变成了祖节点和子节点共同的父亲。这时两个节点不应该出现在同侧。
但是这个情况可以转化为情况A,故可以视为A的变种:对调子节点和父节点的位置即转化为情况A。
2.3)情况C。对付这种情况的套路可以称之为“分裂”
2.3.1)将祖结点变成红色,叔节点和父节点变为黑色。这样,本来祖节点贡献的一个黑色节点数目,改为由它的两个子节点贡献。故性质4被满足。此外,父节点的变色也使得性质2被满足。
2.3.2)祖结点变为红色后,有可能会影响与曾祖节点的关系。因为以前祖节点是黑色,所以曾祖节点可能是黑色,也可能是红色。如果曾祖是红色,将破坏性质2.但是这时的情况又跟开始一样了:插入一个红节点。所以回到《如何用一个红色节点代替叶子节点》的开头继续操作。
综合以上,情况A和情况B是好处理的,但是情况C可能要反复调用,最坏的情况,直到根节点。所以最坏情况下,C的开销最大。但是,不难看出,无论是哪种情况,被操作的只有直系的节点(祖父、父亲)以及与直系直接相连的节点。前面说了,红黑树从上到下的层数大约是量级。故直系这条线上节点数也是这个量级。就算把与直系相邻的节点也算上,再乘以2即可,不会改变数量级的。所以插入操作的复杂度就是
有人要问了,操作叔节点,会不会引起与叔节点相邻的其它节点破坏红黑树性质。不会的。首先,以上操作,只会把树节点的颜色从红变黑,不会从黑变红,所以性质2被满足。其次,前面分析了,叔节点变成黑色的同时,祖节点和父节点也随之调整,保证性质4不被破坏。
标签:黑色,插入,要点,红色,红黑树,讲完,节点,性质 来源: https://blog.csdn.net/liji_digital/article/details/118358748