[C++][数据结构]红黑树的介绍和模拟实现

前言

之前我们简单学习了一下搜索树和平衡搜索树,今天我们来学习一下红黑树

简介

概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色 ,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能有连续的红色节点)
  4. 对于每个结点 ,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点NIL节点),是为了方便第四点

实现

基本结构

我们默认新插入的节点为红色 ,因为根据第四条规则,黑色节点不能轻易添加

cpp 复制代码
template<class K, class V>
struct RBTreeNode
{
	using Node = RBTreeNode<K, V>;
	Node* _left;
	Node* _right;
	Node* _parent;
	pair<K, V> _kv;
	Color _col;

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

插入

插入节点最终的颜色的关键是看叔叔节点

  1. 原因:

    我的父节点是红的,那我的爷爷节点一定是黑的,并且爷爷一定存在(因为根节点不为红色),所以就是看叔叔节点的颜色来判断插入结点的颜色

  2. 修改原则:

    a. 叔叔存在且为红,则将父节点和叔叔节点变黑再将爷爷节点变红,

    • 更新cur节点为爷爷节点,直到更新到根节点

b. 叔叔为黑或叔叔不存在

  • 叔叔不存在,说明cur是新增的节点,直接就是红色
  • 叔叔为黑,那么cur不可能是新增,不然parent不满足第四条规则

针对第二点,我们需要考虑会不会最长路径超出最短路径的二倍或者面临下面这种情况(出现连续的红色节点且无法修改),那么这里我们就必须要进行旋转+变色。

(与AVL的旋转相同)
(好像有个小规律:parent始终为黑色)

旋转的规则:

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,

p为g的右孩子,cur为p的右孩子,则进行左单旋转

p变黑,g变红

c. cur为红,p为红,g为黑,u不存在/u为黑

更新规则:

  • p为g的左孩子,cur为p的右孩子:左右双旋+变色
  • p为g的右孩子,cur为p的左孩子:右左双旋+变色
cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	
	//1.按照搜索树规则插入:先找到合适的位置,然后链接
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}//如果树为空,特殊判断

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur != nullptr)
	{
		if (cur->kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);		//默认新节点是红色
	if (parent->kv.first > kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	//持续更新parent,直到更新到根节点
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		//根据父亲,找叔叔
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			//情况一:叔叔存在且为红
			if (uncle != nullptr && uncle->_col == RED)
			{
				//变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//更新节点
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二:叔叔不存在或者为黑
			else
			{
				if (cur == parent->_left)
				{
					//       g
					//    p    u
					// c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//       g
					//    p     u
					//      c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			if (uncle != nullptr && uncle->_col == RED)
			{
				//变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//更新节点
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//结构图
				//      g
				//   u     p
				//            c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//		g
					//   u     p
					//      c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;

	return true;
}

验证

要验证这棵树是不是红黑树,要根据他的规则去判断

  1. 根节点是黑色的
  2. 不能有连续的红色节点
  3. 对于每个结点 ,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

所以我们可以根据这三点去判断

cpp 复制代码
	bool Check(Node* cur)
	{
		if (cur == nullptr)
			return true;

		if (cur->_col == RED && cur->_parent->_col == RED)
		//第二点
		//根据孩子找父亲
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		return Check(cur->_left)
			&& Check(cur->_right);
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
			return false;
		//第一点

		return Check(_root);
	}

以上是对于第一点和第二点的验证,对于第三点,我们要求每两条路径的黑色节点数量,判断是否相等,

我们可以用老方法:增加参数个数

cpp 复制代码
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
			if (refBlackNum != blackNum)
			{
				throw("黑色节点的数量不相等");
			}
			return true;
		}

		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << endl;
			throw("存在连续的红色节点");
		}

		if (cur->_col == BLACK)
			++blackNum;
		
		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

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

		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col == BLACK)
				refBlackNum++;

			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

结语

红黑树的实现还没写完,下一篇文章会介绍红黑树的迭代器的简单实现和map和set是如何封装的 ,下次见~

相关推荐
weixin_4327022613 分钟前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
y52364819 分钟前
Javascript监控元素样式变化
开发语言·javascript·ecmascript
IT技术分享社区1 小时前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
极客代码1 小时前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow
疯一样的码农1 小时前
Python 正则表达式(RegEx)
开发语言·python·正则表达式
passer__jw7671 小时前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
&岁月不待人&1 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
StayInLove1 小时前
G1垃圾回收器日志详解
java·开发语言
无尽的大道2 小时前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒2 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法