PKU 数据结构与算法笔记——红黑树

定义

满足下列条件的二叉搜索树

  • 有红黑两种颜色
  • 树根为黑色
  • 树叶也为黑色
  • 红红限制:父子结点不允许红红连续
  • 路径上黑节点数目相同:任意结点到其每个叶结点包含相同数目的黑结点

是一种扩充的BST树

结点x的阶也称”黑色高度“,指从该结点到外部结点的黑色结点的数里,不包括X本身,包括叶结点

叶结点的阶是0,根的阶称为该树的阶

性质

  • 红黑树是满二叉树,空树叶也看作结点
  • K阶红黑树从根到叶的简单路径长度介于【k,2k】,或者说树高介于【k+1,2k+1】之间
  • k阶红黑树内部结点树最少是2k-1
  • n个内部结点红黑树的最大高度为 2 log ⁡ 2 ( n + 1 ) + 1 2\log_2(n+1)+1 2log2(n+1)+1
  • 红黑树检索、删除、插入等操作的复杂度为O(log2n),n为结点的数目
//代码只写出了大概思路,细节部分需要根据实际调整
template<class T>
class RBNode
{
private:
	T data;//数据
	RBNode* lefChild;//左子结点
	RBNode* rightChild;//右子结点
	RBNode * father;//父结点
	COLOR color;//颜色
public:
    RBNode(){}
	RBNode(T value, COLOR col)//构造函数
	{
		data = value;
		color = col;
		lefChild = NULL;
		rightChild = NULL;
		father = NULL;
	}
	void SetData(T value)//设置数据
	{
		data = value;
	}
	T GetData()//获得数据
	{
		return data;
	}
	void SetLeftChild(RBNode* left)//设置左子结点
	{
		lefChild = left;
	}
	RBNode* GetLeftChild( )//获得左子结点
	{
		return lefChild;
	}
	void SetRightChild(RBNode* right)//设置右子结点
	{
		rightChild = right;
	}
	RBNode* GetRightChild()//获得右子结点
	{
		return rightChild;
	}
	void SetFather(RBNode* par)//设置父结点
	{
		father = par;
	}
	RBNode* GetFather()//获得父结点
	{
		return father;
	}
    void SetColor(Color col)//设置颜色
	{
		color = col;
	}
	Color GetCorlor()//获得颜色
	{
		return color;
	} 
    void LeftRotate();//左旋
    void RightRotate();//右旋
};
template<class T>
class RBTree
{
private:
    RBNode<T> *Root;
public:
    RBTree(T data)
    {
        Root->SetData(data);
        Root->SetColor(BLACK);
        Root->SetLefChild(NULL);
        Root->SetRightChild(NULL);
        Root->SetFather(NULL);
    }
    bool Insert(T Key);//插入
   	bool Delete(T Key);//删除
};

操作

旋转

左旋:让旋转结点的右子结点成为旋转结点的父结点,旋转结点的右子结点的左子结点成为旋转结点的右子结点

左旋

右旋:让旋转结点的左子结点成为旋转结点的父结点,旋转结点的左子结点的右子结点成为旋转结点的左子结点

右旋

从示例中可以看出,旋转操作不会改变二叉搜索树的基本要求(左子结点小于父结点,右子结点大于父结点)

//代码只写出了大概思路,细节部分需要根据实际调整
void LeftRotate()
{
    RBNode *child = this->GetRightChild();//先记录下右子结点
    if (child == NULL)
        return;
    child->SetFather(this->father);//头节点的父节点为空
    if (this->father != NULL)
    {
        if (this->GetFather()->GetLeftChild() == this)//将旋转结点的右子结点放在原来旋转结点的位置上
            this->GetFather()->SetLeftChild(child);
        else
            this->GetFather()->SetRightChild(child);
    }
    this->SetFather(child);//将旋转结点的父结点设为其右子结点
    this->SetRightChild(child->GetLeftChild());//将旋转结点的右子结点设为其原来右子结点的左子结点
    child->SetLeftChild(this);//将旋转结点的右子结点的左子结点设为旋转结点
}
void RightRotate()
{
    RBNode *child = this->GetLeftChild();//先记录下左子结点
    if (child == NULL)
        return;
   	child->setFather(this->GetFather());//头节点的父结点为空
   	if (this->GetFather() != NULL)
    {
        if(this->GetFather()->GetLeftChild() == this)//将旋转结点的左子结点放在原来旋转结点的位置上
            this->GetFather()->SetLeftChild(child);
        else
            this->GetFather()->SetRightChild(child);
    }
    this->SetFather(child);//将旋转结点的父结点设为其左子结点
    this->SetLeftChild(child->GetRightChild());//将旋转结点的左子结点设为其原来结点的右子结点
    child->SetRightChild(this);//将旋转结点的左子结点的右子结点设为旋转结点
}

插入

插入前的位置搜索的过程与二叉搜索树相同,插入之后要用颜色变换和旋转保持上述的五条性质,插入的结点设置为红色

若插入结点是根结点,则将其设置为黑色即可;若插入结点的父结点为黑色,不会违背任何一条性质,这两种情况不做细致讨论

情况1

描述:冲突结点的父亲和叔叔都是红色

操作:将父亲和叔叔变为黑色,若爷爷结点不是根结点则将其变为红色

解释:此时将父亲和叔叔变为黑色,会导致这条路径上的黑色结点比其它路径多一个,所以要把其爷爷变为红色,其爷爷也可能和更上层的结点发生冲突,再根据新的冲突情况处理即可;若冲突结点的爷爷是根结点,则不用变为红色,因为虽然会导致这条路径上的黑色结点数加1,但是这条路径包括根结点,也就包括了所有路径,相当于树的阶加1,所以这样操作不会违背任意一条性质

总结:不一定能解决冲突,等价于把冲突向上推了两代(若上推两代没有冲突则冲突解决)

注意:冲突结点(图中的4)是左子结点还是右子结点解决方法都相同

情况2

描述:冲突结点的叔叔为黑色,且冲突结点与父结点和爷爷结点不在的一条线上

操作:以冲突结点的父结点为轴进行旋转,使冲突结点、父结点和也叶结点在一条线上

解释:只是将冲突的形式在局部进行转换,不会产生新的冲突

总结:不能解决冲突,只是转换冲突的形式

注意:冲突结点必须是右子结点

情况3

描述:冲突结点的叔叔为黑色,且冲突结点与父结点和爷爷结点在一条线上

操作:将其父结点变为黑色,爷爷结点(一定有,若没有则根结点为红色,矛盾)变为红色,在以爷爷结点为轴旋转

解释:在局部将冲突解决,可以看到,从局部的根结点出发,到每个叶结点的黑色结点数没有改变,那么整体也不会产生新的冲突

总结:可以解决冲突,插入算法在此停止

转换图

可以看出情况1可以回到自己,也可以转换为情况2、情况3或者结束,但情况2必须转换为情况3

算法

//代码只写出了大概思路,细节部分需要根据实际调整
bool Insert(T Data)
{
    RBNode<T> *root = Root;
	while(root != NULL)//寻找插入的位置
    {
        if (Data > root->GetData())
            root = root->GetRightChild();
        else if (Data < root->GetData())
            root = root->GetLeftChild();
        else
            return false;//已有则插入失败
    }
    RBNode<T> *node = RBNode(T Data, col RED);//插入结点
    node->SetFather(root->GetFather);
    if (root->GetFather->GetLeftChild() == root)
    	root->GetFather->SetLeftChild(node);
    else 
        root->GetFather->SetRightChild(node);
	//进行调整
	if (node->GetFather() == NULL)//插入的是根结点
	{
		node->SetColor(BLACK);
		return true;
	}
	if (node->GetFather()->GetCorlor() == BLACK)//插入结点的父亲是黑色的,无冲突
		return true;
	//接下来分别处理三种情况
    //由于在不在一条线上的判断在左子树和右子树上略有不同,所以分开讨论
	while (node->GetFather()->GetCorlor() == RED)
	{
		if (node->GetFather()->GetFather()->GetLeftChild() == node->GetFather())
		{//插入的节点在爷爷节点的左子树上
			RBNode<T>* uncle = node->GetFather()->GetFather()->GetRightChild();//得到叔叔节点
			if (uncle != NULL && uncle->GetCorlor == RED)//情况1
			{
				//父节点变黑,叔叔节点变黑,爷爷节点变红,node节点指向爷爷节点,完成一次向上的迭代
				node->GetFather()->SetColor(BLACK);
				uncle->SetColor(BLACK);
				node->GetFather()->GetFather()->SetColor(RED);
				node=node->GetFather()->GetFather();
			}
			else//叔叔节点是黑色
			{
				if (node->GetFather()->GetRightChild() == node)//情况2,不在一条直线上,父节点左旋
					node->GetFather()->LeftRotate();
				//情况3,父节点变黑,爷爷节点变红,爷爷点右旋
				node->GetFather()->SetColor(BLACK);
				node->GetFather()->GetFather()->SetColor(BLACK);
				node->GetFather()->GetFather()->RightRotate();
				break;//完成跳出
			}
		}
		else
		{//插入的节点在爷爷节点的右子树上
			RBNode<T>* uncle = node->GetFather()->GetFather()->GetLeftChild();//得到叔叔节点
			if (uncle != NULL && uncle->GetCorlor == RED)
			{//情况1
				//父节点变黑,叔叔节点变黑,爷爷节点变红,node节点指向爷爷节点,向上迭代2层
				node->GetFather()->SetColor(BLACK);
				uncle->SetColor(BLACK);
				node->GetFather()->GetFather()->SetColor(RED);
				node = node->GetFather()->GetFather();
			}
			else//叔叔节点是黑色
			{
				if (node->GetFather()->GetLeftChild() == node)//情况2,不再一条直线上,父节点右旋
					node->GetFather()->RightRotate();
				//情况3,父节点变黑,爷爷节点变红,爷爷点左旋
				node->GetFather()->SetColor(BLACK);
				node->GetFather()->GetFather()->SetColor(BLACK);
				node->GetFather()->GetFather()->LeftRotate();
				//完成跳出
				break;
			}
		}
	}
    return true;
}

删除

整体操作

待删除结点分三种情况

  • 要删除的结点是叶结点,则该结点就是待删除结点
  • 要删除的结点只有一个子结点,该结点也就是待删除结点
  • 要删除的结点有两个子结点,在其右子树中寻找最左子结点(右子树中的最小值),该结点最多有一个子结点,将该值复制到要删除结点,该最左子结点成为待删除结点

找到待删除结点后

  • 若结点是红色的,直接删除并让其子结点代替其位置即可,不会产生红红冲突,也不会使得路径上的黑色结点数改变
  • 若结点是黑色的,将其删除并让其子结点代替其位置,同时把颜色附加给子结点,然后继续调整

对于被附加颜色的结点其颜色为“双黑”或“红黑”

  • 该结点是根结点,将其颜色直接设置为黑色即可
  • 该结点是红黑结点,将其颜色设为黑色即可,这样既不会产生红红冲突,由于这个黑色结点原本也存在于这条路径上(删除后只是值改变),也不会造成路径上黑色结点数目的变化
  • 若该结点是双黑结点,则要进入接下来的讨论

情况1

描述:该结点的兄弟为红色,则其父亲应该为黑色

操作:交换兄弟和父亲的颜色,然后以父亲为轴进行旋转

解释:可以观察到对于这个局部来说这样的操作没有改变路径上的黑色结点数,没有产生新的冲突,但并没有解决冲突

总结:不能解决冲突,只改变冲突的形式

情况2

描述:该结点的兄弟为黑,且其兄弟的两个儿子也都为黑

操作:将该结点和其兄弟都脱去一个黑色,变成黑色和红色,把这个黑色传递给他们的父亲,若父亲是红色的则直接变为黑色,若父亲是黑色变为双黑,将冲突上移一层,若父亲是根结点,将其变为黑色后算法停止

解释:上述操作首先不会产生红红冲突,从两条路径上各拿出一个黑色,给他们的父结点,则整体路径上的黑色结点数没有改变

总结:不一定能解决冲突,至少能将冲突上移一层

情况3

描述:结点的兄弟为黑色,其子结点中有一个红色结点,且与其父结点和爷爷结点不在一条线上

操作:交换该红色结点和其父结点的颜色,并以父结点为轴旋转

解释:无论待删除结点的父结点是黑色还是红色都有一个黑色结点在其与红色结点中间,不会产生红红冲突 ,也不会造成路径上黑色结点数目增加

总结:不能解决冲突,只是转换冲突的形式

情况4

描述:该结点的兄弟为黑色,且对于兄弟结点来说,与其父结点和其自身在一条线上的子结点为红色(无论另一子结点颜色)

操作:交换根结点和其兄弟结点的颜色,并将双黑中的一个黑赋予那个红色结点,然后以根结点为轴旋转

解释:无论父结点的颜色,都不会产生红红冲突,同时以旋转后新的根结点为基准再次计算两边的黑色结点数目,没有改变且解决了冲突

总结:能解决冲突,算法在此结束

转换图

可以看出,情况1和情况3都只能转换为别的情况,情况2和情况4能解决冲突,同时如果情况1转换为情况2一定会直接解决(图中红色的线)

算法

//代码只写出了大概思路,细节部分需要根据实际调整
bool Delete(T Data)
{
    RBNode<T> *node = Root;
	while(root != NULL)//寻找删除的位置
    {
        if (Data > root->GetData())
            root = root->GetRightChild();
        else if (Data < root->GetData())
            root = root->GetLeftChild();
        else
        {
            node = root;//node是实际要删除的结点
            break;
        }
    }
    if (node == NULL)
        return false;//没找到删除的结点
    //对于有两个子结点的情况需要寻找一个替换结点
    if(node->GetLeftChild != NULL && node->GetRightChild != NULL)
    {
        RBNode<T> *n = node->GetRightChild();
        while(n->GetLeftChild() != NULL)
        	n = n->GetLeftChild();
        node->SetData(n->GetData());
        node = n;
    }
    //以后的node就是上述讨论中的待删除结点
    COLOR col = node->GetColor();//记录待删除结点的颜色
    //删除结点并用子结点代替
    if (node->GetRightChild() != NULL)
    {
        if (node->GetFather()->GetLeftChild() == node)
        {
            node->GetFather()->SetLeftChild(node->GetRightChild());
            if (node->GetRightChild() != NULL)
                node->GetRightChild()->SetFather() = node->GetFather();
        }
        node = node->GetRightChild();
    }
    else if (node->GetLeftChild != NULL)
    {
        if (node->GetFather()->GetRightChild() == node)
        {
            node->GetFather()->SetRightChild(node->GetLefttChild());
            if (node->GetLefttChild() != NULL)
                node->GetLefttChild()->SetFather() = node->GetFather();
        }
        node = node->GetLeftChild();
    }
    if (col == RED)//待删除结点为红色则删除后用子结点代替,算法结束
    	return true;
    else if (col == BLACK)//待删除结点为黑色
    {
    	if (node->GetColor() == RED)//代替它的结点为红色,把其设为黑色即可
        {
            node->SetColor(BLACK);
            return true;
        }
        else if (node->GetColor() == BLACK)//代替结点为黑色,以下node即是上述讨论中的双黑结点
        {
           while(node != NULL)
           {
               if (node->GetFather()->GetLeftChild() == node)//讨论双黑结点在左边的情况
               {
                   RBNode<T> *brother = node->GetFather()->GetRightChild();
                   if (brother->GetColor() == RED)//情况1
                   {
                       node->GetFather()->SetColor(RED);
                       brother->SetColor(BLACK);
                       node->GetFather()->LeftRotate();
                   }
                   else
                   {
                       if (brother->GetLeftChild()->GetColor() == BALCK 
                          && brother->GetRighttChild()->GetColor() == BALCK)//情况2
                       {
                           if(node->GetFather()->GetColor() == RED)
                           {
                               node->GetFather()->SetColor(BLACK);
                               brother->SetColor(RED);
                               if (node->GetFather() == Root)
                                   return true;
                           }
                           else
                           {
                               brother->SetColor(RED);
                               node = node->GetFather();
                           }
                       }
                       if (brother->GetLeftChild()->GetColor() == RED 
                          && brother->GetRighttChild()->GetColor() == BALCK)//情况3
                       {
                           brother->SetColor(RED);
                           brother->GetLeftChild()->SetColor(BLACK);
                           brother->RightRotate();
                       }
                       if (brother->GetLeftChild()->GetColor() == BLACk 
                          && brother->GetRighttChild()->GetColor() == RED)//情况4
                       {
                           node->GetFather()->SetColor(BLACK);
                           brother->GetRightChild()->SetColor(BLACK);
                           node->GetFather()->LeftRotate();
                           return true;
                       }
                   }
               }
               else//双黑结点在右边的情况
               {
                   //处理类似,把所有的方向换一下即可
               }
           }
        }
    }
    return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值