学习算法导论——红黑树旋转插入和删除

参考:

《算法导论》

红黑树(一) 原理和算法详细介绍

浅谈算法和数据结构: 九 平衡查找树之红黑树

红黑树是一棵二叉搜索树,每个节点有一个标志位表示颜色,该颜色可以是红(RED)或黑(BLACK)。通过对任何一条从根到叶子的简单路径上各点的颜色进行约束,就能确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的。

红黑树每个节点有5个属性,color,key,leftChild,rightChild,p。其中color是颜色,key是关键字,leftChild是指向左孩子的指针,rightChild是指向右孩子的指针,p是指向父节点的指针。如果一个节点没有子节点或父节点,则该节点的leftChild,rightChild,p指针指向NIL。这些NIL视为二叉搜索树的外部节点,其他节点为内部节点。

一棵红黑树有如下性质:

(1)每个节点或是红色,或是黑色;

(2)根节点是黑色的;

(3)每个叶节点(NIL节点)是黑色的;

(4)如果一个节点是红色的,则它的两个子节点都是黑色的;

(5)对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。


如图(a)为一棵红黑树;

《学习算法导论——红黑树旋转插入和删除》

我们可以把NIL看成哨兵T.nil,它的color是黑色,其他属性任意。如图(b);

《学习算法导论——红黑树旋转插入和删除》

左旋

指针结构的修改是通过旋转来完成的,这时一种能保持二叉搜索树性质的操作。旋转有左旋和右旋两个,左旋操作为:

《学习算法导论——红黑树旋转插入和删除》

如图,对x作左旋操作。假设x的右孩子为y(不是NIL),x可以是任何右孩子不为NIL的内部节点,简单理解为:

以x为中心向左旋转,交换x,y;α,β,γ顺序不变。其中还要注意,每个节点还有指向父节点指针。

动图:

《学习算法导论——红黑树旋转插入和删除》

左旋的伪代码:

LEFT-ROTATE(T, x)  
01  y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作
02  right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
03  p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
04  p[y] ← p[x]             // 将 “x的父亲” 设为 “y的父亲”
05  if p[x] = nil[T]       
06  then root[T] ← y                 // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
07  else if x = left[p[x]]  
08            then left[p[x]] ← y    // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
09            else right[p[x]] ← y   // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
10  left[y] ← x             // 将 “x” 设为 “y的左孩子”
11  p[x] ← y                // 将 “x的父节点” 设为 “y”

c++代码:

void Left_Rotate(BiTreeNode* root,BiTreeNode* x)
{
	BiTreeNode* y = x->rightChild;
	x->rightChild = y->leftChild;
	y->leftChild->p = x;

	y->p = x->p;

	if (x->p == NULL)
		root = y;
	else if (x->p->leftChild == x)
		x->p->leftChild = y;
	else
		x->p->rightChild = y;

	y->leftChild= x;
	x->p = y;
}

《学习算法导论——红黑树旋转插入和删除》

如上图,对节点“11”左旋;要注意的是:如果x是根节点,旋转后y称为根节点;还有一点,需要知道x是其父节点的左孩子还是右孩子。

右旋

右旋和左旋基本相同:

《学习算法导论——红黑树旋转插入和删除》

《学习算法导论——红黑树旋转插入和删除》

伪代码:

RIGHT-ROTATE(T, y)  
01  x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作
02  left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
03  p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
04  p[x] ← p[y]             // 将 “y的父亲” 设为 “x的父亲”
05  if p[y] = nil[T]       
06  then root[T] ← x                 // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
07  else if y = right[p[y]]  
08            then right[p[y]] ← x   // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
09            else left[p[y]] ← x    // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
10  right[x] ← y            // 将 “y” 设为 “x的右孩子”
11  p[y] ← x                // 将 “y的父节点” 设为 “x”

c++代码:

void Right_Rotate(BiTreeNode* root, BiTreeNode* y)
{
	BiTreeNode* x = y->leftChild;

	y->leftChild = x->rightChild;
	x->rightChild->p = y;

	x->p = y->p;

	if (y->p == NULL)
		root = x;
	else if (y->p->rightChild == y)
		y->p->rightChild = x;
	else
		y->p->leftChild = x;

	x->rightChild = y;
	y->p = x;
}

插入

我们先当红黑树为普通的二叉搜索树一样插入节点z,并将其颜色置为RED,然后,为了保证红黑树性质,调用一个辅助函数来调整红黑树。 伪代码:

RB-INSERT(T, z)  
01  y ← nil[T]                        // 新建节点“y”,将y设为空节点。
02  x ← root[T]                       // 设“红黑树T”的根节点为“x”
03  while x ≠ nil[T]                  // 找出要插入的节点“z”在二叉树T中的位置“y”
04      do y ← x                      
05         if key[z] < key[x]  
06            then x ← left[x]  
07         else x ← right[x]  
08  p[z] ← y                          // 设置 “z的父亲” 为 “y”
09  if y = nil[T]                     
10     then root[T] ← z               // 情况1:若y是空节点,则将z设为根
11   else if key[z] < key[y]        
12      then left[y] ← z       // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
13   else right[y] ← z      // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” 
14  left[z] ← nil[T]                  // z的左孩子设为空
15  right[z] ← nil[T]                 // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
16  color[z] ← RED                    // 将z着色为“红色”
17  RB-INSERT-FIXUP(T, z)             // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树

主要是插入节点后红黑树性质的调整函数

伪代码:

RB - INSERT - FIXUP(T, z)
01 while color[p[z]] = RED                          // 若“当前节点(z)的父节点是红色”,则进行以下处理。
02     if p[z] = left[p[p[z]]]                      // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
03           y ← right[p[p[z]]]                    // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
04           if color[y] = RED                      // Case 1条件:叔叔是红色
05                 color[p[z]] ← BLACK ? Case 1   //  (01) 将“父节点”设为黑色。
06                 color[y] ← BLACK ? Case 1      // (02) 将“叔叔节点”设为黑色。
07                 color[p[p[z]]] ← RED ? Case 1   //  (03) 将“祖父节点”设为“红色”。
08                  z ← p[p[z]] ? Case 1   //  (04) 将“祖父节点”设为“当前节点”(红色节点)
09             else if z = right[p[z]]      // Case 2条件:叔叔是黑色,且当前节点是右孩子
10                  z ← p[z] ? Case 2   //  (01) 将“父节点”作为“新的当前节点”。
11                  LEFT - ROTATE(T, z) ? Case 2   //  (02) 以“新的当前节点”为支点进行左旋。
12               color[p[z]] ← BLACK ? Case 3   // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
13		 color[p[p[z]]] ← RED ? Case 3   //  (02) 将“祖父节点”设为“红色”。
14		 RIGHT - ROTATE(T, p[p[z]]) ? Case 3   //  (03) 以“祖父节点”为支点进行右旋。
15       else (same as then clause with "right" and "left" exchanged)      // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
16 color[root[T]] ← BLACK

《学习算法导论——红黑树旋转插入和删除》

void RB_Insert_FixUp(BiTreeNode* root, BiTreeNode* z)
{
	BiTreeNode* y;
	while (z->p && z->p->Color == RED)
	{
		if (z->p == z->p->p->leftChild)
		{
			y = z->p->p->rightChild;
			if (y->Color == RED)
			{
				z->p->Color = BLACK;
				y->Color = BLACK;
				z->p->p->Color = RED;
				z = z->p->p;
			}
			else 
			{
				if (z == z->p->rightChild)
				{
					z = z->p;
					Left_Rotate(root, z);
				}
				z->p->Color = BLACK;
				z->p->p->Color = RED;
				Right_Rotate(root, z->p->p);
			}
		}
		else
		{
			y = z->p->p->leftChild;
			if (y->Color == RED)
			{
				z->p->Color = BLACK;
				y->Color = BLACK;
				z->p->p->Color = RED;
				z = z->p->p;
			}
			else 
			{
				if (z == z->p->leftChild)
				{
					z = z->p;
					Right_Rotate(root, z);
				}
				z->p->Color = BLACK;
				z->p->p->Color = RED;
				Left_Rotate(root, z->p->p);
			}
		}
	}
}

删除

将红黑树T内的节点z删除。需要执行的操作依次是:首先,将T当作一颗二叉树,将节点删除;然后,通过RB – DELETE – FIXUP来对节点重新着色并旋转,以此来保证删除节点后的树仍然是一颗红黑树。

(01) 将T当作一颗二叉树,将节点删除。

这和”删除常规二叉搜索树中删除节点的方法是一样的”。分3种情况:
第一种,被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
第二种,被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
第三种,被删除节点有两个儿子。那么,首先把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。

这里有两点需要说明:第一步中复制时,仅仅复制内容,即将“它的后继节点的内容”复制给“该节点的内容”。    这相当于用“该节点的后继节点”取代“该节点”,之后就删除“该节点的后继节点”即可,而不需要删除“该节点”(因为“该节点”已经被“它的后继节点”所取代)。
第二步中删除“该节点的后继节点”时,需要注意:“该节点的后继节点”不可能是双子非空,这个根据二叉树的特性可知。 既然“该节点的后继节点”不可能双子都非空,就意味着“该节点的后继节点”要么没有儿子,要么只有一个儿子。若没有儿子,则按“第一种”种的办法进行处理;若只有一个儿子,则按“第二种”中的办法进行处理。

(02) 通过RB – DELETE – FIXUP来对节点重新着色并旋转,以此来保证删除节点后的树仍然是一颗红黑树。

因为(01)中删除节点之后,可能会违背红黑树的特性。所以需要,通过RB – DELETE – FIXUP来重新校正,为当前树保持红黑树的特性。

下面是《算法导论》中 “从红黑树T中删除节点z”的伪代码

RB - DELETE(T, z)
01 if left[z] = nil[T] or right[z] = nil[T]
02    then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
03    else y ← TREE - SUCCESSOR(z)                  // 否则,将“z的后继节点”赋值给 “y”。
04 if left[y] ≠ nil[T]
05    then x ← left[y]                            // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
06    else x ← right[y]                           // 否则,“y的右孩子” 赋值给 “x”。
07 p[x] ← p[y]                                    // 将“y的父节点” 设置为 “x的父节点”
08 if p[y] = nil[T]
09    then root[T] ← x                            // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
10    else if y = left[p[y]]
11            then left[p[y]] ← x                 // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
12            else right[p[y]] ← x                // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
13 if y ≠ z
14    then key[z] ← key[y]                        // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
15         copy y's satellite data into z         
16 if color[y] = BLACK
17    then RB - DELETE - FIXUP(T, x)                  // 若“y为黑节点”,则调用
18 return y

RB - DELETE - FIXUP(T, x)
01 while x ≠ root[T] and color[x] = BLACK
02     do if x = left[p[x]]
03           then w ← right[p[x]]                                             // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                          
04                if color[w] = RED                                           // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
05                   then color[w] ← BLACK ? Case 1   //   (01) 将x的兄弟节点设为“黑色”。
06                        color[p[x]] ← RED ? Case 1   //   (02) 将x的父节点设为“红色”。
07                        LEFT - ROTATE(T, p[x]) ? Case 1   //   (03) 对x的父节点进行左旋。
08                        w ← right[p[x]] ? Case 1   //   (04) 左旋后,重新设置x的兄弟节点。
09                if color[left[w]] = BLACK and color[right[w]] = BLACK       // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
10                   then color[w] ← RED ? Case 2   //   (01) 将x的兄弟节点设为“红色”。
11                        x ←  p[x] ? Case 2   //   (02) 设置“x的父节点”为“新的x节点”。
12                   else if color[right[w]] = BLACK                          // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
13                           then color[left[w]] ← BLACK ? Case 3   //   (01) 将x兄弟节点的左孩子设为“黑色”。
14                                color[w] ← RED ? Case 3   //   (02) 将x兄弟节点设为“红色”。
15                                RIGHT - ROTATE(T, w) ? Case 3   //   (03) 对x的兄弟节点进行右旋。
16                                w ← right[p[x]] ? Case 3   //   (04) 右旋后,重新设置x的兄弟节点。
17                         color[w] ← color[p[x]] ? Case 4   // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
18                         color[p[x]] ← BLACK ? Case 4   //   (02) 将x父节点设为“黑色”。
19                         color[right[w]] ← BLACK ? Case 4   //   (03) 将x兄弟节点的右子节设为“黑色”。
20                         LEFT - ROTATE(T, p[x]) ? Case 4   //   (04) 对x的父节点进行左旋。
21                         x ← root[T] ? Case 4   //   (05) 设置“x”为“根节点”。
22        else (same as then clause with "right" and "left" exchanged)        // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
23 color[x] ← BLACK

详细内容看
这里

    原文作者:算法小白
    原文地址: https://blog.csdn.net/sinat_30071459/article/details/50858310
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞