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

【C++篇】红黑树的实现

目录

前言:

一,红黑树的概念

1.1,红黑树的规则

1.2,红黑树的最长路径 

1.3,红黑树的效率分析

 二,红黑树的实现

2.1,红黑树的结构

2.2,红黑树的插入

2.2.1,大致过程 

2.2.2,情况1:变色处理 

 2.2.3,情况2:单旋+变色

2.2.4,情况3:双旋+变色

2.3,红黑树插入代码的实现

2.4,红黑树的验证

三,整体代码

四,测试代码


前言:

本篇会用到上篇【AVL树的实现】中的旋转知识。

一,红黑树的概念

        红黑树是一颗二叉搜索树,它的每一个节点增加一个存储为来表示节点的颜色。可以是红色或者黑色。它通过对从根开始到叶子节点的每条路径上各个节点颜色的约束,确保最长路径不会超过最短路径的2倍,从而实现平衡的。

1.1,红黑树的规则

1,每个节点不是红色就是黑色。

2,根节点是黑色的。

3,红色节点的两个孩子只能是 黑色节点或者是空节点。也就是说不能出现连续的红色节点

4,对于任意一个节点,从该节点开始,到叶子节点的所有路径上,均包含相同数量的黑色节点。

以上 都是红黑树,满足红黑树的规则。

1.2,红黑树的最长路径 

1,由第四条规则可知,从根节点开始的每条路径上,黑色节点的数量相同。所以在极端场景下,一颗红黑树,它的最短路径就是节点全为黑色的路径。假设红黑树的每条路径黑色节点数量都为b,那么最短路径的节点数量为b.

2,由 第三条规则可知,一条路径上不能由连续的红色节点,最长路径是由一黑一红间隔组成的,所以最长路径为2*b。

3,而对于一颗红黑树,最长和最短路径不一定存在。我们可以得出对于任意一颗红黑树,它的任意 一条路径长度x都是,b<=x<=2*b.

1.3,红黑树的效率分析

假设N是红黑树节点的数量,h是最短路径的长度,最长路径不超过2*h

可以得到2^h-1<= N <= 2^(2*h)-1,推出h大致为logN,也就意味着红黑树的增删查该最坏走2*logN,时间复杂度O(logN).

 二,红黑树的实现

2.1,红黑树的结构

enum color
{
    Red,
    Black
};

template<class k,class v>
struct RBTreeNode
{
    RBTreeNode(const pair<k,v>& kv)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_kv(kv)
    {}
    RBTreeNode<k, v>* _left;
    RBTreeNode<k, v>* _right;
    RBTreeNode<k, v>* _parent;
    pair<k, v> _kv;
    color _col;
};

template<class k,class v>
class RBTree
{
    typedef RBTreeNode<k, v> Node;
public:

   //...

private:

    Node* _root=nullptr;
};
 

2.2,红黑树的插入

2.2.1,大致过程 

1,插入一个值需要按照搜索树的规则进行插入,再判断插入后是否满足红黑树的规则。

2,如果是空树插入,新增节点就是黑色节点。如果是非空树插入,新增节点就必须是红色节点,因为如果插入黑色节点,就一定会破坏规则4,而插入红色节点是有可能会破坏规则3,而且对于规则3来说,解决方法比规则4的解决方法容易。

3,非空树插入后,如果父节点是黑色节点,则没有违反任何规则,插入结束。

4,非空树插入后,如果父节点是红色节点,则违反规则3,进一步分析。

2.2.2,情况1:变色处理 

由上图可知,c为红,p为红,g为黑,u存在且为红。在这种情况下,我们需要将p和u变黑,g变红,g成为新的c,继续向上更新。

分析:

        因为p和u都是红色的,g是黑色的。把p和u变黑,左边子树路径各增加一个黑色节点,g再变红,相当于g所在路径的黑色节点数量不变,同时解决了c和p连续红节点的问题。

        需要继续往上跟新是因为g是红色,如果g的父亲还是红色,就需要继续处理;如果g的父亲是黑色,则处理结束;如果g是整棵树的根,再将g变成黑色即可。 

 2.2.3,情况2:单旋+变色

c为红,p为红,u不存在或者u存在且u为黑色。在这种情况下,就需要进行单旋+变色处理

分析:

u不存在时,c一定是新增节点。

u存在且为黑色时,c一定不是新增节点,是在c的子树中插入,符号情况1,经过情况1后,c被调整为红色的。

 上图展示的是右单旋的场景,下面是根据右单旋,画出的左单旋大致图,与右单旋过程相似:

 总结:经过单旋+变色后,我们可以看到p做了子树新的根,且p是黑色的,所以不管p的父亲是黑色的还是红色的,都满足红黑树的规则,所以此时,就不需要往上更新了。

2.2.4,情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增结点,u存在且为黑,则 c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。

 上图展示的是左右双旋+变色,同样右左双旋类似:

同样经过双旋+变色后,c成为新的根,且c为黑色,所以也不需要继续向上更新了。

2.3,红黑树插入代码的实现

bool Insert(const pair<k, v>& kv)
{
	//插入根节点,color->Black
	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);
	cur->_col = Red;
	//与父亲节点连接好
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	//颜色处理+旋转
	while (parent&& parent->_col == Red)
	{
		//g
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			//p为g的左边
			//    g
			//  p   u
			Node* uncle = grandfather->_right;
			//叔叔存在且为红,情况1
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = Black;
				uncle->_col = Black;
				grandfather->_col = Red;
				//继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况2,3
				//叔叔不存在或者叔叔为黑
				//u为黑,则c之前是黑的
				//u不存在,则c是新插入的
				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
		{
			//p为g的右边
			//   g
			// u   p
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == Red)
			{
				//情况1
				//变色
				parent->_col = Black;
				uncle->_col = Black;
				grandfather->_col = Red;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况2,3
				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;
	Node* pparent = parent->_parent;

	if (subLR)
		subLR->_parent = parent;
	parent->_left = subLR;
	subL->_right = parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		_root->_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;
	Node* pparent = parent->_parent;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;
	parent->_parent = subR;
	subR->_left = parent;
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
}

2.4,红黑树的验证

我们只需验证形成的树是否满足红黑树的四条规则即可。

其中,规则1和规则 2可以直接检查。

对于规则3,我们可以进行前序遍历,遍历到一个节点,判断该节点的颜色,再判断它的两个孩子的颜色,这样做太麻烦了。我们可以反过来,遍历到一个节点,如果他是红色的,判断它的父亲节点是否为黑色。

对于规则4,我们可以先从根开始找到一条路径上黑色节点的个数refNum,再对整棵树进行前序遍历,用变量 blackNum记录黑色节点的个数,当遍历到空的时候,与refNum比较即可。

bool Check(Node* root, int BlackNum, int num)
{
	if (root == nullptr)
	{
		if (BlackNum != num)
			return false;
		return true;
	}

	if (root->_col == Red && root->_parent->_col == Red)
		return false;
	if (root->_col == Black)
		BlackNum++;
	return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
}
//验证
bool isbalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == Red)
		return false;
	int num = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == Black)
			num++;
		cur = cur->_left;
	}
	return Check(_root, 0, num);
}

三,整体代码

enum color
{
	Red,
	Black
};

template<class k,class v>
struct RBTreeNode
{
	RBTreeNode(const pair<k,v>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
	{}
	RBTreeNode<k, v>* _left;
	RBTreeNode<k, v>* _right;
	RBTreeNode<k, v>* _parent;
	pair<k, v> _kv;
	color _col;
};

template<class k,class v>
class RBTree
{
	typedef RBTreeNode<k, v> Node;
public:
	bool Insert(const pair<k, v>& kv)
	{
		//插入根节点,color->Black
		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);
		cur->_col = Red;
		//与父亲节点连接好
		if (parent->_kv.first < kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		//颜色处理+旋转
		while (parent&& parent->_col == Red)
		{
			//g
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				//p为g的左边
				//    g
				//  p   u
				Node* uncle = grandfather->_right;
				//叔叔存在且为红,情况1
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = Black;
					uncle->_col = Black;
					grandfather->_col = Red;
					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//情况2,3
					//叔叔不存在或者叔叔为黑
					//u为黑,则c之前是黑的
					//u不存在,则c是新插入的
					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
			{
				//p为g的右边
				//   g
				// u   p
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == Red)
				{
					//情况1
					//变色
					parent->_col = Black;
					uncle->_col = Black;
					grandfather->_col = Red;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//情况2,3
					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;
		Node* pparent = parent->_parent;

		if (subLR)
			subLR->_parent = parent;
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			_root = subL;
			_root->_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;
		Node* pparent = parent->_parent;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		parent->_parent = subR;
		subR->_left = parent;
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	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 num = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == Black)
				num++;
			cur = cur->_left;
		}
		return Check(_root, 0, num);
	}
private:
	bool Check(Node* root, int BlackNum, int num)
	{
		if (root == nullptr)
		{
			if (BlackNum != num)
				return false;
			return true;
		}

		if (root->_col == Red && root->_parent->_col == Red)
			return false;
		if (root->_col == Black)
			BlackNum++;
		return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
	}
	int _size(Node* root)
	{
		if (root == nullptr)
			return 0;
		return _size(root->_left) + _size(root->_right) + 1;
	}
	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;
	}
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
	Node* _root=nullptr;
};

四,测试代码

void TestRBTree1()
{
	RBTree<int, int> t;
	// 常规的测试用例
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto e : a)
	{
		t.Insert({ e, e });
	}

	t.Inorder();
	cout << t.isbalance() << endl;
}
int main()
{
	TestRBTree1();
	return 0;
}


http://www.kler.cn/a/506547.html

相关文章:

  • 浅谈云计算20 | OpenStack管理模块(下)
  • 移动端H5缓存问题
  • 如何在 Rocky Linux 上安装极狐GitLab?
  • IP层之分片包的整合处理
  • 学习threejs,使用FlyControls相机控制器
  • Jmeter进行http接口并发测试
  • 自定义注解使用AspectJ切面和SpringBoot的Even事件优雅记录业务接口及第三方接口调用日志实现思路
  • @NotNull、@NotBlank 和 @NotEmpty 注解的区别
  • 工业过程自动化技术:控制系统、传感器、互联网、AI通信
  • Excel 技巧10 - 如何检查输入重复数据(★★)
  • [Qt]常用控件介绍-容器类控件-QGroupBox、QTabWidget
  • Bootstrap UI 编辑器
  • vue3里面,事件触发一次,方法执行多次
  • 通过ssh连接debian
  • Redis :redis的大Key问题
  • 复盘思维课程
  • Spring MVC核心组件与请求处理流程
  • BertTokenizerFast 和 BertTokenizer 的区别
  • 探索 AI 自动化编程:效率革命与未来教育的转型
  • Java 对象池管理的高性能工具库 Apache Commons Pool 2
  • 2.两数相加--力扣
  • tomcat文件目录讲解
  • 剑指Offer|LCR 031. LRU 缓存
  • Haskell语言的网络编程
  • 基于 Electron 应用的安全测试基础 — 提取和分析 .asar 文件
  • 【k8s面试题2025】1、练气期