红黑树删除·图文详解

删除须知

红黑树的性质

  • 性质1:节点都是红色活着黑色
  • 性质2:根节点是黑色
  • 性质3:所有叶子节点都是黑色(叶子是Nil节点)
  • 性质4:每个红色节点的还自己节点只能是黑色
  • 性质5:对每个节点,从该节点到其所有后代叶结点的简单路径上,均包含相同数目的黑色节点

红黑树性质详解

标题

由于最近在实现红黑树删除时遇到诸多麻烦,查看了许多篇文章,但终未为我问题的解决提供帮助,因此作此篇文章供以帮助,若有不妥之处 望各位大佬不吝指正。

红黑树节点结构

template <class Type>
class RBNode
{
	friend class RBTree<Type>;

public:
	RBNode(Type d=Type(),RBNode<Type> *left=nullptr,RBNode<Type> *right=nullptr)
		:data(d),leftChild(left),rightChild(right),parent(nullptr),color(RED)
		{}

private:
	Type data;
	RBNode<Type>* leftChild;
	RBNode<Type>* rightChild;
	RBNode<Type>* parent;
	Color_t color;
};

节点名称规定

被删节点为x
x的父亲节点为father
x的兄弟节点为bother
x的侄子节点为son
临时指针temp

单旋转

在这里插入图片描述

删除函数

RBNode<Type>* Erase(RBNode<Type>* x)
delete的是函数Erase的返回值

红黑树删除

删除情况总列

  • 情况一:x为红,且无子女节点
  • 情况二:x为红,且仅有一个子女节点(不可能发生)
  • 情况三:x有两个子女节点(x为红–两个子女节点 AND x为黑–两个子女节点)
  • 情况四:x为黑,且仅有一个子女节点
  • 情况五:x为黑,且无子女节点

情况一:x为红,且无子女节点

在这里插入图片描述

----------father的左孩子置空---------------------------------------- father的右孩子置空---------
在这里插入图片描述
返回x进行delete

if (x->parent->leftChild == x)
			x->parent->leftChild = Nil;
		else
			x->parent->rightChild = Nil;
		x->color = BLACK;//为情况五服务不影响情况一的删除

情况二:x为红,且仅有一个子女节点

在这里插入图片描述

x为红,且有一个孩子节点,则孩子节点必为黑,即如图:x的左子树路径上有一个黑色,而x的右子树上有两个黑色,不满足性质五,因此这种情况不可能存在,不讨论

情况三:x有两个子女节点

对有两个孩子节点的节点删除,实则是把x->data的值替换为与x->data相邻大小的值。即替换为左子树最大值或者右子树最小值,再对替换的节点进行删除
在这里插入图片描述
其中A为x的左子树的最大值,B为x的右子树的最小值

将x->data替换为A->data或者B->data,再将A节点或者B节点删除,如此便已转化为删除黑色叶子节点
在这里插入图片描述
此时转为情况五将41节点进行删除,最后将41的值赋值给原来的x->data。注意返回时需返回A/B(41)节点的地址。

		RBNode<Type>* temp;
		temp=Max_t(x->leftChild);
		Erase(temp);
		x->data = temp->data;
		return temp;

情况四:x为黑,且有一个子女节点(仅有的子女节点必为红色)

此时x的子女节点必为红,原因如下
在这里插入图片描述
如图若x为黑且只有一个孩子节点为黑,此时便不满足红黑树的性质五

因此x的孩子节点的颜色必为红色
在这里插入图片描述
将x的父节点与x的孩子节点相连,返回x节点进行delete

		if (x->leftChild->color == RED)
		{//孩子节点在左侧,右侧为Nil
			if (x->parent->leftChild == x)
				x->parent->leftChild = x->leftChild;
			else
				x->parent->rightChild = x->leftChild;

			x->leftChild->parent = x->parent;
			x->leftChild->color = BLACK;
		}
		else
		{//孩子节点在右侧,左侧为Nil
			if (x->parent->leftChild == x)
				x->parent->leftChild = x->rightChild;
			else
				x->parent->rightChild = x->rightChild;

			x->rightChild->parent = x->parent;
			x->rightChild->color = BLACK;
		}

情况五:x为黑,且无子女节点

这个情况较为复杂,分为五种情形 如下:
情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧
情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧
情形三:被删节点的兄弟节点为黑色,且其兄弟节点无孩子节点
情形四:被删节点的兄弟节点为红色,即它必有黑色的孩子节点
情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil

情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧

情形特点【x为黑色,无孩子节点,bother为黑色,至少有一个红色节点,且该红色节点为外侧】
在这里插入图片描述
通过旋转使得father替换x的位置,bother替换father的位置,son替换bother的位置,如此就相当于将即将删除的x节点的黑色转移到son中,使得该树在删除x节点后满足性质五。

在这里插入图片描述

旋转过后对各个节点的颜色进行赋值,需要注意的是father的颜色可红可黑,要将father的颜色赋给bother,最后对其他节点颜色进行赋值,再返回x节点进行delete

			if (bother->color == BLACK && bother->rightChild->color == RED)
			//情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧
			{
				//单旋转
				Color_t c = father->color;
				son = bother->rightChild;
				Single_rotation(son, bother, father, x);
				bother->color = c;
				father->color = BLACK;
				son->color = BLACK;
				father->leftChild = Nil;
			}
情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧

情形特点【x为黑色,无孩子节点,bother为黑色,至少有一个红色节点,且该红色节点为内侧】
在这里插入图片描述
此情形与情形一相似,只需将son位置和bother的位置进行调整便变为情形一,按照情形一对其操作,便保证了红黑树的性质,最后返回x节点进行delete

			else if (bother->color == BLACK && bother->leftChild->color == RED)
			{//情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧
				Color_t c = father->color;
				son = bother->leftChild;
				bother->leftChild = son->rightChild;
				if (son->rightChild != Nil)
					son->rightChild->parent = bother;
				father->rightChild = son->rightChild;
				if (son->leftChild != Nil)
					son->leftChild->parent = father;
				father->rightChild = son;
				son->parent = father;
				son->rightChild = bother;
				bother->parent = son;//以上将其调整为外侧
				Single_rotation(bother, son, father, x);//单旋转
				son->color = c;
				father->color = BLACK;
				bother->color = BLACK;
				father->leftChild = Nil;
				//father->rightChild  = Nil;

			}
情形三:被删节点的兄弟节点为黑色,且其兄弟节点无孩子节点

情形特点【x为黑色,无孩子节点,bother为黑色,无孩子节点】
在这里插入图片描述
此情形中又分为两种情况father为红,father为黑,如上图

b.1 father为红

思路上,是要将x节点的黑色转移到father中,再将bother的黑色转为红色,如此便满足红黑树各条性质
在这里插入图片描述

b.2 father为黑

当father时,将x的黑色转移到father处,用Erase(father)函数调整father内多余的黑色,但为了防止递归死循环,调用前需先将father的孩子节点断开赋空。
在这里插入图片描述
最后将bother赋红,连接father与bother即可。
在这里插入图片描述
返回x进行delete

			else if (bother->color == BLACK && bother->leftChild == Nil && bother->rightChild == Nil)
			{//情形三:兄弟节点为黑色,且无孩子节点
				if (father->color == RED)
				{//父节点为红色
					bother->color = RED;
					father->color = BLACK;
				}
				else
				{//父节点为黑色
					father->leftChild = Nil;
					father->rightChild = Nil;
					//断开孩子节点
					Erase(father);
					if (bother->data < father->data)
						father->leftChild = bother;
					else
						father->rightChild = bother;
					//连接father与bother节点
					if (father->data < father->parent->data)
						father->parent->leftChild = father;
					else
						father->parent->rightChild = father;
						//连接father与father->parent
					bother->color = RED;
				}
				x->parent->leftChild = Nil;
				//father->rightChild  = Nil;
			}
情形四:被删节点的兄弟节点为红色,即它的孩子节点和父节点必为黑

在这里插入图片描述

对bother的外侧孩子进行单旋转,将bother赋黑,father赋红,此时x产生了新的bother再调用Erase(x)函数(情况五的情形三b.1)返回x进行delete

			else if(bother->color == RED)
			{
				//情形四:被删节点的兄弟节点为红色,即它的孩子节点和父节点必为黑
				father->rightChild = bother->leftChild;
				bother->leftChild->parent = father;
				if (father->parent->leftChild == father)
					father->parent->leftChild = bother;
				else
					father->parent->rightChild = bother;
				bother->parent = father->parent;
				bother->leftChild = father;
				father->parent = bother;
				//单旋转
				father->color = RED;
				bother->color = BLACK;
				Erase(x);
			}
情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil

正常情况不可能直接进入此情形(原因是直接进入时 x没有孩子节点,而bother为黑且还有两个黑色的孩子节点,便不满足性质五,所以不可能直接进入),进入此情形是必由情况三的情形三的b.2调用函数进入的。则此时x存储了两个黑色,且兄弟节点及其孩子节点为黑色:
在这里插入图片描述

第一步:我们需要断开father与其孩子节点的连接(视为x处的黑色转移到了father处)接下来需要将father多余的黑色转移
在这里插入图片描述
第二步:调用Erase(father)调整father的黑色【1、把红色的father覆盖(情况一);2、转移到father的红色兄弟节点上(情况四);3、转移到father的红色子侄节点(情况五的情形一/二);4、father节点,father的兄弟节点,father的子侄节点均为黑(情况五的情形五)】下方图c系列 详解

返回后father的黑色已被转移
在这里插入图片描述

第三步:将father与father->parent重新进行连接,原因是在传入参数father的Erase函数内视为要将father删除,会断开father与father->parent的连接。

第四步:重新连接father与孩子节点的联系,因为在调用Erase前断开了father与其孩子节点的联系,此时也需重新进行连接(注:左右孩子都需要连接,该逻辑内,x不是真正要被删除的节点)

最后返回至情况五的情形三的b.2

在这里插入图片描述

			else if (bother->leftChild != Nil && bother->rightChild != Nil && bother->leftChild->color == BLACK && bother->rightChild->color == BLACK)
			{//情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil
				bother->color = RED;
				father->leftChild = Nil;
				father->rightChild = Nil;
				Erase(father);
				if (father->data < father->parent->data)
					father->parent->leftChild = father;
				else
					father->parent->rightChild = father;
					//连接father和father->parent
				if (father->data > bother->data)
				{
					father->leftChild = bother;
					father->rightChild = x;
				}
				else 
				{
					father->leftChild = x;
					father->rightChild = bother;
				}
				//连接father和两个孩子节点
			}
调整father的黑色:三种节点:x的父节点,x的兄弟节点,x的子侄节点

图c.1便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的父节点
图c.2是最后一次调用Erase(father),函数中将被视为情况一进行处理,需要注意的是此处需要为这个红色父节点赋黑,由于情况一认为删除的就是此红节点,所以在情况一的逻辑内中对x的颜色进行赋黑处理,是不会影响情况一的删除逻辑的。

在这里插入图片描述

图c.3便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的兄弟节点。
图c.4是最后一次调用Erase(father),函数中将被视为情况五中的情形四处理
在这里插入图片描述

图c.5便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的子侄节点
图c.4是最后一次调用Erase(father),函数中将被视为情况五中的情形四处理
在这里插入图片描述
删除也就到此完毕,欢迎留言评论
红黑树删除源码
测试函数

#include"Insert.h"
#include"Eease.h"
#include<map>
#include<vector>
using namespace std;

/* 检查黑色平衡、指针、结点颜色,函数返回黑色高度 */
template<class Type>
int RBTree<Type>::check_tree(RBNode<Type>* temp)
{
    if (temp == Nil)
        return 0;

    /* 黑色节点计数,判断黑色平衡 */
    int ct_black = 0;

    if (temp->color == BLACK)
        ct_black = 1;
    
    /* 判断parent指针是否正确 */
    if (temp->leftChild != Nil && temp->leftChild && temp->leftChild->parent != temp)
    {
        //Print_Tree();

        cout << temp->data << "ERROR: pointer left error: " << temp->leftChild->data << endl;
        exit(-1);

    }
    if (temp->rightChild != Nil && temp->rightChild && temp->rightChild->parent != temp)
    {
        cout << temp->data << "ERROR: pointer right error: " << temp->rightChild->data << endl;
        exit(-1);
    }

    /* 判断是否有两个连续红色节点 */
    if (temp->color == RED)
    {
        // tourist();
        if (temp->leftChild != nullptr && temp->leftChild->color == RED)
        {

            cout << temp->data << "ERROR: color error with left: " << temp->leftChild->data << endl;
            exit(-1);
        }
        if (temp->rightChild != nullptr && temp->rightChild->color == RED)
        {
            cout << temp->data << "ERROR: color error with right: " << temp->rightChild->data << endl;
            exit(-1);
        }
    }

    /* 判断黑色平衡 */
    int left_black = check_tree(temp->leftChild);
    int rrr = check_tree(temp->rightChild);
    if (left_black != rrr)
    {
        cout << temp->data << endl;
        cout << "ERROR: check_Node error in color" << endl;
        exit(-1);
    }

    return ct_black + left_black;
}


/* BST中序遍历的得到的key从小到大 */
template<class Type>
void RBTree<Type>::isValidBST(RBNode<Type>* root) {
    stack<RBNode<Type>* > st;
    RBNode<Type>* last = Nil, * now = root;
    while (true)
    {
        while (now != Nil)
        {
            st.push(now);
            now = now->leftChild;
        }
        if (st.size())
            now = st.top();
        else return;
        st.pop();
        if (last != Nil && now->data <= last->data)
        {
            std::cout << "EROOR: not BST" << std::endl;
            exit(-1);
        }
        last = now;
        now = now->rightChild;
    }
    return;
}
  
/* 判断红黑树的正确性 */
template<class Type>
void RBTree<Type>::isVaildRbTree()
{
    /* 判断是否是BST */
    check_tree(root);
    /* 判断红黑树其他要求 */
    isValidBST(root);
    if (root != Nil && root->color == RED)
    {
        std::cout << "ERROR: root color is  not black" << std::endl;
        exit(-1);
    }
}



/* 测试函数 */
void test(int n)
{
    RBTree<int> rbTree{};
    map<int, int> mp;  /* 标记已经生成的key */
    vector<int> index; /* 保存key方便删除 */

    /* 测试插入 */
    while (n--)
    {
        /* 随机生成key */
        int key;
        while (mp.find(key = rand() % 1000) != mp.end())
        {
        }
        mp[key] = 1;

        /* 保存key */
        index.push_back(key);

        cout << "insert key: " << key << endl;
        rbTree.Insert(key);

        rbTree.check_tree(rbTree.get_root());
        // rbTree.tourist();
    }
    rbTree.Print_Tree();
    cout << "--------------------------------" << endl;
    cout << "--------------------------------" << endl;

    /* 随机将插入的key删除 */
    while (index.size())
    {
        /* 随机生成已有的key并删除对应结点 */
        int id = rand() % index.size();
        int key = index[id];

        cout << "erase key: " << key << endl;
        index.erase(index.begin() + id);
    
        rbTree.Erase(key);
       
        rbTree.check_tree(rbTree.get_root());
    }

    cout << "rb_tree test success" << endl;
}


int main()
{
    test(500);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值