破除高大上的迷雾

       可不要被名号吓着了!红黑树其实就是:二叉搜索树+四条红黑树规则的特殊二叉搜索树

        红黑树四大规则
                1.红黑树的每一个节点都有且仅有两种颜色:红色与黑色

                2.红黑树的根节点一定为黑色

                3.对于一个红色节点,其左右孩子一定为黑色

                4.对于任意一个节点来说,从这个节点出发到空节点的有所路径中,黑色节点的数量是一定相同的

基础~ 红黑树的结构

        对于规则1,我们可以使用enum枚举红和黑,这样会更加便捷、并且不会再颜色分配上出错。然后对于节点类RBTreeNode的实现,我们还是默认使用pair结构实现。同时因为我们会用到找父亲节点的情况,所以我们这里与avl树一样都为三叉链

        这里我们最重要的一个成员变量为_col ,在初始化的时候注意要初始化为 红(RED)具体原因我们后面解释

// 枚举值表⽰颜⾊ 
enum Colour
{
	RED,
	BLACK,
};

// 这里我们默认按key/value结构实现 
template<class K, class V>
struct RBTreeNode
{
	// 这⾥更新控制平衡也要加⼊parent指针 
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)//const?
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_col(RED)
	{}
};

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

private:
    Node* _root = nullptr;
}

重点~红黑树的插入

         红黑树的插入主要分为两大步骤:插入、调整

        插入

        红黑树的插入的本质与搜索二叉树的插入相同:

        1.若树为空,插入的数据为根节点的数据

        2.若树不为空,按照二叉搜索树的性质,判断节点的值与插入值的大小关系。若大于节点的值则往右边走。若小于节点的值则往左边走

(抱有疑问的同学可以参考:二叉搜索树的实现(C++)-CSDN博客

        插入颜色:对于插入节点的颜色,我们有红与黑两个选择。如果我们插入黑,就会违反规则4导致一条路径的黑色节点数量增加,而这种情况我们是及难维护的(所以这就是为什么我们一开始就说要初始化为红)。所以我们选择插入红,如果父亲节点为黑则顺利完成插入,如果父亲节点为红,则违反规则3,我们插入之后进行调整(特殊:当然如果插入的节点为根节点,我们则插入黑色节点,因为遵循规则2)

bool Insert(const pair<K, V>& kv)
{
	Node* cur = _root;
	Node* parent = cur;

	if (!cur)//如果是空树
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}

	while(cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = parent->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = parent->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (parent->_kv.first > kv.first)
		parent->_left = cur;
	else
		parent->_right = cur;

	//连接至上一层
	cur->_parent = parent;

    return true;
}

        由于这里我们是三叉链,所以与二叉搜索树不同的处理是,我们最后一步要将插入的节点的parent链接至上一层

     调整

        在插入节点后判断是否违反了规则3,若违反了则我们来到了这里,进行调整。

        (插入节点:cur,简称c    cur的父亲:parent,简称p    parent的父亲:pparent,简称pp   parent的兄弟:uncle,简称 u)

        变色

        当uncle存在且为红时,我们只需要进行变色操作,就可以让其再次符合规则:

        首先,parent是一定要变为黑色,不然无法满足规则3。变为黑色后,不满足规则4,我们直接将pp变为红,u变为黑。这样就完全符合所有规则了

        现在可能就有同学问了:为什么不将c变为黑呢?首先c之所以是红,是因为我们在一开始说的一来就插入黑色,我们是极其不好维护的,现在又将c变回去,那岂不是走老路了吗?所有c是不能变的,只能让parent变

        总结:当u存在且为红时,p、u变黑,pp变黑
        后续处理:此时pp为红,我们需要判断一下pp是否为这颗树的根。若pp此时正好是根,那么将pp再次赋值为黑(满足规则2)结束调整。如果不为空,判断pp的父亲节点是否为红,若不为红结束调整。如果为红则继续向上调整

        变色+单旋

        当uncle不存在时:

        右单旋:让pp变成p的右子树

        左单旋:让pp变成p的左子树

        这以下情况通过一次旋转+变色,就可以使其再次满足规则,而其他情况则需要双旋+变色

            当uncle存在且为黑时: 

       大家看这个图的时候可能有点疑惑。怎么感觉最后结果每个路劲的黑色节点数也没有保持一样呢?

        这里需要我们考虑一个小细节:那就是cur节点是怎么来的?我先给出结论:cur这个节点一定是之前已经存在且为黑的节点。原因:如果cur是之后插入进来的节点,那么这棵树一开始黑色节点的数量就不一致,这是不可能的。所以cur是之前就存在的节点,既然存在,那么要想使得黑色节点的数量保持一致,那cur之前只能是黑色的。

        反过来思考,当uncle不存在时。cur就只能是之后插入进来的

        所以cur是之前存在且为黑的,那它现在变成了红,也就意味着发生了我们最开始讲的变色情况:cur的左右孩子变为黑色,cur变为红色。所以这里的结果看似不符合规则,其实是符合的

        

         一样的,与上一个同理

        总结: 

        先旋转在将p变为黑,pp变为红,使其满足规则 。这样调整过后p变成了新的”根”节点,且为黑色,而黑色节点不论p的上一层是黑色还是红色都符合规则。所以旋转+变色后就结束调整。(在旋转过程中,因为这里是三叉链要注意_parent指针的更新,详细可参考具体的实现代码)

        变色+双旋

         当uncle不存在时:

        以下的四种情况仅仅依靠一次旋转是无法解决的,我们这里使用了两次旋转:左单选和右单选(简称双旋)

        当uncle存在且为黑时:

                这里和单旋是情况一样:cur的之前就已经存在且为黑的点,这里cue变成了红色,而cur的左右孩子变成了黑色。看起来黑色节点是数量不一致,其实是一致的

        与上同理 

        总结:

        先p点旋转,再pp点旋转,最后cue变黑色 pp变红色。这样调整过后cur变成了新的”根”节点,且为黑色,而黑色节点不论cur的上一层是黑色还是红色都符合规则。所以旋转+变色后就结束调整。(在旋转过程中,因为这里是三叉链要注意_parent指针的更新,详细可参考具体的实现代码)

        调整的代码实现
//判断是否满足条件
while (parent&&parent->_col == RED)
{
	Node* pparent = parent->_parent;

	//先讨论parent在左边的情况
	if (pparent->_left == parent)
	{
		Node* uncle = pparent->_right;
		//uncle存在且为红 -> 变色
		if (uncle && uncle->_col == RED)
		{
			parent->_col = uncle->_col = BLACK;
			pparent->_col = RED;

			cur = pparent;
			parent = cur->_parent;
		}
		else
		{
			//单旋+变色
			if (parent->_left == cur)
			{
				RotateR(pparent);
				pparent->_col = RED;
				parent->_col = BLACK;
				break;
			}
			//双旋+变色
			else
			{
				RotateL(parent);
				RotateR(pparent);
				pparent->_col = RED;
				cur->_col = BLACK;
				break;
			}
		}
	}
	else//再讨论parent在右边的情况
	{
		Node* uncle = pparent->_left;
		//uncle存在且为红 -> 变色
		if (uncle && uncle->_col == RED)
		{
			parent->_col = uncle->_col = BLACK;
			pparent->_col = RED;

			cur = pparent;
			parent = cur->_parent;
		}
		else
		{
			//单旋+变色
			if (parent->_right == cur)
			{
				RotateL(pparent);
				parent->_col = BLACK;
				pparent->_col = RED;
				break;
			}
			else//双旋+变色
			{
				RotateR(parent);
				RotateL(pparent);
				cur->_col = BLACK;
				pparent->_col = RED;
				break;
			}
		}
	}
	_root->_col = BLACK;
}
//右单旋
void RotateR(Node* parent)
{
	Node* pparent = parent->_parent;
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

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

//左单旋
void RotateL(Node* parent)
{
	Node* pparent = parent->_parent;
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	subR->_left = parent;
	parent->_parent = subR;

	if(!pparent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subR;
			subR->_parent = pparent;
		}
		else
		{
			pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
}

对于红黑树验证

       1. 规则1:枚举颜色类型,天然实现保证了颜色不是黑色就是红色。

        2. 规则2:直接检查根即可。

        3. 规则3:前序遍历检查,遇到红色结点查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲的颜色就方便多了。

        4. 规则4:前序遍历,遍历过程中用形参记录跟到当前结点的blackNum(黑色结点数量),前序遍历遇到黑色结点就++blackNum,走到空就计算出了一条路径的黑色结点数量。再任意一条路径黑色结点数量作为参考值,依次比较即可。

//检查红黑树是否满足要求
bool Check(Node* root, int blacknum,int refNum)
{
	if (!root)
	{
		if (blacknum != refNum)
		{
			cout << "存在黑色节点不一致的路径";
			return false;
		}
		return true;
	}

	int num = blacknum;
	//检查根
	if (root == _root && _root->_col != BLACK)
		return false;
	
	//前序遍历验证:黑色节点数以及红色节点的孩子
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "出现连续红色节点";
		return false;
	}
	if (root->_col == BLACK)
	{
		num++;
	}

	return Check(root->_left, num,refNum) && Check(root->_right, num, refNum);
}

bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == RED)
		return false;

	// 参考值 
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refNum;
		}
		cur = cur->_left;
	}
	return Check(_root, 0, refNum);
}

红黑树代码实现

//红黑树:二叉搜索树+4条红黑树规则
// 1.红黑树所有节点有且只有两种颜色:红、黑
// 2.红黑树的根必须为黑色
// 3.红色节点的孩子必须为黑色
// 4.对于任意一个节点,从这个节点开始到NULL节点的黑色节点数量都是一样的


// 枚举值表⽰颜⾊ 
enum Colour
{
	RED,
	BLACK,
};

// 这里我们默认按key/value结构实现 
template<class K, class V>
struct RBTreeNode
{
	// 这⾥更新控制平衡也要加⼊parent指针 
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)//const?
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_col(RED)
	{}
};

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

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}


	bool Insert(const pair<K, V>& kv)
	{
		Node* cur = _root;
		Node* parent = cur;

		if (!cur)//如果是空树
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		while(cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = parent->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = parent->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;

		//连接至上一层
		cur->_parent = parent;


		//判断是否满足条件
		while (parent&&parent->_col == RED)
		{
			Node* pparent = parent->_parent;

			//先讨论parent在左边的情况
			if (pparent->_left == parent)
			{
				Node* uncle = pparent->_right;
				//uncle存在且为红 -> 变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					pparent->_col = RED;

					cur = pparent;
					parent = cur->_parent;
				}
				else
				{
					//单旋+变色
					if (parent->_left == cur)
					{
						RotateR(pparent);
						pparent->_col = RED;
						parent->_col = BLACK;
						break;
					}
					//双旋+变色
					else
					{
						RotateL(parent);
						RotateR(pparent);
						pparent->_col = RED;
						cur->_col = BLACK;
						break;
					}
				}
			}
			else//再讨论parent在右边的情况
			{
				Node* uncle = pparent->_left;
				//uncle存在且为红 -> 变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					pparent->_col = RED;

					cur = pparent;
					parent = cur->_parent;
				}
				else
				{
					//单旋+变色
					if (parent->_right == cur)
					{
						RotateL(pparent);
						parent->_col = BLACK;
						pparent->_col = RED;
						break;
					}
					else//双旋+变色
					{
						RotateR(parent);
						RotateL(pparent);
						cur->_col = BLACK;
						pparent->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;
		}
		return true;
	}

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

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

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

	//左单旋
	void RotateL(Node* parent)
	{
		Node* pparent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if(!pparent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
				subR->_parent = pparent;
			}
			else
			{
				pparent->_right = subR;
				subR->_parent = pparent;
			}
		}
	}

	//检查红黑树是否满足要求
	bool Check(Node* root, int blacknum,int refNum)
	{
		if (!root)
		{
			if (blacknum != refNum)
			{
				cout << "存在黑色节点不一致的路径";
				return false;
			}
			return true;
		}

		int num = blacknum;
		//检查根
		if (root == _root && _root->_col != BLACK)
			return false;
		
		//前序遍历验证:黑色节点数以及红色节点的孩子
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "出现连续红色节点";
			return false;
		}
		if (root->_col == BLACK)
		{
			num++;
		}

		return Check(root->_left, num,refNum) && Check(root->_right, num, refNum);
	}

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col == RED)
			return false;

		// 参考值 
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}
			cur = cur->_left;
		}
		return Check(_root, 0, refNum);
	}


private:
	

	Node* _root = nullptr;
};

以上结束本文的内容了,感谢大家,我们下期再见!如有问题还请各位大佬斧正 

相关内容:让我来告诉你如何实现AVL树(C++)-CSDN博客 

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐