【C++】红黑树简单实现

目录

一、红黑树的性质

1、什么是红黑树

二、红黑树节点的定义

三、红黑树的插入

1、cur为红,parent为红,grandfather为黑,uncle存在且为红

2、cur为红,parent为红,grandfather为黑,uncle不存在或者存在且为黑(单旋+变色)

3、cur为红,parent为红,grandfather为黑,uncle不存在或者存在且为黑(双旋+变色)

四、红黑树的验证

总结


一、红黑树的性质

1、什么是红黑树

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

2、红黑树的性质

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

红黑树的规则保证了,最长路径中的节点个数不会超过最短路径的2倍

极端情况下:

最短路径全是黑色节点

最长路径一黑一红交替出现


我们只要保证了红黑树的规则就保证了,最长路径中的节点个数不超过最短路径节点个数的2倍

二、红黑树节点的定义

enum Colour
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<const K, V> _kv;
	Colour _col;

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

 

三、红黑树的插入

 

假如我们要插入28这个节点,我们一定要插入到27的右边,那么我们新插入的节点的颜色设定为什么呢?

如果设定为红色那么就是与 规则3相悖,如果设定为黑色就是与规则4相悖

既然无论如何都要相悖,那么我们选择与规则3相悖,因为如果是在11,15后面插入时,直接插入红色节点,就不用再处理其它节点了

如果插入黑色节点,我们就要遍历整棵树来调整黑色节点的个数

    bool insert(const pair<const K, V> kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

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

		//调整节点颜色

        return true;
	}

1、cur为红,parent为红,grandfather为黑,uncle存在且为红

这是最简单的情况了

 

我们让parent和uncle变成黑色,grandfather变为红色,如果grandfather是根就再将其变为黑色

 

 

2、cur为红,parent为红,grandfather为黑,uncle不存在或者存在且为黑(单旋+变色)

 

3、cur为红,parent为红,grandfather为黑,uncle不存在或者存在且为黑(双旋+变色)

 

	bool insert(const pair<const K, V> kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

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

		//调整节点颜色
		//走到空或者父亲的颜色是黑色,就停止更新
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);

			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					/*    g
					*   p
					* c
					*/
					if (cur == parent->_left)
					{
						RotateR(parent);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					/*    g
					*   p    u
					*     c
					*/
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					/*    g
					*         p
					*            c
					*/
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					/*    g
					*   u    p
					*      c
					*/
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}


	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* ppNode = parent->_parent;//记录parent的parent,旋转完成之后链接
		subL->_right = parent;
		parent->_parent = subL;
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
	}

四、红黑树的验证

红黑树的验证主要是根据它的规则来验证,不能使用最长路径是最短路径的二倍

因为最短路径是最长路径的二倍的树不一定是红黑树

所以我们要使用它的条件来判断

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

第一点因为使用的是枚举类型,所以很容易保证

第二点只要验证根不为红色就可以

第三点是前序遍历,判断是否具有连续的红色节点

第四点是使用一个计数器,随便记录一条路的黑色节点个数,然后对比

	bool IsBalance()
	{
		int benchmark = 0;
		return _PrevCheck(_root, 0, benchmark);
	}

	bool _PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "黑色节点数不同" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (root->_col == BLACK)
		{
			blackNum++;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续的红色节点" << endl;
			return false;
		}

		return _PrevCheck(root->_left, blackNum, benchmark)
			&& _PrevCheck(root->_right, blackNum, benchmark);
	}


总结


以上就是今天要讲的内容,本文仅仅简单介绍了红黑树的基本结构和插入操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值