红黑树实现原理

原文链接:https://blog.csdn.net/v_JULY_v/article/details/6105630

一、红黑树介绍

1、定义

  1. 每个结点或者为黑色或者为红色
  2. 根结点为黑色
  3. 每个叶结点(实际上就是NULL指针)都是黑色的
  4. 如果一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点
  5. 对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同

2、二叉树和AVL树比较

1、二叉树

  • 任意节点左子树不为空,则左子树的值均小于根节点的值.
  • 任意节点右子树不为空,则右子树的值均大于于根节点的值.
  • 任意节点的左右子树也分别是二叉查找树
  • 没有键值相等的节点.
    由二叉查找树的性质可知,构成二叉树的结构和插入数据的顺序有关,存在不稳定性。最好情况可以构建成平衡二叉树查找效率O(lgn),最坏情况也有可能构造成线性树(查找效率O(n))
    二叉树
    2、AVL树
    AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡。
    左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。
    AVL树的插入 删除元素必须保持绝对的平衡,所以从插入删除元素起,要一直进行旋转操作一直到根节点,所以
    插入/删除/查找操作比较稳定,时间复杂度是O(logn)
    AVL树
    3、红黑树
    红黑树也是一种平衡二叉树,但是它与AVL树相比是一种相对宽松的平衡,平衡因子是从任意非叶子节点到其所有叶子节点的路径上黑色节点保持一致(红黑树性质5),所以当非叶子节点A到其一条路径的叶子节点包含n个黑色节点,则A到其他路径的所有节点最多只能为2n个,以此来保证易中动态平衡。
    这种平衡与AVL树相比能减少 增加和删除节点时的时间消耗,因为红黑树性质保证了新增删除节点复杂度介于O(1)到O(logn)之间;
    查找时,红黑树时间复杂度也为O(logn),相比AVL树的O(logn)只是多了一点系数,相对会慢一点。
    但是综合增加、删除和查找性能看,红黑树还是优于AVL树的。

二、红黑树实现

1、左旋、右旋

在红黑树中新加节点和删除节点时,如果需要调整红黑树时就会涉及到节点的左旋和右旋。
左旋和右旋是一组对称的操作
左旋右旋

1.1、左旋

左旋静态实现
左旋
动态实现
左旋
如上图,E-S轴指向右下方,向左旋转E-S轴时其指向右上方

1.2、右旋

静态实现
右旋
动态实现
右旋
E-S轴向右旋转

2、插入节点

2.1、插入过程

1)首先找插入位置。这个和二叉树流程一样,从根开始遍历到叶子结点找到该插入的位置。
2) 插入值节点(红色)
3) 确认插入值是否影响红黑树性质,影响就要做调整操作。

/*插入伪代码*/
B-INSERT(T, z)

/*找待插入位置*/
y ← nil
x ← T.root
while x ≠ T.nil
	do y ← x
	if z.key < x.key
		then x ← x.left
	else x ← x.right
	
/*插入元素*/
z.p ← y
if y == nil[T]
	then T.root ← z
else if z.key < y.key
	then y.left ← z
else y.right ← z
z.left ← T.nil
z.right ← T.nil
z.color ← RED

/*调整红黑树*/
RB-INSERT-FIXUP(T, z)

插入节点的颜色选黑色,必定会破坏性质5(黑色节点增加),可能需要一层一层调整红黑树 ,而插入颜色选红色,则有一半机率破坏性质4(父子节点都为红), 而且性质4调整比5调整更加简单,所以插入颜色选择红色。

2.2、插入调整

如果待插入节点的父节点为黑,则插入红色不破坏红黑树性质,不需要调整,所以需要调整必然情况必然是父节点为红色。

RB-INSERT-FIXUP(T, z)
while z.p.color == RED
	do if z.p == z.p.p.left
		then y ← z.p.p.right
		if y.color == RED
			then z.p.color ← BLACK               ▹ Case 2
			y.color ← BLACK                    ▹ Case 2
			z.p.p.color ← RED                    ▹ Case 2
			z ← z.p.p                            ▹ Case 2
		else if z == z.p.right
			then z ← z.p                          ▹ Case 3
			LEFT-ROTATE(T, z)                   ▹ Case 3
		z.p.color ← BLACK                        ▹ Case 4
		z.p.p.color ← RED                         ▹ Case 4
		RIGHT-ROTATE(T, z.p.p)                  ▹ Case 4
	else (same as then clause with "right" and "left" exchanged)
T.root.color ← BLACK

现分以插入节点的父节点是左子树的几种情况(右子树同理):
1、如果树是空树,则插入的是根。
调整方案: 则直接颜色涂黑(终态)。

2、父节点是红,叔叔节点是红。
调整方案: 则父节点和叔叔节点都涂黑,祖父节点图红(将红从父辈转嫁到祖辈),将祖父节点设为待插入节点继续递推(调整为初始态)。

调整前:N为插入元素
在这里插入图片描述
调整后:
在这里插入图片描述
3、父节点是红,叔叔节点是黑,待插入节点是右子树。
调整方案:将当前节点的父节点作为新的当前节点,以新当前节点左旋(调整为状态4)。

调整前:
在这里插入图片描述
调整后:
在这里插入图片描述
4、父节点是红,叔叔节点是黑,待插入节点是左子树。
调整方案: 将祖父节点设为红,父节点设为黑,以祖父节点右旋,相当于把父节点这边的多余红色转移到了叔叔节点那一侧,修复了红黑树性质(终态)。

调整前:
在这里插入图片描述
调整后:
在这里插入图片描述

3、删除节点

定义: 删除节点是D, 删除节点的父节点是P, 删除节点的叔叔节点是U, 删除节点的孩子节点是X

3.1、删除过程

1、定位删除节点并删除

  1. 删除节点D没有子节点,直接删除。
  2. 删除的节点只有一个子节点,则删除。
  3. 删除的节点有两个子节点,则找到这个结点的后继结点(successor),也就是它的右子树中最小的那个结点。然后我们将这两个节点中的数据元素互换,将后继节点作为待删除节点删除。(后继节点必然没有左子树,所以将情况转换成2)

2、调整删除节点的父节点P和子节点X的位置

  1. 如果删除节点D没有孩子节点,且他是根,则置树为空,否则D的父节点P指向D的指针置空。
  2. 如果删除节点D有孩子节点X,且他是根,则将X置为根节点,否则调整父节点P的孩子指针, 由原来指向D的指针指向X

3、调整红黑树

 1 if left[z] = nil[T] or right[z] = nil[T]  
 2    then y ← z  
 3    else y ← TREE-SUCCESSOR(z)  
 4 if left[y] ≠ nil[T]  
 5    then x ← left[y]  
 6    else x ← right[y]  
 7 p[x] ← p[y]  
 8 if p[y] = nil[T]  
 9    then root[T] ← x  
10    else if y = left[p[y]]  
11            then left[p[y]] ← x  
12            else right[p[y]] ← x  
13 if y ≠ z  
14    then key[z] ← key[y]  
15         copy y's satellite data into z  
16 if color[y] = BLACK  
17    then RB-DELETE-FIXUP(T, x)  
18 return y  

3.2、红黑树调整

因为删除了一个D节点,如果D节点是红色的话,不影响红黑树任何性质,所以可以不做调整,但是如果D是黑色,则D这一支子树就会少一个黑节点,影响性质5,而且P的颜色和X的颜色如果都是红色也会影响性质4,所以需要调整。所以调整时默认D是黑色,后面假设条件不做说明。
所以我们的思路是 可以将删除的D节点的黑色标记附加在X上,把X节点叫做标记节点,颜色可以说成是红+黑或者黑+黑,暂时保证性质5,然后我们在慢慢调整,将X上附加的颜色一层层往上推,最终调整红黑树保持平衡。

 1 while x ≠ root[T] and color[x] = BLACK  
 2     do if x = left[p[x]]  
 3           then w ← right[p[x]]  
 4                if color[w] = RED  
 5                   then color[w] ← BLACK                        ▹  Case 3
 6                        color[p[x]] ← RED                       ▹  Case 3
 7                        LEFT-ROTATE(T, p[x])                    ▹  Case 3  
 8                        w ← right[p[x]]                         ▹  Case 3
 9                if color[left[w]] = BLACK and color[right[w]] = BLACK  
10                   then color[w] ← RED                          ▹  Case 2  
11                        x ← p[x]                                ▹  Case 2  
12                   else if color[right[w]] = BLACK  and  color[left[w]]  = RED case 4
13                           then color[left[w]] ← BLACK          ▹  Case 4
14                                color[w] ← RED                  ▹  Case 4
15                                RIGHT-ROTATE(T, w)              ▹  Case 4  
16                                w ← right[p[x]]                 ▹  Case 4
17                         color[w] ← color[p[x]]                 ▹  Case 5 
18                         color[p[x]] ← BLACK                    ▹  Case 5 
19                         color[right[w]] ← BLACK                ▹  Case 5  
20                         LEFT-ROTATE(T, p[x])                   ▹  Case 5  
21                         x ← root[T]                            ▹  Case 5 
22        else (same as then clause with "right" and "left" exchanged)  
23 color[x] ← BLACK  

1、如果标记节点X是红+黑或者X是根节点。
调整: 直接将X设置成黑色,然后调整结束。

  • 因为X若是红色,则可以把标记附加色设置进来,则D子树的黑色节点数没变,保证了性质5,同时也避免了X和P同时为红的情况保证了性质4。
  • 因为X若是根节点,则不管X原色是啥,都必须直接置黑,同时因为已经递推到了根,必然已经保证了性质5,所以附加的黑节点可以直接丢弃。

***后面几种情况都是讨论X是叶子节点的情形(D无子节点) 即 X是黑色,且X不是根结点。因为D只有一个子节点或者没有子节点,所以由性质5知 black_num(P->D->X) = black)num(P->D->nil),所以X必为叶子节点 ***

2、如果标记节点X是黑+黑,且叔叔节点U是黑色,叔叔节点的孩子节点全是黑色。

调整: 将叔叔节点U置成红色,同时X上的附加黑色上移到P节点,然后以P节点作为当前节点,继续执行调整操作。

因为将U节点置成红色,则保证了P的左右子树黑色总数一致,然后以P为当前节点继续推进,但叔叔节点U置成红色的前提是2个孩子节点都是黑色。

调整前:(其中的X左边的黑色为附加色)
调整前
调整后:
调整后

3、如果标记节点X是黑+黑,且叔叔节点U是红色,叔叔节点U必有左右孩子且为黑色。
调整:将父节点P设成红色,叔叔节点U设为黑色,然后以P为节点左旋,之后U的左孩子作为P的右孩子替代U,且U为黑色。这样调整主要是让U节点满足黑色条件,然后继续进入调整流程。

调整前:
调整前

调整后:
调整后
4、如果标记节点X是黑+黑,且叔叔节点U是黑色,叔叔节点的左孩子是红色,右孩子是黑色。
调整: 将叔叔节点U置成红色,U的左孩子置成黑色,然后以U为节点右旋,最终U的左孩子变成U节点且为黑色,U的右孩子为红色。然后重新进入调整算法。

调整前:
调整前
调整后:
调整后

5、如果标记节点X是黑+黑,且叔叔节点U是黑色,右孩子是红色色,左孩子是任意色。

调整: 将U的右子节点设置成黑色,叔叔节点U的颜色设置成父节点P的颜色,父节点P的颜色置黑(X上的附加黑色设置到P),然后以P为节点左旋。这种调整实际上将U子树多余的红节点移入到X子树,保证了性质5,最终整个红黑树保持平衡,到达终态。

调整前:
调整前
调整后:
调整后

参考:
1、红黑树(red-black tree)算法
2、教你初步了解红黑树
3、左右旋图参考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章