数据结构 —— 红黑树(C语言源码)

来源:互联网 发布:数控折弯编程教学视频 编辑:程序博客网 时间:2024/06/11 02:16

源代码地址:https://github.com/Zheng-Wenkai/DataStructure.git

一,红黑树的介绍

1. 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。2. 在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(logn)。


二,二叉查找树的回顾

1. 二叉查找树的介绍(a)二叉查找树也称有序二叉树,或已排序二叉树;(b)树的这种数据结构,既能像链表那样快速的插入和删除,又能想有序数组那样快速查找。2. 二叉查找树的基本特征(a)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;(b)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;(c)没有键值相等的节点;(d)需要保证树总是平衡的(或者至少大部分是平衡的),这就是说对树中的每个节点在它左边的后代数目和在它右边的后代数目应该大致相等

三,红黑树的基本特性

1. 每个结点要么是红的,要么是黑的。2. 根结点是黑的。3. 每个叶结点,即空结点(NIL)是黑的。4. 如果一个结点是红的,那么它的俩个儿子都是黑的。(如果一个结点是黑的,则它的俩个儿子不作要求)5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。(故需要特别注意黑色节点的设置)

四,红黑树平衡性的修正

1. 部分结点颜色,重新着色2. 调整部分指针的指向,即左旋、右旋。

(a)左旋


(b)右旋


五,红黑树的插入和插入修复

核心思路:将红色的节点移到根节点(即将出现的问题由下向上抛);然后,将根节点设为黑色

插入的节点必须是红色的。将插入的节点着色为红色,不会违背”特性(5)”!少违背一条特性,就意味着我们需要处理的情况越少。

第一步: 将红黑树当作一颗二叉查找树,将节点插入。从树根开始向下查找,找到val值对应的位置,将节点插入;如果查找到与val值相同的结点,则什么也不做,直接返回

第二步:将插入的节点着色为”红色”。

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。即插入修复。

情况1:插入节点z的父节点是黑色。

    解法:什么也不需要做。节点被插入后,仍然是红黑树。

情况2:插入节点z是根节点。

    解法:直接把此节点涂为黑色。

情况3:插入节点z的父结点是红色的,叔叔结点也是红色的。

    解法:    (a)将叔叔涂黑;    (b)将父结点涂黑;    (c)将祖父节点涂成红色;    (d)当前节点z的祖父节点成为新的当前节点z。

变化前(当前结点4):


变化后:


思路分析:

1、当前节点4和父节点5都是“红色”,违背“特性(4)”。解决方法是:将父节点5设置“黑色”
2、但是,将父节点5变成“黑色”后,父节点所在分支的“黑色”节点的总数增加了1,违背了“特性(5)”。 解决办法是:将祖父节点7变成“红色”,同时,将叔叔节点8变成“黑色”。
3、当前节点4、父节点5、叔叔节点8之间都不会违背红黑树特性,但祖父节点7却不一定。解决方法是:若此时,祖父节点7是根节点( 即二叉树为{ 7,5,8,4} ),直接将祖父节点7设为“黑色”;若祖父节点7不是根节点,那我们需要将祖父节点7设为新的当前节点,接着对新的当前节点7进行分析。
4、破坏红黑树特性的节点由节点4变成节点7

情况4:插入节点z的父结点是红色的,叔叔结点是黑色的,且x是右孩子。

    解法:    (a)当前节点z的父结点成为新的当前结点z;    (b)以新当前结点为支点进行左旋;

变化前(当前结点7):


变化后:


思路分析:

1、若此时父结点2是根节点(即二叉树为{ 2,1,7,5,8,4}),直接将父结点2涂为黑色即可。

2、若此时父结点2不是根节点,则需要对当前结点7做进一步的分析。
<1>、 如果将父节点2染成黑色,以遵循“特性(4)”,那么父节点2所在分支的“黑色”节点的总数增加了1,违背“特性(5)”,那么我们将祖父节点11染成红色,此时违背了“特性(5)和特性(2)”,故以祖父节点11为支点进行右旋,但此时根节点2的左孩子1是黑色的,违背了“特性(4)”,故该方式不可行。
<2>、如此看来,似乎情况4陷入了死局。那么,让我们来回顾一下红黑树插入的核心思路:将红色的节点移到根节点;然后,将根节点设为黑色。既然是“将红色的节点移到根节点”,那就是说要不断将破坏红黑树特性的红色节点(即当前结点)上移(即向根方向移动), 而结点7又是结点2右孩子。解决方法是:通过“左旋”来将破坏红黑树特性的“红色”节点7上移!
<3>、破坏红黑树特性的节点7上移,破坏红黑树特性的节点由节点7变为节点2
<4>、从另一个角度看,若发生情况4且当前结点的父结点不是根结点的话,我们就将情况4通过左旋转化为情况5。

情况5:插入节点z的父结点是红色的,叔叔结点是黑色的,且x是左孩子。

解法:

(a)将父结点涂黑(b)将祖父节点涂红(c)以祖父节点为支点进行右旋

变化前(当前结点2):


变化后:


思路分析:

1、若此时父结点7是根节点(即二叉树为{ 7,2,8,1,5,4}),直接将父结点7涂为黑色即可。

2、若此时父结点7不是根节点,则我们需要作进一步分析。

<1>、当前节点2和父结点7都是“红色”,违背了红黑树的“特性(4)”。解决方法:将父结点7由“红色”变为“黑色”<2>、但父结点7由红色改为黑色之后,所有经过F的分支的黑色节点的个数增加了1,违背了红黑树的“特性(5)”。    解决方法:“将祖父节点11由黑色变成红色”,同时“以祖父节点11为支点进行右旋”来解决。<3>、破坏红黑树特性的节点2上移。

六,红黑树的删除和删除修复

第一步:将红黑树当作一颗二叉查找树,将节点删除。

① 被删除节点z没有儿子,即为叶节点。那么,令y=z,直接删除y节点就可以了。
② 被删除节点z只有一个儿子。那么,令y=z,删除y。并用该节点的唯一子节点顶替它的位置,即y必定有唯一的一个右孩子或左孩子,y的孩子与y的父亲应当建立关系。
③ 被删除节点z有两个儿子。那么,先找出它的后继节点y;然后把“它的后继节点y的内容”复制给“节点z的内容”;之后,删除“它的后继节点”y。此时,y可能有右孩子(不可能有左孩子),若y有右孩子,y的右孩子与y的父亲应当建立关系。

第二步:通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。即删除修复。

如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。以下内容是删除节点为黑色的修复情况。

情况1:当前节点x的兄弟w是红色的。

解法:(a) 把兄弟节点涂成黑色;(b)父结点涂成红色;(c)以当前结点的父结点为支点进行左旋;(d)x的兄弟节点发生变化,要重新定位

变化前:


变化后:


思路分析:

1、把问题转换成兄弟节点为黑的情况(即情况2,3,4)。解决方法:以D为节点进行左旋
2、根节点D是红色的,违反“规则2”。解决方法:将D节点涂成黑色
3、包含“B节点”的分支的黑色节点的总数增加了1,违反“规则5”。解决方法:将B节点涂成红色。

情况2:当前节点x的兄弟w是黑色的,且w的俩个孩子都是黑色的。

解法:(a)将兄弟节点w涂成红色;(b)将当前结点x的父结点作为新的当前结点x

变化前:


变化后:


思路分析:

1、若删除了当前结点 x(即结点),因为 x 子树相对于其兄弟子树 w 少一个黑色节点,可以将w置为红色,这样,x子树与w子树黑色节点一致,保持了平衡。
2、将 x 结点的父结点(即B结点)作为新的当前节点 new x。new x相对于它的兄弟子树new w少一个黑色结点。
(实质是将不平衡点上移至x的父亲,进行下一轮迭代)
3、如果new x为红色,则将new x置为黑,则整棵树平衡。
4、如果new x为黑色的,则要根据情况重新进入情况1,2,3,4

情况3:当前节点x的兄弟w是黑色的,且w的左子是红色,w的右子是黑色。

解法:(a)把兄弟节点涂成红色;(b)把兄弟节点的左子节点涂黑;(c)以兄弟节点为支点做右旋操作;(d)x的兄弟节点发生变化,要重新定位

变化前:


变化后:


思路分析:

1、把情况3转换为情况4

情况4:当前节点x的兄弟w是黑色的,且w的右子是红色的,w左子颜色任意。

解法:(a)把兄弟结点涂成和父结点相同的颜色;(b)将父亲结点染成黑色;(c)将兄弟结点的右孩子染成黑色;(d)以当前结点的父结点为支点进行左旋;(e)x的兄弟节点发生变化,要重新定位。

变化前:


变化后:


思路分析:

1、兄弟节点的右儿子是红色节点,那么将右儿子染成黑色,从整体上补上缺失的黑色。
2、再通过旋转,补回 x 子树中因为删除黑色节点而丢失的黑色。

七,红黑树与AVL树的比较

  1. 如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,时间复杂度O(1)。

  2. 如果删除一个node引起了树的不平衡,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,时间复杂度O(logN),而RB-Tree最多只需3次旋转,时间复杂度O(1)。

  3. 其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。

  4. map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

因此,在需要大量插入和删除node的场景下,RB-Tree效率更高。而AVL由于高度平衡,search的效率更高,适用于插入删除node少,查询多的场景。

源代码地址:

https://github.com/Zheng-Wenkai/DataStructure.git

参考内容:

http://www.cnblogs.com/skywang12345/p/3624177.html

http://blog.csdn.net/v_JULY_v/article/details/6105630

https://www.zhihu.com/question/20545708/answer/58717264

阅读全文
1 0