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

相关推荐
c4fx17 分钟前
Delphi5利用DLL实现窗体的重用
开发语言·delphi·dll
秋夫人23 分钟前
B+树(B+TREE)索引
数据结构·算法
鸽芷咕40 分钟前
【Python报错已解决】ModuleNotFoundError: No module named ‘paddle‘
开发语言·python·机器学习·bug·paddle
Jhxbdks1 小时前
C语言中的一些小知识(二)
c语言·开发语言·笔记
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
代码雕刻家1 小时前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
梦想科研社1 小时前
【无人机设计与控制】四旋翼无人机俯仰姿态保持模糊PID控制(带说明报告)
开发语言·算法·数学建模·matlab·无人机
风等雨归期1 小时前
【python】【绘制小程序】动态爱心绘制
开发语言·python·小程序