【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。

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),每个节点记录一个值:根到当前节点路径中黑色节点的数量,递归时的形参就可以解决这个问题。我们可以任意计算一条路径作为参考值,比如,最左路径。

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树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

抱歉,我是一名语言模型AI,无法为您提供演示实时代码功能。但是,我可以向您解释红黑树的基本原理和操作。红黑树是一种自平衡二叉搜索树,它的每个节点都有一个颜色属性,红色或黑色,具有以下特性: 1. 根节点为黑色。 2. 所有叶子节点(NIL节点)为黑色。 3. 如果一个节点为红色,则它的两个子节点必须都为黑色。 4. 从任意一个节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。 红黑树的基本操作包括插入、删除和查找。插入和删除操作会破坏红黑树的平衡性,需要通过旋转和重新着色来修复平衡。查找操作与二叉搜索树相同,具有O(log n)的时间复杂度。 以下是红黑树的插入操作的伪代码: 1. 新节点插入到红黑树中,颜色为红色。 2. 如果新节点的父节点为黑色,则不需要做任何操作,树仍然平衡。 3. 如果新节点的父节点为红色,需要进行以下操作: (1) 如果新节点的叔节点(父节点的兄弟节点)为红色,则将父节点和叔节点涂黑,祖父节点涂红,然后将当前节点指向祖父节点,重复步骤2。 (2) 如果新节点的叔节点为黑色,并且新节点是父节点的右子节点(父节点为祖父节点的左子节点),则将父节点左旋转,将当前节点指向父节点,重复步骤4。 (3) 如果新节点的叔节点为黑色,并且新节点是父节点的左子节点(父节点为祖父节点的右子节点),则将父节点右旋转,将当前节点指向父节点,重复步骤4。 4. 将父节点涂黑,祖父节点涂红,然后进行以下操作: (1) 如果当前节点是父节点的左子节点,将祖父节点右旋转。 (2) 如果当前节点是父节点的右子节点,将祖父节点左旋转。 以上是红黑树的基本操作,希望能够帮助您理解红黑树的原理。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值