STL源码——关联式容器及其底层红黑树实现(下) 之 红黑树的剖析(插入,删除操作)

本文感谢并参考http://blog.csdn.net/hackbuteer1/article/details/7760584
可滑动右边系统自带目录选择性阅读

红黑树概述

红黑树和AVL树的区别在于它使用颜色来标记节点的高度,红黑树追求的是局部平衡而不是AVL树中非常严格的平衡。红黑树是AVL树的变种,它通过一些着色法则确保没有一条路径会比其他路径长出两倍,因而达到接近平衡的目的。
红黑树必须满足以下规则:
1、每个节点不是红色就是黑色。
2、根节点为黑色。
3、如果节点为红色,其子节点必须为黑色。
4、任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同。
这些约束保证了这个树大致上是平衡的,这也决定了红黑树的插入、删除、查询等操作是比较快速的。
另外,新增节点必须是红色的,因为插入黑色节点的话就会破坏第四条路径上黑色节点数必须相等这条约束。当新增节点根据AVL树的规则到达其插入点时却未能符合上述条件时,就必须调整颜色、旋转树
下面对数的调整会涉及到节点的父亲节点“父”,父亲节点的兄弟节点“叔”, 父亲节点的父亲节点“祖”

红黑树四种节点插入情况

情况1:黑父

如果新节点的父节点为黑色节点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作就完成了。这种情况比较常见,也是红黑树比ACL树优秀的地方
在这里插入图片描述

情况2:红父

如果新节点的父节点为红色,可以判断祖父节点一定为黑色,这时需要根据叔父节点的颜色来决定做什么样的操作
在这里插入图片描述
绿色表示不确定什么颜色,蓝色箭头指向每次进行不平衡判断的起始点

2.1 红叔

当叔父节点为红色时
在这里插入图片描述
将父节点和叔节点变为黑色,将祖父节点变为红色,再将祖父节点作为判定点继续向上(迭代)进行平衡操作

注意,只要叔节点时红色,不管左右节点,都不需要旋转树形只需要调整颜色就可以了

2.2 黑叔

当叔父节点为黑色时,需要进行旋转,下面时所有情况:
在这里插入图片描述详细一下,红黑树规定插入的节点必须时红色的,而且二叉查找树中新插入的节点都是放在叶子节点上的,所以关于插入操作的平衡调整,有这样两种特殊情况:

  • 如果插入节点的父节点是黑色,那么什么都不用做,仍然满足红黑树的定义
  • 如果插入的节点的根节点,那么直接改变它的颜色,把它变成黑色就可以了

除此之外,其他的情况都会违背红黑树的定义,需要进行调整,调整过程包含两种基础操作:左右旋转 和 改变颜色

红黑树的平衡调整过程是一个迭代过程,把正在处理的节点叫做关注节点, 关注节点会随着不停地迭代而不断发生变化,最开始的关注节点就是新插入的节点, 下面复述一下上图的不同情况:

①红叔:将红叔和红父改成黑色,将祖父颜色改成红色,将关注节点变成红色的祖父节点,再迭代判断

②黑叔:

  • 叔叔是黑色,且关注节点是父节点的右子节点: 将关注节点变成父节点,围绕关注节点(父节点)左旋, 再根据情况……
  • 叔叔是黑色,且关注节点是父节点的左子节点:围绕关注节点的祖父节点右旋,将关注节点的父节点和关注节点的兄弟节点交换颜色

红黑树的插入源码

RB-tree的节点设计:

父节点,左右子节点,节点颜色,还有节点值

typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;//红色为0
const __rb_tree_color_type __rb_tree_black = true;//黑色为1

struct __rb_tree_node_base
{
	typedef __rb_tree_color_type color_type;
	typedef __rb_tree_node_base* base_ptr;

	color_type color;//节点颜色
	base_ptr parent;//父节点
	base_ptr left;
	base_Ptr right;

	struct base_ptr minimum(base_ptr x)
	{
		while(x->left != 0) x = x->left;
		return x;
	}
	static base_ptr maximum(base_ptr x)
	{
		while(x->right != 0) x = x->right;
		return x;
	}
};

template <class Value>
struct __rb_tree_node : public __rb_tree_node_base//继承,加节点值
{
	typedef __rb_tree_node<Value>* link_type;
	Value value_field;//节点值
};

红黑树中的节点插入操作 insert_unique

代码是insert_unique方式,不允许出现重复键值,注意回忆map插入时情况,其返回值是个pair,第一个元素是红黑树迭代器,指向新增节点,第二个元素是bool类型,表示插入成功与否

新插入节点x一定是插入在叶子节点上的,第一步先去找插入位置,判断是否可以插入(不能重复),利用定义的comp比较键值的大小不断选择往左或者往右直到到达叶子节点,代码中comp真表示大于的情况,往左,否则小于等于的话就往右;当大于的情况则用_insert()函数进行插入,否则是小于等于的话就需要进一步判断是否与既有节点有重复键值
注意我们在测试时调整了–j, 这是因为之所以走到了当前节点,就是在判断其父节点的时候判断时小于等于才走向了这边,所以要求判断–j的key值和当前节点的key值,
最后返回pair
至于往前退--的操作符重载在红黑树的双向迭代器中已经实现了,有状态的话也在末尾添一下,但是侯老师的书上已经很清楚了,忘记了可以再去翻阅P215

template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
pair<typename rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::iterator , bool>
rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::insert_unique(const Value &v)
{
	rb_tree_node* y = header;    // 根节点root的父节点
	rb_tree_node* x = root();    // 从根节点开始
	bool comp = true;
	while(x != 0)
	{
		y = x;
		comp = key_compare(KeyOfValue()(v) , key(x));    // v键值小于目前节点之键值?
		x = comp ? left(x) : right(x);   // 遇“大”则往左,遇“小于或等于”则往右
	}
	// 离开while循环之后,y所指即插入点之父节点(此时的它必为叶节点)
	iterator j = iterator(y);     // 令迭代器j指向插入点之父节点y
	if(comp)     // 如果离开while循环时comp为真(表示遇“大”,将插入于左侧)
	{
		if(j == begin())    // 如果插入点之父节点为最左节点
			return pair<iterator , bool>(_insert(x , y , z) , true);
		else     // 否则(插入点之父节点不为最左节点)
			--j;   // 调整j,回头准备测试
	}
	if(key_compare(key(j.node) , KeyOfValue()(v) ))
		// 新键值不与既有节点之键值重复,于是以下执行安插操作
		return pair<iterator , bool>(_insert(x , y , z) , true);
	// 以上,x为新值插入点,y为插入点之父节点,v为新值
 
	// 进行至此,表示新值一定与树中键值重复,那么就不应该插入新值
	return pair<iterator , bool>(j , false);
}

真正地插入_insert()

根据comp确实是插入左边还是右边,但不管是哪种情况,都是先create_node创建一个新节点,再在插入好之后设定新节点的父节点,其左右节点为空,并在全局方面增加节点数量,设置其颜色__rb_tree_rebalance(), 最后返回一个迭代器指向新增节点

template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
typename<Key , Value , KeyOfValue , Compare , Alloc>::_insert(base_ptr x_ , base_ptr y_ , const Value &v)
{
	// 参数x_ 为新值插入点,参数y_为插入点之父节点,参数v为新值
	link_type x = (link_type) x_;
	link_type y = (link_type) y_;
	link_type z;
 
	// key_compare 是键值大小比较准则。是个function object
	if(y == header || x != 0 || key_compare(KeyOfValue()(v) , key(y) ))
	{
		z = create_node(v);    // 产生一个新节点
		left(y) = z;           // 这使得当y即为header时,leftmost() = z
		if(y == header)
		{
			root() = z;
			rightmost() = z;
		}
		else if(y == leftmost())     // 如果y为最左节点
			leftmost() = z;          // 维护leftmost(),使它永远指向最左节点
	}
	else
	{
		z = create_node(v);        // 产生一个新节点
		right(y) = z;              // 令新节点成为插入点之父节点y的右子节点
		if(y == rightmost())
			rightmost() = z;       // 维护rightmost(),使它永远指向最右节点
	}
	parent(z) = y;      // 设定新节点的父节点
	left(z) = 0;        // 设定新节点的左子节点
	right(z) = 0;       // 设定新节点的右子节点
	// 新节点的颜色将在_rb_tree_rebalance()设定(并调整)
	_rb_tree_rebalance(z , header->parent);      // 参数一为新增节点,参数二为根节点root
	++node_count;       // 节点数累加
	return iterator(z);  // 返回一个迭代器,指向新增节点
}

插入节点之后令树形平衡(改变颜色、旋转树形)

改变颜色

第一个参数是新插入的节点x, x的颜色必为红色
如上分不同情况,每个情况换颜色或者旋转后换色即可

// 参数一为新增节点,参数二为根节点root
inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
	x->color = _rb_tree_red;    //新节点必为红
	while(x != root && x->parent->color == _rb_tree_red)    // 父节点为红
	{
		if(x->parent == x->parent->parent->left)      // 父节点为祖父节点之左子节点
		{
			_rb_tree_node_base* y = x->parent->parent->right;    // 令y为伯父节点
			if(y && y->color == _rb_tree_red)    // 伯父节点存在,且为红
			{
				x->parent->color = _rb_tree_black;           // 更改父节点为黑色
				y->color = _rb_tree_black;                   // 更改伯父节点为黑色
				x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色
				x = x->parent->parent;
			}
			else    // 无伯父节点,或伯父节点为黑色
			{
				if(x == x->parent->right)   // 如果新节点为父节点之右子节点
				{
					x = x->parent;
					_rb_tree_rotate_left(x , root);    // 第一个参数为左旋点
				}
				x->parent->color = _rb_tree_black;     // 改变颜色
				x->parent->parent->color = _rb_tree_red;
				_rb_tree_rotate_right(x->parent->parent , root);    // 第一个参数为右旋点
			}
		}
		else          // 父节点为祖父节点之右子节点
		{
			_rb_tree_node_base* y = x->parent->parent->left;    // 令y为伯父节点
			if(y && y->color == _rb_tree_red)    // 有伯父节点,且为红
			{
				x->parent->color = _rb_tree_black;           // 更改父节点为黑色
				y->color = _rb_tree_black;                   // 更改伯父节点为黑色
				x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色
				x = x->parent->parent;          // 准备继续往上层检查
			}
			else    // 无伯父节点,或伯父节点为黑色
			{
				if(x == x->parent->left)        // 如果新节点为父节点之左子节点
				{
					x = x->parent;
					_rb_tree_rotate_right(x , root);    // 第一个参数为右旋点
				}
				x->parent->color = _rb_tree_black;     // 改变颜色
				x->parent->parent->color = _rb_tree_red;
				_rb_tree_rotate_left(x->parent->parent , root);    // 第一个参数为左旋点
			}
		}
	}//while
	root->color = _rb_tree_black;    // 根节点永远为黑色
}

旋转树形:左旋、右旋

左旋函数

在这里插入图片描述

inline void _rb_tree_rotate_left(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
	// x 为旋转点
	_rb_tree_node_base* y = x->right;          // 令y为旋转点的右子节点
	x->right = y->left;
	if(y->left != 0)
		y->left->parent = x;           // 跟链表一样,是双向的,需要反回设父节点
	y->parent = x->parent;
 
	// 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)
	if(x == root)    // x为根节点
		root = y;
	else if(x == x->parent->left)         // x为其父节点的左子节点
		x->parent->left = y;
	else                                  // x为其父节点的右子节点
		x->parent->right = y;
	y->left = x;
	x->parent = y;
}

右旋函数

在这里插入图片描述

inline void _rb_tree_rotate_right(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
	// x 为旋转点
	_rb_tree_node_base* y = x->left;          // 令y为旋转点的左子节点
	x->left = y->right;
	if(y->right != 0)
		y->right->parent = x;           // 别忘了回马枪设定父节点
	y->parent = x->parent;
 
	// 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)
	if(x == root)
		root = y;
	else if(x == x->parent->right)         // x为其父节点的右子节点
		x->parent->right = y;
	else                                  // x为其父节点的左子节点
		x->parent->left = y;
	y->right = x;
	x->parent = y;
}

删除

研究红黑树的删除,我们依旧是根据关注节点与周围节点的排布特点,按照一定规则去调整就好了
删除操作的平衡步骤分为两步,第一步是针对删除节点初步调整: 初步调整只是保证整棵红黑树在一个节点删除之后仍然保证黑色节点数目相同这个要求;第二部针对关注节点进行二次调整, 让它满足红黑树不存在相邻两个红色节点

针对删除节点初步调整

情况1,如果要删除的节点a只有一个子节点b:

  • 删除节点a,将节点b替换到节点a的位置,跟普通的二叉查找树的删除操作一样,把节点b改成黑色; 调整结束,不需要二次调整

情况2, 如果要删除的节点a有两个非空子节点,并且它的后继节点就是a的右子节点c:

  • 如果节点a的后继节点就是右子节点c,那右子节点c肯定没有左子树,我们把节点a删除,并且将节点c替换成节点a的位置
  • 把节点c的颜色设置成节点a相同的颜色
  • 如果节点c是黑色,为了不违反红黑树的最后一条定义,给节点c的右子节点多加一个黑色,这时候节点d就成了“红黑”或者“黑黑” (红黑,黑黑, 意思是这个节点暂时被标记成两种颜色)
  • 这个时候,关注节点变成了节点d,第二部的调整操作就会针对关注节点来做

情况3, 如果要删除的节点a右两个非空子节点,且a的后继节点不是右子节点

  • 找到后继节点d,删除后继节点d(情况1)
  • 把节点a替换成d,并把d的颜色变成a一样颜色
  • 同样的,如果节点d是黑色,为了不违反黑色节点数相同这条约束,我们给节点d的右子节点多加一个黑色,这个时候节点c就成了“红黑”或者“黑黑”
  • 下一步,关注节点就是c,d的右子节点

针对关注节点的二次调整

经过初步调整之后,关注节点变成了“红黑”或者“黑黑”节点,针对这个关注节点,再分四种情况进行二次调整,二次调整是为了不让红黑树中存在相邻的红色节点
四种情况分别是:
①关节节点a的兄弟节点c是红色
②关注节点a的兄弟节点c是黑色,且c的左右子节点都是黑色的
③关注节点a的兄弟节点c是黑色,c的左子节点是红色,c的右子节点是黑色
④关注节点a的兄弟节点c是黑色,c的右子节点是红色
具体的平衡调整策略,暂且是记不住了……不如就先按着策略看看代码吧233👇
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

红黑树节点代码

#include<iostream>
using namespace std;
 
// 定义节点颜色  
enum COLOR
{  
    BLACK = 0,  
    RED  
};  
  
// 红黑树节点  
typedef struct RB_Tree_Node
{  
    int key;  
    struct RB_Tree_Node *left;  
    struct RB_Tree_Node *right;  
    struct RB_Tree_Node *parent;  
    unsigned char RB_COLOR;  
}RB_Node;
 
// 红黑树,包含一个指向根节点的指针  
typedef struct RBTree
{  
    RB_Node* root;
}*RB_Tree;
 
// 红黑树的NIL节点  
static RB_Tree_Node NIL = {0, 0, 0, 0, BLACK}; 
 
#define PNIL (&NIL)   // NIL节点地址 
 
void Init_RBTree(RB_Tree pTree) // 初始化一棵红黑树  
{  
    pTree->root = PNIL;  
}   
 
// 查找最小键值节点  
RB_Node* RBTREE_MIN(RB_Node* pRoot)  
{  
    while (PNIL != pRoot->left)
    {
        pRoot = pRoot->left;
    }  
    return pRoot;
}

// 查找指定节点的后继节点  
RB_Node* RBTREE_SUCCESSOR(RB_Node*  pRoot)  
{  
    if (PNIL != pRoot->right)    // 查找图中6的后继节点时就调用RBTREE_MIN函数
    {  
        return RBTREE_MIN(pRoot->right);  
    }
    // 节点没有右子树的时候,进入下面的while循环(如查找图中13的后继节点时,它的后继节点是15)
    RB_Node* pParent = pRoot->parent;  
    while((PNIL != pParent) && (pRoot == pParent->right))
    {  
        pRoot = pParent;
        pParent = pRoot->parent;      
    }
    return pParent;
}

红黑树删除节点代码

先删除,如果删除的节点是红色则直接删除就可以,否则是黑色的话就需要调平衡,按照上述的情况

算法导论上的描述如下:(也就是代码,也就是之后的4种情况分析)
兄弟是红色/兄弟是黑色(子节点黑色/右子节点黑色/右子节点红色),另外对称的4种情况即X是右子节点的时候,只需要将所有的左右旋交换就ok

RB-DELETE-FIXUP(T, 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 //情况1,x的兄弟w是红色的,改颜色,父节点作为关注点左旋
5                   then color[w] ← BLACK                          兄弟改颜色
6                        color[p[x]] ← RED                          父亲改颜色
7                        LEFT-ROTATE(T, p[x])                       以父亲左旋
8                        w ← right[p[x]]                            维护w还是关注节点的右节点
9                if color[left[w]] = BLACK and color[right[w]] = BLACK //情况2,兄弟的两个孩子都黑
10                   then color[w] ← RED                            //兄弟改成红色
11                        x p[x]                                    关注点变成父节点,往上走
12                   else if color[right[w]] = BLACK //情况3:x的兄弟w是黑色,w的右孩子是黑色(w的左孩子是红色
                    {
13                           then color[left[w]] ← BLACK            Case 3 
14                                color[w] ← RED                    Case 3 
15                                RIGHT-ROTATE(T, w)                Case 3 
16                                w ← right[p[x]]                   Case 3 
17                         color[w] ← color[p[x]]                   Case 4 情况4:x的兄弟w是黑色的,且w右孩子是红色
18                         color[p[x]] ← BLACK                      Case 4 
19                         color[right[w]] ← BLACK                  Case 4 
20                         LEFT-ROTATE(T, p[x])                     Case 4 
21                         x ← root[T]                              Case 4 
22        else (same as then clause with "right" and "left" exchanged) 
23 color[x] ← BLACK  

DeleteFixUp调整平衡函数就是按照上面的方法翻译成代码……,而硬记住这个策略略微空洞且为难,那么重点就应该是知道“为什么要这样调整”

// 红黑树的节点删除
RB_Node* Delete(RB_Tree pTree , RB_Node* pDel)  
{  
    RB_Node* rel_delete_point;
    if(pDel->left == PNIL || pDel->right == PNIL)
        rel_delete_point = pDel;
    else
        rel_delete_point = RBTREE_SUCCESSOR(pDel);     // 查找后继节点
 
    RB_Node* delete_point_child;  
    if(rel_delete_point->right != PNIL)  
    {  
        delete_point_child = rel_delete_point->right;  
    }  
    else if(rel_delete_point->left != PNIL)  
    {  
        delete_point_child = rel_delete_point->left;  
    }  
    else  
    {  
        delete_point_child = PNIL;  
    }  
    delete_point_child->parent = rel_delete_point->parent; //这就把节点删除了
    if(rel_delete_point->parent == PNIL)    // 删除的节点是根节点
    {  
        pTree->root = delete_point_child;
    }  
    else if(rel_delete_point == rel_delete_point->parent->right)
    {  
        rel_delete_point->parent->right = delete_point_child;  
    }  
    else  
    {  
        rel_delete_point->parent->left = delete_point_child;  
    }
    if(pDel != rel_delete_point)
    {
        pDel->key = rel_delete_point->key;
    }
    if(rel_delete_point->RB_COLOR == BLACK)  
    {  
        DeleteFixUp(pTree , delete_point_child);  //如果删除的是黑色则需要调
    }
    return rel_delete_point;  
}  
             
 
void DeleteFixUp(RB_Tree pTree , RB_Node* node)  
{  
    while(node != pTree->root && node->RB_COLOR == BLACK)  //直到到根节点或者遇到红色节点才算完成
    {  
        if(node == node->parent->left)  
        {  
            RB_Node* brother = node->parent->right;  
            if(brother->RB_COLOR==RED)   //情况1:x的兄弟w是红色的。  
            {  
                brother->RB_COLOR = BLACK;  
                node->parent->RB_COLOR = RED;  
                RotateLeft(node->parent);  
            }  
            else     //情况2:x的兄弟w是黑色的,  
            {  
                if(brother->left->RB_COLOR == BLACK && brother->right->RB_COLOR == BLACK)  //w的两个孩子都是黑色的  
                {  
                    brother->RB_COLOR = RED;  
                    node = node->parent;  
                }  
                else
                {
                    if(brother->right->RB_COLOR == BLACK)   //情况3:x的兄弟w是黑色的,w的右孩子是黑色(w的左孩子是红色)。  
                    {
                        brother->RB_COLOR = RED;
                        brother->left->RB_COLOR = BLACK;
                        RotateRight(brother);
                        brother = node->parent->right;      //情况3转换为情况4
                    }
                    //情况4:x的兄弟w是黑色的,且w的右孩子是红色的
                    brother->RB_COLOR = node->parent->RB_COLOR;  
                    node->parent->RB_COLOR = BLACK;  
                    brother->right->RB_COLOR = BLACK;  
                    RotateLeft(node->parent);  
                    node = pTree->root;
                }//else
            }//else
        }  
        else   //同上,原理一致,只是遇到左旋改为右旋,遇到右旋改为左旋即可。其它代码不变。  
        {  
            RB_Node* brother = node->parent->left;  
            if(brother->RB_COLOR == RED)  
            {  
                brother->RB_COLOR = BLACK;  
                node->parent->RB_COLOR = RED;  
                RotateRight(node->parent);  
            }  
            else  
            {  
                if(brother->left->RB_COLOR==BLACK && brother->right->RB_COLOR == BLACK)  
                {  
                    brother->RB_COLOR = RED;  
                    node = node->parent;  
                }  
                else
                {
                    if(brother->left->RB_COLOR==BLACK)  
                    {  
                        brother->RB_COLOR = RED;  
                        brother->right->RB_COLOR = BLACK;  
                        RotateLeft(brother);
                        brother = node->parent->left;      //情况3转换为情况4
                    }
                    brother->RB_COLOR = node->parent->RB_COLOR;  
                    node->parent->RB_COLOR = BLACK;  
                    brother->left->RB_COLOR = BLACK;  
                    RotateRight(node->parent);  
                    node = pTree->root;  
                }  
            }  
        }  
    }//while 
    node->RB_COLOR = BLACK;    //如果X节点原来为红色,那么直接改为黑色  
}

下面开始尝试理解https://blog.csdn.net/Hackbuteer1/article/details/7760584论文的内容,企图弄明白策略产生原因💪再去会看代码会更好理解

从红黑树上删除一个节点,可以先用普通二叉搜索树的方法,将节点从红黑树上删除掉,然后再将破坏掉的红黑性质进行恢复

其中,二叉搜索树的节点删除方法:Z指向需要删除的节点,Y指向实质结构上被删除的节点(看图理解,删除15,但是删除的实际的17位置,17到15那里去),如果Z节点只有一个子节点或者没有子节点,那么Y指向的就是Z指向的;如果Z节点右两个子节点,那么Y指向Z节点的后继节点(15的后继节点就是17),也就是上述代码中的rel_delete_point
在这里插入图片描述

接下来看红黑树的红黑性质恢复过程:

  • 如果Y指向的节点是红色节点,则直接删除Y,红黑性质不会被破坏,操作结束。

  • 如果Y指向的节点是黑色节点,那么几条红黑性质就有可能会遭到破坏,①首先包含y路径的黑高度-1; 其次如果Y有红色子节点,且有红色父节点,那么就②会出现两个相邻的红节点;最后,如果Y指向的是根节点,而Y的子节点又是红色的,那么Y被删除后,③根节点就变成红色的了
    其中,黑高度被破坏时最难解决的,这影响到了全局,且在这个条件下,进行其它红黑性质的恢复也很困难。所以首先解决①这个问题如果不改变包含Y路径的黑高度,那么树的其他部分的黑高度就必须做出相应变化来适应它。所以要想办法恢复原来含Y节点的路径的黑高度: 做法就是:无条件地把Y节点的黑色推到它的子节点X上去, X又可能是NIL节点, 这样,X就有可能具有双重给黑色或者同时具有红黑两色!这样只会破坏“节点只能红色或者黑色”这个约束,但是这个约束是比较好恢复的:
    如果X是同时具有红黑两色,那么直接把X涂成黑色,就ok, 因为将X变成黑色,2、4约束也可得到恢复, 算法结束;
    如果X是双黑色,那么我们希望把这种情况向上推一直推到根节点(调整树结构和颜色,X指向新的双黑色节点,X不断向上移动), 让根节点具双黑色,这时 直接把X的一层黑色去掉就行了,因为根节点被包含在所有的路径上,所以这样做就可以使得所有的路径同时高度减一,不会破坏黑高度一致的约束 √

    再来解决①②这两个问题
    我们知道,如果X节点是双黑色,且X有父节点P,则X必然有兄弟节点W,且这个W必定有两个子节点(这是由原树满足红黑条件而自然具备的,X是双黑色,那么P的另一个子节点以下一定要有至少两层的节点,否则黑高度不可能和X路径一致)。
    所以我们就分析这些节点之间都是如何变形的, 把问题限制在比较小的范围内解决。
    另一个前提是X在一开始,肯定是树底的叶节点或者是NIL节点,所以在递归向上的过程中,每一步都保证下一步进行时,至少X的子树是满足红黑特性的,子树的情况就可以认为是已经正确的了。这样,分析就关注在X节点,X的父节点P和X的兄弟节点W以及W的两个子节点中

下面仅仅考虑子节点X原本是黑色的情况,在这种情况下,X此时应该具双重黑色,算法的过程就是将这多出的一重黑色向上移动,直到遇到红节点或者根节点
会遇到8中情况,其中X是左右节点的情况是对称的,下面以X是左节点来分析对应的4中情况,实际上接下来的调整过程就是要将经过X的所有路径上的黑色节点个数增加1,
具体分为以下4种情况:

  1. X的兄弟W是红色(想办法将其变成黑色)
    在这里插入图片描述
  2. X的兄弟W是黑色,且W的两个子节点都是黑色
    在这里插入图片描述
  3. X的兄弟节点W是黑色,且W的左子节点是红色,右子节点是黑色
    在这里插入图片描述
  4. X的兄弟节点是黑色,且W的右子节点是红色
    在这里插入图片描述

只要按照上面的四种情况一致递归下去,X最终总会指向根节点或者一个红色节点,这样我们就可以结束递归并解决

红黑性质:
1,每个节点或是红色,或是黑色
2,根节点是黑色的
3,每个叶节点NIL是黑色的
4,红色节点不能相邻
5,对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑色节点

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值