C++红黑树:理论与实践相结合的平衡艺术

目录

[1 红黑树的概念](#1 红黑树的概念)

[1.1 红黑树的4条规则](#1.1 红黑树的4条规则)

[1.2 红黑树是如何做到最长路径不超过最短路径的两倍的?](#1.2 红黑树是如何做到最长路径不超过最短路径的两倍的?)

[1.3 红黑树的效率](#1.3 红黑树的效率)

[2 红黑树的实现](#2 红黑树的实现)

[2.1 红黑树的结构](#2.1 红黑树的结构)

[2.2 红黑树的插入](#2.2 红黑树的插入)

[2.2.1 红黑树插入一个值的过程](#2.2.1 红黑树插入一个值的过程)

[2.2.2 情况1:变色](#2.2.2 情况1:变色)

[2.2.3 情况2:单旋+变色](#2.2.3 情况2:单旋+变色)

[2.2.4 情况3:双旋+变色](#2.2.4 情况3:双旋+变色)

[2.3 红黑树的插入代码实现](#2.3 红黑树的插入代码实现)

[2.4 红黑树的查找](#2.4 红黑树的查找)

[2.5 红黑树的验证](#2.5 红黑树的验证)


1 红黑树的概念

红黑树是一颗二叉搜索树,它的每个节点可以是红色或者黑色 。通过对任意一条从根节点到叶子节点的路径上各个节点的颜色进行约束。红黑树确保没有一条路径的长度会比其他路径的长度多出2倍。所以是接近平衡的。

1.1 红黑树的4条规则

  • 规则一:每个节点不是红色就是黑色
  • 规则二:根节点是黑色的
  • 规则三:如果一个节点是红色的,那么它两个孩子节点必须是黑色的,也就是说任意一条路径都不会有连续的红色节点
  • 规则四:对于任意一个节点,从该节点到其所有NULL节点的路径上,均包含相同数量的黑色节点

一些红黑树的样例:

1.2 红黑树是如何做到最长路径不超过最短路径的两倍的?

  • 由规则4可知,从根到NULL节点的每条路径都有相同的黑色节点,所以在一些场景下,最短路径是全是黑色节点的路径,假设最短路径长度是h
  • 由规则2和规则3可以知道,任何一条路径不会有连续的红色节点,所以极端场景下,最长路径就是一黑一红间隔组成,那么最长路径的长度就是2*h
  • 综合红黑树的4点规则,全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在。假设任意一条从根节点到NULL节点的路径长度是x,那么h<=x<=2*h

由此得出红黑树最长路径不超过最短路径的2倍

1.3 红黑树的效率

假设N是红黑树中节点的数量,h是最短路径的长度,那么2^h-1<=N<2^(2h)-1,由此得出h≈logN,也就意味着红黑树的增删查改的最长路径是2*logN,时间复杂度就是O(logN).

2 红黑树的实现

2.1 红黑树的结构

创建一个头文件RBTree.h,先搭建好框架,在public中实现操作:

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

//这里默认按照key/value结构实现
template<class K,class V>
struct RBTreeNode
{
	Colour _col;
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

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

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:

private:
	Node* _root = nullptr;
};

2.2 红黑树的插入

2.2.1 红黑树插入一个值的过程

1 插入一个值按照二叉搜索树的规则进行插入,插入后只需要观察是否符合红黑树的4条规则

2 如果是空树插入,新增节点是黑色节点,如果是非空树插入,新增节点必须是红色节点,因为非空树插入黑色节点会破坏规则4.

3 非空树插入后,新增节点必须为红色节点,如果父亲节点是黑色的,则满足所有规则,插入结束

4 非空树插入后,新增节点必须是红色节点,如果父亲节点是红色的,违反规则3。进一步分析,插入节点是红色,它的父亲节点为红,父亲节点的父亲节点必为黑,这三个颜色都是固定的,主要是看父亲节点的兄弟节点的变化情况,下面就根据情况分别处理

这里说明一下,下文中将新增节点标识为c(cur),c的父亲节点标识为p(parent),p的父亲节点标识为g(grandfather),p的兄弟节点标识(uncle)。

2.2.2 情况1:变色

这种情况是c为红,p为红,g为黑,u存在且为红。插入后将p和u变黑,g变红。再把g当作新的c,继续向上更新

说明:因为p和u都是红色,c是红色,不能有连续的红色节点,将p和u变黑,左边子树路径就各增加了一个黑色节点,g变红之后,相当于保持g所在子树的黑色节点的数量不变,同时解决了c和p连续红色节点的问题。需要继续向上更新是因为:g是红色,g的父亲节点可能还是红色,就需要继续处理,如果g父亲是黑色,则处理结束;如果g就是整棵树的根,再把g变成黑色。

这种情况只变色,不旋转,c是p的左还是右不影响。结合文字说明和图像便于理解:

2.2.3 情况2:单旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增节点,u存在且为黑,c不是新增,是c的子树新增后c变成了红色,更新上来的。

说明,p变黑才能解决,需要旋转加变色

如果p是g的左,c是p的左,那么就以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成这棵树的新的根,是黑色,这棵子树的黑色节点数量不变,没有连续的红色节点了,且不需要往上更新,p是黑色,p的父亲节点是黑色还是红色都不违反规则。

这里方框内都是子树,是抽象出来成方框,c是有子树插入节点更新上来的,变成红色。

如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要网上更新,因为p的父亲是黑色还是红色还是空都不违反规则。

2.2.4 情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增节点,u存在且为黑,c不是新增,是c的子树新增后c变成了红色,更新上来的。

说明,p变黑才能解决,需要旋转加变色

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变黑,g变红即可。c变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。

如果p是g的右,c是p的左,那么以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红即可。c变成这棵树新的根,这样子树黑色节点的数量不变,没有连续的红色节点了,且不需要往上更新,因为c的父亲是黑色还是红色还是空都不违反新规则。

2.3 红黑树的插入代码实现

了解完原理之后,我们就可以编写代码来实现插入操作了。

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

	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;
		if (parent == grandfather->_left)
		{
				//g
			//p		//u
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{
				//变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				//然后向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
							//g
						//p		//u
					//c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
							//g
						//p		//u
							//c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
				//g
			//u		//p
			Node* uncle = grandfather->_left;
			//叔叔节点存在且为红,变色即可
			if (uncle&&uncle->_col==RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//叔叔节点不存在,或者存在且为黑
			{
				if (cur == parent->_right)
				{
						//g
					//u		//p
								//c
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
						//g
					//u		//p
						//c
					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;
	if (subLR)
		subLR->_parent = parent;
	Node* pParent = parent->_parent;
	subL->_right = parent;
	parent->_parent = subL;
	if (pParent == nullptr)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pParent->_left == parent)
		{
			pParent->_left = subL;
		}
		else
		{
			pParent->_right = subL;
		}
		subL->_parent = pParent;
	}
}
//左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;
	Node* pParent = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;
	if (pParent == nullptr)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pParent->_left == parent)
		{
			pParent->_left = subR;
		}
		else
		{
			pParent->_right = subR;
		}
		subR->_parent = pParent;
	}
}

2.4 红黑树的查找

查找红黑树的节点比较简单,直接按照二叉搜索树的逻辑实现即可,搜索效率为O(logN)

cpp 复制代码
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < key)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > key)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

2.5 红黑树的验证

要验证一个数是否是红黑树,这里我们获取最长路径和最短路径,然后检查最长路径不超过最短路径的两倍是行不通的,原因是即使满足这个条件,红黑树也可能颜色不满足规则,所以还是去对四点规则进行一一检查,满足4点规则,一定能保证最长路径不超过最短路径的2倍。

  • 1 对于规则1保证红黑树节点不是黑色就是红色
  • 2 对于规则2直接检查根节点即可
  • 3 对于规则3前序遍历检查,遇到红色节点时,查它的孩子节点不太方便,因为孩子节点可能有两个,反过来检查父亲节点的颜色就方便许多
  • 4 规则4前序遍历,遍历过程中记录黑色节点数量,记录一条路径的黑色节点数量,然后其他路径黑色节点的数量作为参考值,依次比较即可。

代码实现:

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->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色节点" << endl;
		return false;
	}

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

	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()//检查是否平衡
{
	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);
}

3 完整代码实现

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

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

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

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		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);
		cur->_col = RED;

		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;
			if (parent == grandfather->_left)
			{
					//g
				//p		//u
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//然后向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
								//g
							//p		//u
						//c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
								//g
							//p		//u
								//c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else
			{
					//g
				//u		//p
				Node* uncle = grandfather->_left;
				//叔叔节点存在且为红,变色即可
				if (uncle&&uncle->_col==RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔节点不存在,或者存在且为黑
				{
					if (cur == parent->_right)
					{
							//g
						//u		//p
									//c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
							//g
						//u		//p
							//c
						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;
		if (subLR)
			subLR->_parent = parent;
		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;
		if (pParent == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
			subL->_parent = pParent;
		}
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		Node* pParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (pParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
			subR->_parent = pParent;
		}
	}

	void Inorder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	bool IsBalance()//检查是否平衡
	{
		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);
	}
private:
	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->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

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

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

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int _Size(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		return _Size(root->_left) + _Size(root->_right) + 1;
	}
private:
	Node* _root = nullptr;
};

以上就是红黑树的核心知识了,其中插入操作是重点。如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力。后续会不断地更新知识。

相关推荐
itwangyang52029 分钟前
在 GitHub 上生成和配置个人访问令牌(PAT),并将其用于 R 环境中的凭证管理和包安装。
开发语言·r语言·github
宠..33 分钟前
创建文本框控件
linux·运维·服务器·开发语言·qt
Sally_xy35 分钟前
安装 Java
java·开发语言
湫兮之风36 分钟前
C++: 一文掌握std::vector::assign函数
开发语言·c++
AA陈超37 分钟前
Lyra学习6:GameFeatureAction_AddComponents分析
c++·笔记·学习·ue5
南玖i38 分钟前
vue2/html 实现高德点聚合
开发语言·ios·swift
飞梦工作室39 分钟前
PHP 中 php://input 的全面使用指南
android·开发语言·php
第二只羽毛40 分钟前
订餐系统的代码实现
java·大数据·开发语言
小熊熊知识库42 分钟前
C# EF.core 介绍以及高性能使用
开发语言·c#