【C++】红黑树

目录

红黑树的概念

红黑树的性质

红黑树节点的定义

红黑树的插入操作

红黑树的验证

红黑树与AVL树的比较


红黑树的概念

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

红黑树的性质

1.每个节点不是红色就是黑色

2.根节点是黑色的

3.如果一个节点是红色的,则它的两个孩子节点是黑色的(即不存在连续的红色节点)

4.对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点(每条路径都存在相同数量的黑色节点)

5.每个叶子节点都是黑色的(此处的叶子节点指的是空节点,NIL)

前4条规则就可以保证最长路径<=最短路径*2,因为最短的就是全是黑节点的路径,最长的就是一黑一红间隔的路径,在最极端的条件下仍能满足最长路径<=最短路径*2,我们不得不感慨,发明红黑树的前辈真是一个条件组合大师啊!

红黑树节点的定义

enum Color
{
	BLACK,
	RED
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
	pair<K, V> _kv;
	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _color(RED)
	{}

};

在RBTreeNode的构造函数中,我们默认节点为红色,这是为什么呢?

反过来想,如果我们默认节点是黑色,当插入这个节点后,会违反性质3,把其他所有路径都得罪了, 后果还是很严重的;如果默认节点是红色,可能会违反性质4,后果轻一些。

红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1.按照二叉搜索的树规则插入新节点

2. 新节点插入后,检测红黑树的性质是否造到破坏

由于新节点的默认颜色是红色,因此,如果其双亲节点是黑色,没有违反红黑树任何性质,则不需要调整;但是当新插入节点的双亲节点是红色,就会存在连续的红节点,违反性质3,此时需要分情况讨论:

为了方面描述,我们做一些约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

其实,有很多因素是确定的,cur是红色,p也是红色,g是黑色,唯一的变量是u的颜色以及u是否存在。

情况1:cur为红,p为红,g为黑,u存在且为红

注:a/b/c/d/e每条路径有x个黑色节点,x>=0。

cur和p均为红,违反了性质三,能否将p直接改为黑?

不可以,如果p直接改为黑,那么p所在路径将会导致违反性质4

g是否可以不变红?

不可以,因为g所在的树,可能是整棵树的子树,不变红,这棵子树路径黑色节点数量都+1,破坏规则4

解决方式:将p、u改为黑,g改为红,如果g不是根,然后g当做cur,继续向上调整;如果g是根,再把g变黑。

g当做cur,继续向上调整还要分情况:

1.g的父亲是黑色的,就结束了

2.g的父亲是红色的,还要继续处理
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

说明:u的情况有两种

1.如果u节点不存在,则cur一定是新插入节点,如果cur不是新插入节点,则cur和p一定有一个节点颜色是黑色,就不满足性质4。

2.如果u节点存在,则其一定是黑色的,那么cur原来颜色一定是黑色的,现在看到红色是因为cur的子树在调整过程中将cur节点的颜色由黑色改成红色。

解决方式:p为g的左孩子,cur为p的左孩子,则进行右单旋;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转;p、g变色--p变黑,g变红。

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反, p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,则转换成了情况2。

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_color = BLACK;
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		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);
	cur->_color = RED;
	if (cur->_kv.first < parent->_kv.first)
	{ 
		parent->_left = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_right = cur;
		cur->_parent = parent;
	}

	while (parent && parent->_color == RED)
	{
		Node* parent = cur->_parent;
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_color == RED)
			{
				//叔叔存在且为红
				parent->_color = BLACK;
				uncle->_color = BLACK;
				grandfather->_color = RED;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//叔叔不存在,或者存在且为黑
					
				if (cur == parent->_left)
				{
					//      g 
					//    p   u
					//  c
					//单旋
					RotateR(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				else
				{
					//      g 
					//    p   u
					//      c
					//左右双旋
					RotateL(parent);
					RotateR(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				break;


			}
				
		}
		else
		{
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_color == RED)
			{
				//叔叔存在且为红
				parent->_color = BLACK;
				uncle->_color = BLACK;
				grandfather->_color = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//叔叔不存在,或者存在且为黑
					
				if (cur == parent->_right)
				{
					//     g
					//   u   p
					//         c
					// 
					RotateL(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				else
				{
					//     g
					//   u   p
					//     c
					// 
					RotateR(parent);
					RotateL(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}

				break;

			}
			cur = grandfather;
		}
	}
	_root->_color = BLACK;

	return true;
}

红黑树的验证

为了验证我们建立的树是红黑树,可以依次检查红黑树的几条性质,如果全部符合,那么就可以认为这棵树是红黑树:

1.根节点是黑色节点:这条性质很容易检查。
2.如果一个节点是红色的,则它的两个孩子节点是黑色的(即不存在连续的红色节点):这条性质在检查时,有两种思路:

1)如果遇到红色节点,去检查它的孩子节点是不是红色,但是还要检查它的左右两个孩子,比较麻烦;

2)如果遇到红色节点,去检查它的父亲节点是不是红色,由于父亲节点是唯一的,这样检查比较简单。

所以我们采用方式2)检查这条性质。

3.对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点(每条路径都存在相同数量的黑色节点)

为了测试性质3是否成立,可以记录下来每条路径黑色节点的数量。为了求出每条路径黑色节点的数量,我们可以使用深度优先遍历(DFS),每个节点记录一个值:根到当前节点路径中黑色节点的数量,递归时的形参就可以解决这个问题。我们可以任意计算一条路径作为参考值,比如,最左路径。

cpp 复制代码
bool IsBalance()
{
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_color == BLACK)
		{
			refNum++;
		}
		cur = cur->_left;
	}
	return Check(_root,0, refNum);
}

private:
	bool Check(Node* root,int BlackNum,const int ref)
	{
		if (root == nullptr)
		{
			//cout << BlackNum << endl;
			if (BlackNum != ref)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
			

		if (root->_color == RED && root->_parent->_color == RED)
		{
			cout << root->_kv.first << "->及其父亲节点构成连续的红色" << endl;
			return false;
		}

		if (root->_color == BLACK)
		{
			BlackNum++;
		}

		return Check(root->_left, BlackNum,ref) && Check(root->_right, BlackNum,ref);
	}

*******如果在验证是否为红黑树时报错,可以通过下面的步骤检查:********

1.先看是插入谁导致出现的问题

2.打条件断点,画出插入前的树

3.单步跟踪,对比图,分析细节原因

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

相关推荐
兵哥工控3 分钟前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
我爱工作&工作love我10 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
娃娃丢没有坏心思40 分钟前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
lexusv8ls600h40 分钟前
探索 C++20:C++ 的新纪元
c++·c++20
lexusv8ls600h1 小时前
C++20 中最优雅的那个小特性 - Ranges
c++·c++20
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
依旧阳光的老码农2 小时前
标准C++ 字符串
开发语言·c++
白-胖-子2 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-成绩排序
c++·算法·蓝桥杯·真题·蓝桥等考
极地星光2 小时前
JSON-RPC-CXX深度解析:C++中的远程调用利器
c++·rpc·json