[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是如何封装的 ,下次见~

相关推荐
Buleall5 分钟前
期末考学C
java·开发语言
重生之绝世牛码7 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行13 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
Algorithm157623 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
用户00993831430128 分钟前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明32 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
Monly2139 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu40 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202140 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
小俊俊的博客41 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机