【C++】红黑树及其实现

目录

一、红黑树的定义

1.为什么提出红黑树?

AVL树和红黑树的插入、删除和查找操作的时间复杂度都是 l o g ( n ) log(n) log(n),既然如此,为什么会提出红黑树这一概念呢?

AVL树在插入和删除过程中,为了保持平衡性,会非常频繁地调整全树整体的拓扑结构,代价较大,为此在AVL树的平衡标准上进一步放宽条件,引入了红黑树的结构。

2.红黑树的概念

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

3.红黑树的性质

一棵红黑树是满足如下红黑性质的二叉排序树:

  1. 每个结点或是红色,或是黑色的。
  2. 根结点是黑色的。
  3. 叶结点(这里指的是空结点)都是黑色的。
  4. 不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色的)。
  5. 对每个结点,从该结点到任意一个叶结点的简单路径上,所含黑结点的数量相同。

对于这样的红黑树,有两个结论:

  1. 从根到叶结点的最长路径不大于最短路径的2倍。
  2. 有n个内部结点的红黑树的高度 h < = 2 l o g 2 ( n + 1 ) h<=2log_2(n+1) h<=2log2(n+1)。

二、红黑树的实现

1.红黑树的结构

红黑树的结构就是在二叉排序树的基础上加上了一个表示颜色的变量,我们可以使用枚举来实现。

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

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

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

2.红黑树的插入

红黑树的插入操作也需要先按照二叉排序树的插入方式插入结点,然后根据其是否破坏了红黑树的性质来进行调整。

当我们插入一个新结点时,其默认颜色是红色,不过当这个新结点为根时,则要把它改成黑色。

红黑树的插入调整共分为三种情况,想要知道该选用哪种方式,要看的是叔叔结点的位置和颜色,因此我们想要进行调整,必须准备好四个结点:cur(当前结点)、parent(父结点)、grandfather(爷结点)和uncle(叔叔结点)。

2.1 uncle为红色

当叔叔结点为红色时,不需要旋转操作即可完成调整,先将parent和uncle变为黑色,再将grandfather变为红色即可,如果grandfather是根结点,则调整后要将其改成黑色,如果其父结点是红色则需要继续向上调整。

2.2 uncle为黑色,且是grandfather的右孩子

这里的uncle可以为空,因为叶结点就是黑色的。

此时若cur在parent的左侧,则可以进行右旋,右旋后将parent和grandfather的颜色进行交换。

若cur载parent的右侧,需要进行两次旋转,先对parent进行左旋,此时即可看作是cur在parent的左侧了,再对grandfather进行右旋,再将cur改为黑色,parent和grandfather改为红色即可。

2.3 uncle为黑色,且是grandfather的左孩子

这种情况则和上一种情况相对称,cur在parent的右侧,则可以进行左旋,左旋后将parent和grandfather的颜色进行交换。

若cur载parent的左侧,需要进行两次旋转,先对parent进行右旋,此时即可看作是cur在parent的右侧了,再对grandfather进行左旋,再将cur改为黑色,parent和grandfather改为红色即可。

以下是插入的代码实现:

cpp 复制代码
bool Insert(const T& data)
{
	// 先按照二叉排序树的方式进行排序
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (data < cur->_data)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(data);
	if (data < parent->_data)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	// 开始调整
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		// 当叔叔在爷结点的右侧
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 当叔叔为红色
			if (uncle && uncle->_col == RED)
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			// 当叔叔为黑色
			else
			{
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					parent->_col = RED;
					grandfather->_col = RED;
				}
				break;
			}
		}
		// 当叔叔在爷结点的左侧
		else
		{
			Node* uncle = grandfather->_left;
			// 当叔叔为红色
			if (uncle && uncle->_col == RED)
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandfather->_col = RED;
				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;
					parent->_col = RED;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
	return true;
}

3.红黑树的验证

由于红黑树的删除过于复杂,这里就不再谈了。

关于红黑树的验证,我们需要从红黑树的性质入手,查看当前红黑树是否违背了性质。性质1、3不必检查,剩下的就是2、4和5了。

关于性质2,只需要简单一条if语句即可判定。

对于性质4,我们可以对红黑树进行遍历,如果父结点和当前结点同时为红色,则可以返回false。

性质5想要判断有一点麻烦了,我们可以使用一个mark来记录某一条路径上有多少黑色结点,然后递归每一条路径,来判断是否有路径上黑色结点总值与mark不同。

以下是具体实现:

cpp 复制代码
bool Isbalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根结点为红色" << endl;
		return false;
	}
	int benchmark = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++benchmark;
		}
		cur = cur->_left;
	}
	return _Check(_root, 0, benchmark);
}

bool _Check(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "某条路径黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blacknum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红色节点" << endl;
			return false;
		}
		return _Check(root->_left, blacknum, benchmark)
			&& _Check(root->_right, blacknum, benchmark);
	}

4.红黑树的查找效率分析

我们先引入一个概念,
黑高 :从某结点出发(不含该结点)到达一个叶结点的任意一个简单路径上的黑结点总数称为该结点的黑高(记为bh),黑高的概念是由性质5确定的。根结点的黑高称为红黑树的黑高。

比如下图中13的bh=2,15的bh=1。

由红黑树的性质可知,从根到任意一个叶结点的简单路径最短时,就是这条路径上全是黑结点。某条路径最长时,这条路径必然是由黑结点和红结点相间构成的。这样也就引出了结论1:从根到叶结点的最长路径不大于最短路径的两倍。这样就能够知道根的黑高至少为h/2,于是就有 n > = 2 h / 2 − 1 n>=2^{h/2}-1 n>=2h/2−1可以证得结论2。

可见红黑树的"适度平衡",由AVL树的"高度平衡",降低到"人体一个结点左右子树的高度,相差不会超过2倍",也降低了动态操作时调整的频率。对于一颗动态查找树,若插入和删除操作比较少,查找操作比较多,则采用AVL树比较合适,否则采用红黑树比较合适。

但由于维护这种高度平衡所付出的代价比获得的收益大得多,红黑树的实际应用更广泛,比如C++中的map和set、Java中的TreeMap和TreeSet就是用红黑树实现的。

5.整体代码

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

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

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
	}
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data < key)
			{
				cur = cur->_right;
			}
			else if (cur->_data > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool Insert(const T& data)
	{
		// 先按照二叉排序树的方式进行排序
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);
		if (data < parent->_data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			// 当叔叔在爷结点的右侧
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 当叔叔为红色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				// 当叔叔为黑色
				else
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
			// 当叔叔在爷结点的左侧
			else
			{
				Node* uncle = grandfather->_left;
				// 当叔叔为红色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					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;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}
	bool Isbalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根结点为红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}
	int Height()
	{
		return _Height(_root);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
protected:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}
	bool _Check(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "某条路径黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blacknum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红色节点" << endl;
			return false;
		}
		return _Check(root->_left, blacknum, benchmark)
			&& _Check(root->_right, blacknum, benchmark);
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		subR->_left = parent;
		parent->_right = subRL;
		Node* ppnode = parent->_parent;		// 记录parent的父结点

		parent->_parent = subR;

		if (subRL)
		{
			subRL->_parent = parent;
		}
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}
	void RotateR(Node* parent)
	{
		// subL:parent的左孩子
		// subLR:parent的左孩子的右孩子,注意:该点可能不存在
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		subL->_right = parent;
		parent->_left = subLR;
		Node* ppnode = parent->_parent;		// 记录parent的父结点,用于连接新的子树

		parent->_parent = subL;

		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
private:
	Node* _root = nullptr;
};

void RBTreeTest1()
{
	// int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int> t1;
	for (auto e : a)
	{
		t1.Insert(e);
	}
	t1.InOrder();
	cout << t1.Isbalance() << endl;
}

void RBTreeTest2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(x);
	}
	cout << t.Isbalance() << endl;
	cout << t.Height() << endl;
}
相关推荐
cdut_suye几秒前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
南城花随雪。8 分钟前
单片机:实现FFT快速傅里叶变换算法(附带源码)
单片机·嵌入式硬件·算法
dundunmm23 分钟前
机器学习之scikit-learn(简称 sklearn)
python·算法·机器学习·scikit-learn·sklearn·分类算法
古希腊掌管学习的神24 分钟前
[机器学习]sklearn入门指南(1)
人工智能·python·算法·机器学习·sklearn
波音彬要多做25 分钟前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
捕鲸叉25 分钟前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
Noah_aa35 分钟前
代码随想录算法训练营第五十六天 | 图 | 拓扑排序(BFS)
数据结构
KpLn_HJL1 小时前
leetcode - 2139. Minimum Moves to Reach Target Score
java·数据结构·leetcode
只做开心事1 小时前
C++之红黑树模拟实现
开发语言·c++
程序员老冯头3 小时前
第十五章 C++ 数组
开发语言·c++·算法