当前位置: 首页 > article >正文

C++从入门到起飞之——红黑树 全方位剖析!

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1. 红⿊树的概念

2. 红⿊树的实现

2.1 构建整体框架

 2.2 红黑树的插入

 2.3 红黑树的验证

 2.4 红黑树的其他简单接口

3、红黑树完整源码

4、完结散花


1. 红⿊树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路 径⻓出2倍,因⽽是接近平衡的。

>红⿊树的规则:

1. 每个结点不是红⾊就是⿊⾊

2. 根结点是⿊⾊的

3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的 红⾊结点。

4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

 说明:《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦结点 不是传统的意义上的叶⼦结点,⽽是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了 ⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道 ⼀下这个概念即可。

2. 红⿊树的实现

2.1 构建整体框架

枚举类型定义红黑色:

//枚举类型定义颜色
enum Colour
{
	RED,
	BLACK
};

红黑树每个节点的结构: 

//节点结构(默认存储pair类型的key/val结构)
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		,_col(RED)
	{}
	pair<K, V> _kv;
	RBTreeNode* _parent;
	RBTreeNode* _left;
	RBTreeNode* _right;
	Colour _col;//初始化为红色
};

红黑树整体结构

//红黑树
template<class K,class V>
class RBTree
{
	typedef RBTreeNode Node;
public:
       //各种接口的实现

private:
	Node* _root=nullptr;
};

 2.2 红黑树的插入

1、红黑树是特殊的二叉搜索数,所以我们进行插入时,还是先按照二叉搜索数的规则进行插入!

//如果树为空,在根插入并且颜色为黑色
if (_root == nullptr)
{
	_root = new Node(kv);
	_root->_col = BLACK;
	return true;
}
//树不为空按搜索树规则先进行插入
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
	if (kv.first < cur->_kv.first)//小往左走
	{
		parent = cur;
		cur = parent->_left;
	}
	else if (kv.first > cur->_kv.first)//大往右走
	{
		parent = cur;
		cur = parent->_right;
	}
	else
	{
		return false;//不支持相同元素的插入
	}
}
cur = new Node(kv);
cur->_col = RED;
if (kv.first < parent->_kv.first)//K小插入在左边
	parent->_left = cur;
else//K大插入在右边
	parent->_right = cur;
cur->_parent = parent;

2、插入成功后,我们就要维护红黑树的规则了。 

>如果插入节点的父亲是黑色,那插入后依然符合红黑树的规则,我们不用处理。

>如果插入节点的父亲是红色,那么此时就有连续的红色节点,我们就要进行处理。

        a、插入节点的叔叔存在且为红,我们只需要进行变色。

变色原理:因为parent的颜色为红,所以g的颜色一定为黑。插入前从g开始的路径的黑色节点的数量相同,要解决连续红色节点的问题,我们必然要把parent变黑色。但从g到cur路径的黑色节点多了一个,所以我们把g变红,在把u变黑就完美的解决了问题!

不过,g变红后,又可能会引起祖先节点的连续红色节点问题,所以我们还要继续向上维护红黑树的规则!

变色代码实现

//插入后进行维护红黑树规则的逻辑
//parent存在且为红
while (parent&& parent->_col = RED)
{
	Node* grandfather = parent->_parent;
	//p在g的右边
	if (parent == grandfather->_right)
	{
		//g
	//u		p
		Node* uncle = grandfather->_left;
		if (uncle&& uncle->_col = RED)//uncle存在且为红
		{
			//变色处理
			uncle->_col = parent->_col = BLACK;
			grandfather->_col = RED;
			//更新cur继续向上处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else//uncle不存在或者存在且为黑
		{

		}
	}
	else//p在g的左边
	{
		//g
	//p		u
		Node* uncle = grandfather->_left;
		if (uncle&& uncle->_col = RED)//uncle存在且为红
		{
			//变色处理
			uncle->_col = parent->_col = BLACK;
			grandfather->_col = RED;
			//更新cur继续向上处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else//uncle不存在或者存在且为黑
		{

		}
	}
}

        b、如果uncle不存在或者uncle存在且为黑,这时我们就要进行旋转加变色处理。

单旋+变色:

>如果uncle不存在,说明c一定是新增节点,如果c是变色后的节点,那么它在变色前一定是黑色,而从g开始的路径到c就多一个黑色节点!

 >如果uncle存在且为黑,说明c一定是变色后的节点,如果c是新增的节点,那么从g开始的路径到u就多一个黑色节点!

 变色原理:我们根据c、p、g的位置来选择合理的旋转逻辑,然后再把p变黑,g变红即可解决问题!

双旋+变色:

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则 c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上 来的。

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解 决问题,需要旋转+变⾊。

如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且 不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且 不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

 插入完整代码:

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 (kv.first < cur->_kv.first)//小往左走
		{
			parent = cur;
			cur = parent->_left;
		}
		else if (kv.first > cur->_kv.first)//大往右走
		{
			parent = cur;
			cur = parent->_right;
		}
		else
		{
			return false;//不支持相同元素的插入
		}
	}
	cur = new Node(kv);
	cur->_col = RED;
	if (kv.first < parent->_kv.first)//K小插入在左边
		parent->_left = cur;
	else//K大插入在右边
		parent->_right = cur;
	cur->_parent = parent;

	//插入后进行维护红黑树规则的逻辑
	//parent存在且为红
	while (parent&& parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		//p在g的右边
		if (parent == grandfather->_right)
		{
			//g
		//u		p
			Node* uncle = grandfather->_left;
			if (uncle&& uncle->_col == RED)//uncle存在且为红
			{
				//变色处理
				uncle->_col = parent->_col = BLACK;
				grandfather->_col = RED;
				//更新cur继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle不存在或者存在且为黑
			{
				if (cur == parent->_right)
				{
					//g
				//u		p
				//		   c
				//以g为旋转点进行左单旋
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//g
				//u		p
				//	  c
				//进行右左双旋
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
				break;
			}
		}
		else//p在g的左边
		{
			//g
		//p		u
			Node* uncle = grandfather->_left;
			if (uncle&& uncle->_col == RED)//uncle存在且为红
			{
				//变色处理
				uncle->_col = parent->_col = BLACK;
				grandfather->_col = RED;
				//更新cur继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle不存在或者存在且为黑
			{
				if (cur == parent->_left)
				{
					//g
				//p		u
			//c
				//以g为旋转点进行右单旋
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//g
				//p		u
			//		c
				//进行左右双旋
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
				break;
			}
		}
	}
	//如果持续更新变色到根
	_root->_col = BLACK;
	return true;
}

 2.3 红黑树的验证

>检查每条路径的黑色节点是否相等,是否有连续的红色节点

//检查每条路径的黑色节点是否相等,是否有连续的红色节点
bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历⾛到空时,意味着⼀条路径⾛完了 
		//cout << blackNum << endl;
		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);
}

 >插入一万个随机数看看是否平衡

void testInert()
{
	const int N = 10000;
	RBTree<int, int> t;
	vector<int> v;
	srand((unsigned int)time(nullptr));
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}
	for (auto e : v)
	{
		t.insert({ e,e });
	}
	cout << t.IsBalance() << endl;
}

 2.4 红黑树的其他简单接口

//默认构造
RBTree() = default;
//拷贝构造
RBTree(const RBTree<K,V>& rbt)
{
	_root=_copy(rbt._root);
}
// 赋值重载
RBTree<K, V>& operator=(RBTree<K, V> tmp)
{
	std::swap(_root, tmp._root);
	return *this;
}
//中序遍历
void InOrder()
{
	_InOrder(_root);
}
//二叉树的析构
~RBTree()
{
	_Destroy(_root);
}
private:
//递归拷贝
Node* _copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* newNode = new Node(root->_kv);
	newNode->_left = _copy(root->_left);
	newNode->_right = _copy(root->_right);
	return newNode;
}
//中序遍历
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << "<" << root->_kv.first << "," << root->_kv.second << ">" << endl;
	_InOrder(root->_right);
}
//二叉树的销毁
void _Destroy(Node* root)
{
	if (root == nullptr)
		return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
}

3、红黑树完整源码

 

#pragma once
#include<iostream>
using namespace std;
//枚举类型定义颜色
enum Colour
{
	RED,
	BLACK
};
//节点结构(默认存储pair类型的key/val结构)
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		,_col(RED)
	{}
	pair<K, V> _kv;
	RBTreeNode* _parent;
	RBTreeNode* _left;
	RBTreeNode* _right;
	Colour _col;//初始化为红色
};

//红黑树
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
public:
	//默认构造
	RBTree() = default;
	//拷贝构造
	RBTree(const RBTree<K,V>& rbt)
	{
		_root=_copy(rbt._root);
	}
	// 赋值重载
	RBTree<K, V>& operator=(RBTree<K, V> tmp)
	{
		std::swap(_root, tmp._root);
		return *this;
	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
	}
	//二叉树的析构
	~RBTree()
	{
		_Destroy(_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 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 (kv.first < cur->_kv.first)//小往左走
			{
				parent = cur;
				cur = parent->_left;
			}
			else if (kv.first > cur->_kv.first)//大往右走
			{
				parent = cur;
				cur = parent->_right;
			}
			else
			{
				return false;//不支持相同元素的插入
			}
		}
		cur = new Node(kv);
		cur->_col = RED;
		if (kv.first < parent->_kv.first)//K小插入在左边
			parent->_left = cur;
		else//K大插入在右边
			parent->_right = cur;
		cur->_parent = parent;

		//插入后进行维护红黑树规则的逻辑
		//parent存在且为红
		while (parent&& parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//p在g的右边
			if (parent == grandfather->_right)
			{
				//g
			//u		p
				Node* uncle = grandfather->_left;
				if (uncle&& uncle->_col == RED)//uncle存在且为红
				{
					//变色处理
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//更新cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑
				{
					if (cur == parent->_right)
					{
						//g
					//u		p
					//		   c
					//以g为旋转点进行左单旋
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//g
					//u		p
					//	  c
					//进行右左双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
					break;
				}
			}
			else//p在g的左边
			{
				//g
			//p		u
				Node* uncle = grandfather->_right;
				if (uncle&& uncle->_col == RED)//uncle存在且为红
				{
					//变色处理
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//更新cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑
				{
					if (cur == parent->_left)
					{
						//g
					//p		u
				//c
					//以g为旋转点进行右单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//g
					//p		u
				//		c
					//进行左右双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
					break;
				}
			}
		}
		//如果持续更新变色到根
		_root->_col = BLACK;
		return true;
	}
	//检查平衡
	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:
	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pParent = parent->_parent;

		parent->_left = subLR;
		if (subLR)//如果不为空
			subLR->_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* pParent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		subR->_left = parent;
		parent->_parent = subR;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		if (pParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
			subR->_parent = pParent;
		}
	}
	//检查每条路径的黑色节点是否相等,是否有连续的红色节点
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			// 前序遍历⾛到空时,意味着⼀条路径⾛完了 
			//cout << blackNum << endl;
			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);
	}
	//递归拷贝
	Node* _copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newNode = new Node(root->_kv);
		newNode->_left = _copy(root->_left);
		newNode->_right = _copy(root->_right);
		return newNode;
	}
	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << "<" << root->_kv.first << "," << root->_kv.second << ">" << endl;
		_InOrder(root->_right);
	}
	//二叉树的销毁
	void _Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

private:
	Node* _root=nullptr;
};

4、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​


http://www.kler.cn/news/354995.html

相关文章:

  • 【Flutter 面试题】 Flutter如何使用路由、全局错误捕获和自定义组件统一管理错误页面?
  • SpringBoot3响应式编程全套-R2DBC
  • 模板方法设计模式
  • hdfs API操作 hadoop3.3.5
  • java项目技术架构图-样例
  • mysql--表的约束
  • 【Ubuntu】虚拟机共享文件夹启用
  • NFTScan | 10.07~10.13 NFT 市场热点汇总
  • vmware ubuntu根分区扩容
  • 纯前端如何实现批量下载功能呢?
  • ChatGPT免费使用:人工智能在现代社会中的作用
  • Day31 || 122.买卖股票的最佳时机 II、55. 跳跃游戏、 45.跳跃游戏II 、1005.K次取反后最大化的数组和
  • LangGraph - Hierarchical Agent Teams
  • 京东背调有病吧......
  • 开源节流-2024年10月17日
  • 【秋招笔试】10.13拼多多(已改编)秋招-三语言题解
  • LLM大模型的评测维度有哪些?
  • 试用cursor的简单的记录
  • mysql迁移到达梦的修改点
  • 企业电子印章主要通过以下几种方式进行防伪