【STL】红黑树(插入、删除底层实现)

news2024/11/17 1:32:11

目录

红黑树简介

红黑树框架构建

构造函数       析构函数

四种旋转逻辑(左单旋,右单旋,左右双旋,右左双旋)

左右双旋与右左双旋

左单旋

右单旋

红黑树的插入 Insert

插入节点的颜色

红黑树插入的三种情况

代码编写

插入完整代码

红黑树的删除 Erase

1. 当删除节点有两个孩子

2. 当删除节点有一个孩子

3. 当删除节点没有孩子(叶子节点)

3.1 该节点为红色节点

3.2 该节点为黑色节点

结语


红黑树简介

首先我们来聊一聊红黑树是什么

在之前的学习中,我们了解到了二叉搜索树,AVL树

二叉搜索树的特性是左子树  <  根  <  右子树,但是二叉搜索树也有缺点,就是如果处在极端情况下,那么二叉搜索树的效率就和链表差不多

为了解决这个问题,我们学习了AVL树,这棵树是通过旋转来控制树的高度,以此来达到绝对平衡的目的,也就是任何一棵子树,左右高度差都不会超过1

但是这也伴随着一个问题就是,如果我们每一次插入,都判断、旋转,这样子的效率固然是高,但是有没有更好的办法,让其在保持高度平衡的同时,尽量减少旋转的次数

这时候我们就引出了我们的红黑树

AVL树是通过平衡因子来控制树的高度的,而红黑树是一种通过颜色控制高度的树

红黑树有以下四条规则:

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 每条路径均包含相同数目的黑色结点 

红色节点是不能与黑色节点相连的,并且,每条路径都有相同黑色节点,这点非常重要

我们来思考一下,为什么通过颜色,就能控制住树的高度,还有就是这样子是否真的能保证树的平衡

注意看,上图是最极限的情况(高度),因为两边都不能再加黑色节点了,每条路径都只有两个黑色节点,如果再加节点的话,在(根节点的)右子树加那么高度就没有差那么多了,不是最极限的情况,如果在(根节点的)左子树插入节点的话,就只能插入黑色的节点,但是单在左树上插入黑色节点,那么其他路径的黑色节点和这个的就不一样了,所以就需要旋转,这样就控制住了高度

而且,红黑树要求的不是绝对的平衡,而是一种相对平衡

他不像AVL树,一有不平衡就旋转,他会到真的不得已了才旋转,旋转次数下来了,效率就高了,并且红黑树还能保持树的高度平衡

所以,在库里面的set、map封装都是拿红黑树做的底层

红黑树框架构建

首先,我们的红黑树是通过颜色控制的,所以我们可以写一个联合体enum,我们后面就可以直接写对应颜色的名字,这样代码的可读性也会提高

enum Colour
{
	RED,
	BLACK
};

然后我们还需要写一个红黑树的节点类,因为一个节点里面要存指针,存联合体,存pair类型的数据,所以我们就只能写一个类,然后将这个类做为红黑树的成员变量

template<class k, class v>
struct RBTreeNode
{
	RBTreeNode<k, v>* _left;// 左指针
	RBTreeNode<k, v>* _right;// 右指针
	RBTreeNode<k, v>* _parent;// 父节点
	Colour _col;// 颜色
	pair<k, v> _data;// 数据

	RBTreeNode(const pair<k, v>& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
	{}
    // 这里的颜色不需要初始化,我们后面插入节点的时候才初始化
};

最后,我们还需要将这个节点类做为红黑树的成员变量,当然我们在这之前还可以对其typedef一下,可以将其直接typedef成Node,这样我们也好写

然后红黑树这个类需要的是一个节点指针,这个指针指向根节点

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:

private:
	Node* _root;
};

构造函数       析构函数

由于我们的成员变量是一个类,默认生成的构造函数会调用这个类的构造,所以我们并不需要显示写构造,所以写不写其实一样

但是考虑到有些人会喜欢加上拷贝构造,那我们就需要强制编译器生成一个构造函数

// 强制编译器生成构造
RBTree() = default;

至于析构的话,我们直接写一个递归即可

先递归左子树,然后是右子树,最后删除当前节点即可,至于递归出口就是,当该节点为空,就退出

当然我们可以写一个函数来完成这个递归的逻辑,最后在函数结束后,我们再将这个根节点置空

~RBTree()
{
	Destroy(_root);
	_root = nullptr;
}

void Destroy(Node* root)
{
	if (root == nullptr)return;

	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

四种旋转逻辑(左单旋,右单旋,左右双旋,右左双旋)

左右双旋与右左双旋

我们在AVL树中,之所以要单独实现这个逻辑,是因为双旋之后的平衡因子需要我们另外去控制

但是我们在红黑树中并没有平衡因子这个概念,而且我们颜色的控制还是统一在旋转完之后控制的

所以我们并不需要显示实现双旋,要实现双旋只需要将把两个单旋直接拿来用即可

左单旋

左单旋的代码如下:(解析在代码下面)


//左单旋
void RotateL(Node* parent)
{
    ///
    
	Node* curR = parent->_right;
	Node* curRL = curR->_left;
	Node* Parentparent = parent->_parent;
 
	//旋转逻辑
	parent->_right = curRL;
	curR->_left = parent;
 
    ///
 
	//处理parent
	if (curRL)curRL->_parent = parent;
	parent->_parent = curR;
 
    ///
 
	if (!Parentparent)
	{
		_root = curR;
		curR->_parent = nullptr;
	}
	else
	{
		if (Parentparent->_left == parent)
			Parentparent->_left = curR;
		else
			Parentparent->_right = curR;
 
		curR->_parent = Parentparent;
	}
 
    ///
}

这代码其实很简单,不要将其想得太复杂,首先我们是先将三个节点定义了出来

其中第三个是parent的_parent,我们将其命名为Parentparent

然后将curRL变成parent的右节点

然后parent自己成为curR的左节点

因为本来就是curR那边多出来了,我们旋转一下之后,parent下去了,那么自然就平衡了

(上述为第一块代码区域)

而后面的代码就都是在干一件事:找父节点

因为我们的节点是有三个指针的,一个左,一个右,一个parent

我们接下来解决的就是parent指针的指向

首先是curRL,因为我们不确定其是否为空,所以我们需要判断一下,如果存在,再让其的parent指针指向parent

(上述为第二块代码区域)

然后是parent,parent的_parent指针指向的是curR

最后是curR,我们无法确定Parentparent是否为空,因为如果parent节点旋转之前是根节点呢

所以我们就加了一条判断逻辑(如果为空,就直接让curR做根节点,并将curR的parent节点置空)

然后,如果Parentparent不为空,因为Parentparent还指向着parent节点,所以我们就直接判断curR是要链接在Parentparent的左边还是右边

最后让curR的parent指针指向Parentparent

(上述为第三块代码区域)

右单旋

右单旋其实就是左单旋的镜像处理,代码如下:

// 右单旋
	void  RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parentParent == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}
	}

红黑树的插入 Insert

插入节点的颜色

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 每条路径均包含相同数目的黑色结点 

这里我们需要明白的一个点是,我们插入的节点是要什么颜色的

颜色就分为红色和黑色

如果我们插入黑色呢?

只要我们插入黑色,那就必须要调整,因为规则 4 是:每条路径上的黑色节点数量都需要相同

但是我们插入了黑色节点之后,就破坏了规则 4 ,所以就必须要进行处理

但是如果我们插入的是一个红色节点

当我们为红色节点的时候,如果父节点为黑色,那么我们就可以直接插入,并不需要做任何调整,因为并没有违反任何一条规则

但是如果我们的父节点是红色,那就违反规则 3 了,这时候才需要做出相应的调整

我们试想一下,插入黑色节点,就必须要调整,但是如果我们插入红色节点的话,如果父节点也是红才需要调整

就像是你考差了,你拿给爸爸那就肯定要挨顿打,但是如果拿给妈妈看,说不定妈妈心情好,就不会打,说不定看你这么差,就打你一顿

既然选爸爸一定会,选妈妈可能会,那我们肯定还是选妈妈嘛,说不定呢

综上,我们插入的节点颜色为红色

红黑树插入的三种情况

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 每条路径均包含相同数目的黑色结点 

由于我们插入的节点为红色,那么就只有当父节点为红色的时候,才会进入调整的逻辑,所以父节点肯定是红色的

根据规定 2,根节点是黑色的,那既然父节点为红,那上面就一定还有一个爷爷(grandpa)节点(黑色)

那现在就是爷爷节点为黑,父节点和新插入节点为红,就只有一个叔叔(uncle)节点是不确定的

目前情况如上

而叔叔节点分三种情况:为红色、为黑、不存在

1. 叔叔节点为红

如果叔叔节点为红色的话,那就意味着,我们是不需要旋转控制的,因为如果叔叔为黑,父亲和新插入节点都为红,那么父亲和新节点下面就必然有黑色节点,所以新插入的节点其实并不是新插入的,而是其他情况变过来的

但是如果叔叔也为红,那就不会有这种情况,我们只需要变色处理即可

具体就是将父节点和叔叔节点变黑,然后新插入节点还是红色,爷爷节点变红即可

2. 叔叔节点不存在

如果叔叔节点不存在的话,那我们就必须要旋转了

这种情况就是旋转 + 变色

3. 叔叔节点为黑

叔叔节点为黑的话,那也是必须要旋转的

而且,这种情况一定是上面的情况变过来的,因为如果叔叔为黑,那么父节点和新插入节点为红,就不满足每条路径都只有一个黑色节点的规则,所以,父节点的另一个孩子节点一定为黑,并且新插入节点也一定有两个孩子节点且为黑

但是这时候新插入节点和父节点都为红色啊,所以就一定是其他情况变过来的

代码编写

关于红黑树的插入,我们在插入之前,需要先判断一下根节点是否为空,如果为空,就证明这是一棵空树,我们直接new一个节点给根,然后退出即可

接着需要查找一下这个值在不在红黑树里面,如果在的话,我们就没有插入的必要了

bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (cur->_data.first < data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_data.first > data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
				return false;
		}

如果能走到下面的逻辑,就证明我们已经找到待插入的位置了,我们只需要做三件事:

  1. new一个新节点
  2. 将这个节点的_parent指针与parent的孩子指针处理好
  3. 新节点颜色设置为红色
cur = new Node(data);
cur->_col = RED;

if (cur->_data.first < parent->_data.first)
	parent->_left = cur;
else
	parent->_right = cur;
cur->_parent = parent;

接着就是颜色改变与旋转的逻辑了

首先,我们的父节点为黑色的话,是不需要进入这个逻辑的,直接插入节点就好,不需要任何调整

然后当我们进入循环,代表着父节点一定为红

这时候就要开始分情况了,叔叔节点在爷爷节点的左边、叔叔节点在爷爷节点的右边

这是两种情况,代表着(右单旋、左右双旋)和(左单旋、右左双旋)

接着我们就是进入判断叔叔的环节了,我们先把代码实现一下:

while (parent && parent->_col == RED)
{
	Node* grandpa = parent->_parent;

	if (grandpa->_left == parent)
	{
		// parent 在爷爷节点左边
	}
	else
	{
		// parent 在爷爷节点右边
	}
}

然后叔叔又分三种情况:

  1. 叔叔为红
  2. 叔叔不存在
  3. 叔叔为黑

这里的话,叔叔为黑与不存在可以当成是一种情况,因为这两种情况都是要旋转

并且不管叔叔在不在,最后变色都不需要叔叔来变色

我们看上图,两种情况都是爷爷变红,父节点变黑

而同时,也还有另一种情况,就是双旋的问题,这个其实比较简单,就是新插入节点在父节点的右或左的问题,如果父节点在爷爷的左,新插入节点在父亲的左,那就是右单旋

如果新插入节点在父亲的右,那就是左右双旋,父节点在爷爷右的情况镜像处理即可

这两个唯一的区别就在于:

双旋是新插入节点变黑,爷爷变红

单旋是父节点变黑,爷爷变红

代码如下:

while (parent && parent->_col == RED)
{
	Node* grandpa = parent->_parent;

	if (grandpa->_left == parent)
	{
		// parent 在爷爷节点左边
		Node* uncle = grandpa->_right;

		if (uncle && uncle->_col == RED)
		{
			uncle->_col = parent->_col = BLACK;
			grandpa->_col = RED;

			cur = grandpa;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_left)
			{
				// 单旋的逻辑
				RotateR(grandpa);
				parent->_col = BLACK;
				grandpa->_col = RED;
			}
			else
			{
				// 双旋的逻辑
				RotateL(parent);
				RotateR(grandpa);
				cur->_col = BLACK;
				grandpa->_col = RED;
			}
			break;
		}

	}
	else
	{
		// parent 在爷爷节点右边
		Node* uncle = grandpa->_left;
		if (uncle && uncle->_col == RED)
		{
			grandpa->_col = RED;
			uncle->_col = parent->_col = BLACK;

			cur = grandpa;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_right)
			{
				RotateL(grandpa);
				grandpa->_col = RED;
				parent->_col = BLACK;
			}
			else
			{
				RotateR(parent);
				RotateL(grandpa);
				grandpa->_col = RED;
				cur->_col = BLACK;
			}
			break;
		}
	}
}

当我们颜色调节和旋转的逻辑处理好之后,我们只需要再让根节点变成黑色即可

因为我们在颜色调节过程中可能会将根节点调整成红色的,比如情况一

这时候我们以防万一,就需要加上这一步,最后return true即可

_root->_col = BLACK;
return true;

插入完整代码

bool Insert(const pair<K, V>& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_data.first > data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
			return false;
	}

	cur = new Node(data);
	cur->_col = RED;

	if (cur->_data.first < parent->_data.first)
		parent->_left = cur;
	else
		parent->_right = cur;
	cur->_parent = parent;


	Node* newone = cur;// 提前记录值以便返回

	while (parent && parent->_col == RED)
	{
		Node* grandpa = parent->_parent;

		if (grandpa->_left == parent)
		{
			// parent 在爷爷节点左边
			Node* uncle = grandpa->_right;

			if (uncle && uncle->_col == RED)
			{
				uncle->_col = parent->_col = BLACK;
				grandpa->_col = RED;

				cur = grandpa;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					// 单旋的逻辑
					RotateR(grandpa);
					parent->_col = BLACK;
					grandpa->_col = RED;
				}
				else
				{
					// 双旋的逻辑
					RotateL(parent);
					RotateR(grandpa);
					cur->_col = BLACK;
					grandpa->_col = RED;
				}
				break;
			}

		}
		else
		{
			// parent 在爷爷节点右边
			Node* uncle = grandpa->_left;
			if (uncle && uncle->_col == RED)
			{
				grandpa->_col = RED;
				uncle->_col = parent->_col = BLACK;

				cur = grandpa;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_right)
				{
					RotateL(grandpa);
					grandpa->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateR(parent);
					RotateL(grandpa);
					grandpa->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
	}

	_root->_col = BLACK;
	return true;

}

红黑树的删除 Erase

红黑树的删除相对来说会更为复杂,因为相比于插入的逻辑,删除会多出更多情况

1. 当删除节点有两个孩子

当删除的节点有两个孩子的时候,我们就需要转换问题:

首先我们需要找到左子树的最右节点或者右子树的最左节点,这里我们以右子树的最左节点为例

然后我们让右子树最左节点将待删除节点的数据覆盖,然后问题就转换成了删除右子树的最左节点

我们之所以要找右子树的最左,是因为这个节点最符合比待删除节点的左子树大,比待删除节点的右子树小,所以我们选择这个节点覆盖待删除节点的数据,然后删除右子树最左节点

而且由于红黑树的特殊性,右子树的最左节点只能是叶子节点,或者只有一个孩子节点且这个孩子节点为叶子节点

试想一下,已经是最左节点了,就不可能有左孩子,那就是右孩子可能在或可能不在

如果不在,那最左节点就是叶子节点,如果右右子树,那么最左节点只能是黑色且这个孩子节点只能是红色,因为如果孩子节点为黑色,那么左边那条路径的黑色节点数量为1,右边的为2,数量不相等,不符合规矩,所以是不可能存在的

综上,如果有一个孩子节点的话,那么这个孩子节点只能是红色的,如果孩子为红色的,那么父亲就一定是黑色的

所以,我们就将问题由删除一个有两个孩子节点的节点,转换成了删除一个有一个孩子或者没有孩子节点的节点,也就是转换成了我们下面讨论的两种大情况

2. 当删除节点有一个孩子

如上所述,如果删除节点只有一个孩子节点的话,那么孩子一定为红,父亲一定为黑

面对这种情况,我们只需要将孩子节点的值赋给父亲,然后变成删除孩子节点

并且,如果我们单单是删除一个红色叶子节点的话,是不需要做任何处理的,直接删除即可,因为删除一个红色节点,不会影响任何一条规则,高度差也不会变成非旋转不可的情况,所以直接删除即可

总结,如果待删除节点只有一个孩子的话,我们只需要将孩子节点的值赋值给父节点,然后将问题转换成删除孩子节点即可

3. 当删除节点没有孩子(叶子节点)

3.1 该节点为红色节点

这种情况直接将该节点删除即可,不需要做任何处理

3.2 该节点为黑色节点

既然这个节点为黑色,那么我们就需要去看这个兄弟节点,因为父节点是什么颜色并无所谓,在后面的逻辑里,父节点的颜色只需要和旋转到最上面位置的节点进行覆盖即可

3.2.1  兄弟节点为红色

既然兄弟为红色,那么父亲就一定为黑色,但是此时我们的待删除节点为黑色,这时,待删除节点路径就有两个黑色节点,兄弟的那条路径只有一个,那就意味着兄弟节点一定有两个孩子,且这两个孩子都为黑

面对这种情况,我们要做的只是旋转 + 变色,并且单旋足矣

代码如下(下图中的uncle就是上文中的uncle节点,这里写错了):

// uncle节点的颜色为红
if (parent->_left == uncle)
{
	uncle->_col = BLACK;
	uncle->_right->_col = RED;
	RotateR(parent);
}
else
{
	uncle->_col = BLACK;
	uncle->_left->_col = RED;
	RotateL(parent);
}

3.2.2  兄弟节点为黑色

如果是兄弟节点为黑色的情况,我们就需要讨论兄弟节点有没有孩子,因为待删除节点也是黑色的,所以删除之后路径上的黑色节点就少了一个,这时候如果兄弟节点有孩子节点的话,我们就能在旋转后通过染色来控制黑色节点的数量

并且由于待删除节点是叶子节点且为黑,所以兄弟节点的孩子必定为红,因为为黑的话路径上的黑色节点数量就不一样了

接下来要分兄弟节点有一个孩子,两个孩子,没有孩子三种情况讨论

3.2.2.1  兄弟节点有一个红色孩子节点

这时候我们就需要看,这个红色孩子节点在兄弟节点的左还是右了

如果兄弟节点在父节点的左,并且兄弟节点的孩子节点在兄弟节点的左,那么我们就右单旋+变色即可,但是这里的变色要注意,我们删除了节点 + 旋转之后,单从结果来看是兄弟节点在最上面,然后父节点下来了,那我们父节点的颜色是不知道的,所以我们只能让兄弟节点的颜色变成父节点的颜色,然后父节点和兄弟节点的孩子节点都变成黑色

如果兄弟节点(按上图)的孩子节点在右的话,就直接走双旋逻辑即可,只不过变色变成了兄弟节点的孩子节点变成父节点的颜色,然后兄弟节点和父节点变黑而已

3.2.2.2  兄弟节点有两个红色孩子节点

面对这种情况,直接单旋即可,变色的逻辑和上面只有一个子节点且同向的处理一样

3.2.2.3  兄弟节点没有孩子节点

如果没有孩子节点,那就意味着,我们在删除完节点之后,没有多余的节点变色使每条路径上的黑色节点与原来保持一致

这时候就得看父节点了,所以我们还得再分两种情况,一种是父节点为红,一种是父节点为黑

3.2.2.3.1  父节点为红

如果此时父节点为红的话,那么就是原本为每条路径都只有一个黑色节点,这时候删除了一个,要想保持原本的黑色节点的数量的话,这时我们可以将父节点染黑,然后兄弟节点变红即可,这样这棵子树的每条路径上的黑色节点就都还是一个

总结,父亲变黑,兄弟变红

3.2.2.3.2  父节点为黑

如果是这种情况的话,我们的黑色节点的数量就没法保持两个了,所以我们就只能将兄弟节点变成红色,然后递归向上调整

直到碰到红色的父节点,节点的黑色路径就能调整回来了

代码如下(下图中的uncle就是上文中的uncle节点,这里写错了):

bool erase(const K& data)
{
	if (!_root)return false;


	Node* del = _root;
	while (del)
	{
		if (data < del->_data.first)
			del = del->_left;
		else if (data > del->_data.first)
			del = del->_right;
		else
			break;
	}

	if (del == nullptr)
		return false;
		
	if (_root == del && del->_left == nullptr && del->_right == nullptr)
	{
		delete _root;
		_root = nullptr;
		return true;
	}


	// 有两个孩子的情况
	if (del->_left && del->_right)
	{
		Node* rightMin = del->_right;
		while (rightMin->_left)
		{
			rightMin = rightMin->_left;
		}
		del->_data = rightMin->_data;

		// 将问题转换成叶子节点或者一个节点的删除
		del = rightMin;
	}

	// 有一个孩子的情况
	if ((del->_left && !del->_right) || (!del->_left && del->_right))
	{
		// 一个孩子的情况只能是父黑子红
		// 不然不满足每条路径都只有一个黑色节点

		Node* child = del->_left == nullptr ? del->_right : del->_left;
		del->_data = child->_data;
		del = child;
	}

	/ 没有孩子的情况分为红色和黑色两种
	/ 红色直接删,黑色需要细分情况
	if (del->_col == BLACK)
	{
		adjustRBTreeBalance(del);
	}

	Node* parent = del->_parent;
	if (parent->_left == del)
		parent->_left = nullptr;
	else
		parent->_right = nullptr;

	delete del;
	return true;

}



void adjustRBTreeBalance(Node* del)
{
	// 前面已经判断过了,但是这里是后面代码的递归出口
	// 因为后面写到兄弟为黑并没有子节点,并且父亲为黑的情况的时候,就需要递归处理
	if (_root == nullptr)
		return;

	Node* parent = del->_parent;
	Node* uncle = del == parent->_left ? parent->_right : parent->_left;

	if (uncle->_col == BLACK)
	{
		int type = whichtype(uncle);
		switch (type)
		{
		case LL:
			uncle->_col = parent->_col;
			parent->_col = uncle->_right->_col = BLACK;
			RotateL(parent);
			break;
		case RR:
			uncle->_col = parent->_col;
			parent->_col = uncle->_left->_col = BLACK;
			RotateR(parent);
			break;
		case LR:
            uncle->_right->_col = parent->_col;
			parent->_col = BLACK;
			RotateL(uncle);
			RotateR(parent);
			break;
		case RL:
			uncle->_left->_col = parent->_col;
			parent->_col = BLACK;
			RotateR(uncle);
			RotateL(parent);
			break;
		default:
			// 没有红色子节点
			// 需要判断父节点是什么颜色
			// 如果父节点为红色,交换颜色即可
			// 如果父节点为黑色, 则uncle节点变红后递归处理
			if (parent->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = RED;
			}
			else
			{
				uncle->_col = RED;
				adjustRBTreeBalance(parent);
			}

			break;
		}
	}
	else
	{
		// uncle节点的颜色为红
		if (parent->_left == uncle)
		{
			uncle->_col = BLACK;
			uncle->_right->_col = RED;
			RotateR(parent);
		}
		else
		{
			uncle->_col = BLACK;
			uncle->_left->_col = RED;
			RotateL(parent);
		}
	}

}

int whichtype(Node* uncle)
{
	Node* parent = uncle->_parent;

	if (uncle == parent->_left)
	{
		if (uncle->_left != nullptr)
		{
			return RR;
		}
		else if (uncle->_right != nullptr)
		{
			return LR;
		}
	}
	else
	{
		if (uncle->_right != nullptr)
		{
			return	LL;
		}
		else if (uncle->_left != nullptr)
		{
			return RL;
		}
	}

	// 无红色节点的情况
	return 0;
}

结语

看到这里,这篇博客有关红黑树的相关内容就讲完啦~( ̄▽ ̄)~*

如果觉得对你有帮助的话,希望可以多多支持博主喔(○` 3′○)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2049527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Datawhale X 魔搭 AI夏令营第四期 魔搭-AIGC方向 task03笔记

Datawhale官方的Task3链接&#xff1a;Task03 往期Task1、Task2链接&#xff1a;Task01&#xff0c; Task02 【学习者手册】&#xff1a;链接直达 【QA文档】&#xff1a;链接直达 【赛事官网】&#xff1a;链接直达 ComfyUI ComfyUI是一个基于深度学习的图像生成软件&…

鸿萌数据恢复服务:SQL Server 中的“PFS 可用空间信息不正确”错误

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份、网络及终端数据安全等解决方案与服务。 同时&#xff0c;鸿萌是国际主流数据恢复软件(Stellar、UFS、R-Studio、ReclaiMe Pro 等)的授权代理商&#xff0c;为专…

【教学类-58-10】黑白三角拼图08(参考图+操作卡+黑白块,适合个别化)

背景需求&#xff1a; 前期做了一套适合集体操作的绘画“黑白三角” 【教学类-58-09】黑白三角拼图07&#xff08;1页3张黑白的白点卡片&#xff0c;一种宫格36张&#xff0c;适合一个班级一次操作&#xff09;-CSDN博客文章浏览阅读1k次&#xff0c;点赞30次&#xff0c;收藏…

面向自动驾驶保证车辆转向稳定性的模型预测控制

摘 要 车辆智能化是当前和未来汽车发展的主要方向和核心技术之一。随着车辆智能化水 平的提高&#xff0c;自动驾驶等级从无自动驾驶向完全自动驾驶提升。在自动驾驶的人机协同控制 和完全自动驾驶阶段&#xff0c;由于人类驾驶员在动态驾驶任务中的参与程度不同&#xff0c;…

23. 机盒通信

1. 概述 耳机和充电盒,采用机盒通信的方式,完成通信和充电。受限于耳机上的触电,机盒通信采用单线模式。 注:耳机只做应答,不主动发起通信 2. 硬件连接 由于bes2700外置SY5501的电源管理芯片,实际上串口通信,通过SY5501进行转发。 充电盒 -> SY5501 -> bes27…

windows安装boost后没有b2.exe(无法执行b2)

原因&#xff1a;如果你是在官网下载的.exe文件进行的boost安装&#xff0c;那么就不需要再执行bootstrap.bat&#xff0c;也因此不会有b2.exe 链接&#xff1a;官方网址下载 通过.exe安装boost并配置环境变量之后就可以直接使用了 如果你仍希望有b2.exe&#xff0c;那么双击执…

2024“钉耙编程”中国大学生算法设计超级联赛(9)hdu7529 树异或价值(树形dp+贪心)

题目 t(t<20)组h里&#xff0c;每次给定一棵n(n<2e5)个点的&#xff0c;点1为根的有根树&#xff0c; 定义树的价值为&#xff0c; 其中&#xff0c;&#xff0c;dep为深度&#xff0c;1号点的深度为0 而a数组待确定&#xff0c;对于所有的种方案&#xff0c;你要使树…

Leetcode每日刷题之118.杨辉三角

1.题目解析 杨辉三角作为一个经典的数学模型&#xff0c;其基本原理相信大家已经耳熟能详&#xff0c;这里主要是在学习了vector之后&#xff0c;对于本题有了新的解法&#xff0c;更加简便。关于vector的基本使用详见 面向对象程序设计(C)之 vector&#xff08;初阶&#xff0…

Selenium + Python 自动化测试17(数据驱动-文本操作)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 之前有一篇我们讨论了使用模块化测试来优化我们的测试脚本&#xff0c;今天我们试着进一步深入学习数据驱动。 本篇文章我们讨论一下数据驱动思想&#xff0c;如何将数据和脚本…

Zookeeper的在Ubuntu20.04上的集群部署

安装资源 官方安装包下载地址&#xff1a;https://zookeeper.apache.org/releases.html 懒得找版本的可以移步下载zookeeper3.84稳定版本&#xff1a; https://download.csdn.net/download/qq_43439214/89646735 安装方法 创建安装路径&&解压安装包 # 创建路径 m…

机器学习之ROC曲线

机器学习之ROC曲线 1.TPR与FPR计算2.TPR、FPR与分类阈值的关系3.生成ROC曲线4.AUC计算参考文献本博客主要参考了https://www.evidentlyai.com/classification-metrics/explain-roc-curve。 1.TPR与FPR计算 真阳率TPR(True Positive rate),又称召回率recall rate。 假阳率F…

AtCoder Beginner Contest 367(ABCDEF题)视频讲解

A - Shout Everyday Problem Statement In the Kingdom of AtCoder, residents are required to shout their love for takoyaki at A A A o’clock every day. Takahashi, who lives in the Kingdom of AtCoder, goes to bed at B B B o’clock and wakes up at C C C o’…

flink车联网项目前篇:项目设计(第64天)

系列文章目录 车联网项目设计 5.1 数仓分层 5.2 数仓主题数据建模数据仓库建模方法论 2.1 关系建模 2.1.1 ER模型 2.1.2 关系模式范式 文章目录 系列文章目录前言5. 车联网项目设计5.1 数仓分层5.2 数仓主题 1. 数据建模2. 数据仓库建模方法论2.1 关系建模2.1.1 ER模型2.1.2 关…

[Meachines] [Medium] TartarSauce Wordpress-gwolle-gb-RFI+tar权限提升+定时器备份文件权限提升

信息收集 IP AddressOpening Ports10.10.10.88TCP:80 $ nmap -p- 10.10.10.88 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 80/tcp open tcpwrappedWordpress & gwolle-gb & RFI $ feroxbuster --url http://10.10.10.88/ $ wpscan --url http://10.…

汽车IVI中控OS Linux driver开发实操(二十五):GPIO设备驱动的上手编写

概述: 1、验证GPIO是否有效。 2、如果有效,则可以从内核GPIO子系统请求GPIO。 3、将GPIO导出到sysfs(这是可选的)。 4、设置GPIO的方向 5、如果将GPIO设置为输出引脚,则将其设置为高/低。 6、设置去抖动间隔,如果将其设置为输入引脚,则读取状态。您还可以为边缘/级别触…

图像直方图计算

1. 图像直方图&#xff08;Image histogram&#xff09; 图像直方图&#xff0c;又叫影像直方图&#xff0c;是一种用来表现数位影像中像素分布的直方图&#xff0c;根据统计影像中不同亮度的像素总数&#xff0c;我们可以画出一张代表这张影像的影像直方图&#xff0c;透过这…

排序算法【快速排序】

一、快速排序算法原理 直接采用实际的例子解释原理&#xff0c;实际的数组如下图所示。 排序算法流程如下所示 然后按照上面顺序递归下去&#xff0c;直到排序完成推出。 二、算法代码 #include <stdio.h> #include "test.h"/* 快速排序算法 */ void quick_s…

Camera基础知识系列(1)——凸\凹透镜

目录 前言 一. 凸\凹透镜 1 凸透镜 1.1 凸透镜成像 1.2 物距\像距 1.3 凸透镜成像规律 2. 凹透镜 2.1 凹透镜成像规律 二. 相机 相机镜头 前言 平日里总是时不时地听到别人讲起一些摄影相关的术语&#xff0c;比如&#xff1a;光圈、焦距、等效焦距、EV、画幅、景深、快门…

使用Qdrant+FastText实现向量存储和检索

1 概述 在《使用FastText库训练词向量》一文中&#xff0c;已经训练并保存好了一个用 FastText 训练的词向量模型-model_0809_001。在接下来的实践中&#xff0c;将直接使用该词向量模型来生成对应数据的向量&#xff0c;然后将向量和对应的负载存储到 Qdrant 中&#xff0c;然…

基于Conda的Python版本管理与Python包管理

文章目录 前言Conda是什么Conda与Anaconda安装Anaconda安装包windows v2024.06-1安装包更多版本安装包(Windows/Mac/Linux) 安装 使用步骤创建Python环境激活Python环境安装Python包列出和切换 Python 版本管理多个环境 总结 前言 开发环境中&#xff0c;需要使用不同的Python…