红黑树的模拟实现

一.红黑树的概念与规则

红黑树是一棵满二叉树,他的每个结点相较于满二叉树多增加一个储存位来表示结点的颜色,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,确保没有一条路径会比其他路径长处两倍,因此是接近平衡的。

在红黑树下,每个结点不是红色就是黑色,其中根结点一定为黑色。在同一条路径中不会有连续的红色结点。对于任意的一个结点,从该结点到所有null结点的简单路径上均包含数量相同的黑色结点。

那么红黑树是如何保证其规则的实现的呢?

由上述规则可知,红黑树没条分支上的黑色结点数量都是一致的,如果我们将新添加进来的结点颜色设置为黑色,那么就要使得其他分支的结点数都要增加一个,这样处理起来难度很高;倘若我们将进入的结点设置为红色,只需要使得同一条路径下不连续出现两个红色结点即可,所以我们统一将插入的结点设置为红色。

二.红黑树的实现

1.红黑树的结构

红黑树的结构由枚举变量红色与黑色,存入结点的键值,左右父母结点构成。

cpp 复制代码
enum Colour
{
	RED,
    BLACK
};

template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;

	RBTreeNode(const T& data)
		:_data(data)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};
template<class K,class T,class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
private:
	Node* _root = nullptr;
};

2.红黑树的插入

当我们插入一个数时,首先他要满足二叉树的规则,我们就按照小值在左大值在右安排,找到合适的空结点位置存放。第二步开始考虑红黑树的规则,若树为空,直接插入黑色结点,若不为空,由上述可知,我们的红黑树插入的结点需为红色结点,因为若为黑色结点,就会破坏了所有分支的平衡性,而插入红色结点只会破坏当前分支的平衡性,所以我们选择插入红色结点。

当我们插入了红色结点后,我们要考虑如何使得树达到平衡。我们将当前结点标识为cur,c的父亲为parent,parent的父亲为grandparent,parent的兄弟为uncle。

若此时parent为红,cur为红,那么grandparent一定为黑,uncle可能为黑或者空。此时我们就需要进行分类讨论进行相应的旋转和变色处理。

下面我们一步一步进行

首先根据二叉树规则插入结点

cpp 复制代码
if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		cur->_col = RED;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

遍历整棵树,若data值相较于当前结点大就遍历到右边,小就遍历到左树,直到结点为空时停止。然后再找到cur结点的parent结点就完成满足了二叉树规则。

第二步我们要对红黑树进行变色和旋转处理,主要分为6种情况

其中parent在grandparents左与parent在 grandparents在右逻辑一致,所以这里我们只对一边进行讲解。

1.叔叔结点存在且为红

我们用白色来代表黑色结点,此时的G为grandparents结点且为黑。

此时我们只需要将P和U结点颜色变为黑色,G结点变为红色,即可满足当前子树的平衡。然后再将cur交给G进行下一次判断即可。

2.叔叔不存在或存在且为黑

(1)cur在parent的左边

由于此时两条路径上红色结点的数量相差2,所以我们要进行旋转操作。面对当前情况,我们可以对grandparents进行右旋操作,使parent结点为根。

(2)cur在parent的右边

此时两个红色结点不处于同一直线上,所第一步我们先对parent进行左旋操作,使得与grandparents和cur在同一直线,再对grandparents进行右旋操作,使红色结点变为根结点,最后进行变色处理即可,下面放上变化图

对parent结点左旋操作后

对grandparents结点右旋操作后

由于剩余的三种情况与上述三种情况类似,只是旋转方向相反,所以不在此进行讲解。

下面放上代码:

cpp 复制代码
bool insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		cur->_col = RED;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//变色旋转
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				//         g
				//      p     u
				//   c
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//叔叔存在且为红
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else //叔叔不存在或为黑
				{
					if (cur == parent->_left)//cur在左
					{
						// 调整前
						//       g(黑)
						//    p(红) u(黑或空)
						//  c(红)
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						// 调整后
						//      p(黑)
						//   c(红)   g(红)
						//               u(黑或空)
					}
					else//cur在右
					{
						// 调整前
						//           g(黑)
						//        p(红)  u(黑或空)
						//           c(红)
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
						//	调整后
						//          c(黑)
						//       p(红)    g(红)
						//                  u(黑或空)
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					grandfather->_col = RED;
					parent->_col = BLACK;
					uncle->_col = BLACK;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;

		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;
		parent->_parent = subL;

		//if (parentParent == nullptr)
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}
	}

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

3.红黑树的平衡检查

我们用递归计算红黑树的黑色结点数量,在各条路径进行判断即可。

cpp 复制代码
	bool check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			if (refNum != blackNum)
			{
				cout << "存在黑色结点数量不相同的路径" << endl;
				return false;
			}
			return true;
		}

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

		if (root->_col == blackNum)
		{
			blackNum++;
		}

		return check(root->_left, blackNum, refNum)
			&& check(root->_right, blackNum, refNum);
	}

	bool IsBalanceTree()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;

		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}
			cur = cur->_left;
		}
		return check(_root, 0, refNum);
	}

三.总结

红黑树相较于前文的AVL树相比而言,时间复杂度都是logn级别的,红黑树对平衡条件管理更加宽松,使得其调整的次数大大减少,通过红黑规则的巧妙布局,保持高效的操作性的同时也降低了平衡维护的复杂度,适用场景广。

相关推荐
君义_noip1 小时前
信息学奥赛一本通 1514:【例 2】最大半连通子图 | 洛谷 P2272 [ZJOI2007] 最大半连通子图
c++·图论·信息学奥赛
努力学习的小廉1 小时前
【C++】 —— 笔试刷题day_7
开发语言·c++
wuqingshun3141591 小时前
蓝桥杯 整数变换
数据结构·c++·算法·职场和发展·蓝桥杯
禁默2 小时前
C++11之深度理解lambda表达式
开发语言·c++
wen__xvn2 小时前
每日一题洛谷P8717 [蓝桥杯 2020 省 AB2] 成绩分析c++
c++·算法·蓝桥杯
Tadecanlan2 小时前
[C++面试] 你了解transform吗?
开发语言·c++
f狐0狸x2 小时前
【蓝桥杯每日一题】3.25
开发语言·数据结构·c++·算法·蓝桥杯
敲上瘾2 小时前
定长内存池原理及实现
c++·缓存·aigc·池化技术
扫地的小何尚2 小时前
NVIDIA TensorRT 深度学习推理加速引擎详解
c++·人工智能·深度学习·gpu·nvidia·cuda
UestcXiye5 小时前
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
c++·计算机网络·ip·tcp