【数据结构】C++实现红黑树

文章目录


前言

本章对红黑树做一个总结,包括红黑树的基本规则,代码实现以及图片展示等


一、红黑树的概念

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

二、红黑树的规则

  1. 每个结点不是红⾊就是⿊⾊
  2. 根结点是⿊⾊的
  3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点。
  4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

三、红⿊树的效率

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么 由此推出2^h − 1 <= N < 2^2∗h − 1由此推出h ≈ logN 也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径 2 ∗ logN,那么时间复杂度还是O(logN)

红黑树与AVL树的关系:

AVL树和红黑树都是自平衡二叉搜索树,旨在优化插入、删除和查找操作的性能。它们在设计和实现上有所不同,但在某些条件下存在相互转换的可能性。

  • AVL树可以染成红黑树,通过特定的染色算法可以满足红黑树的规则。
  • 红黑树不一定是AVL树,因为红黑树的平衡性要求较低,可能不满足AVL树的严格高度平衡条件。
  • 在实际应用中,选择使用哪种树结构取决于具体场景的需求,例如插入、删除和查询操作的频率。
  • 红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格

三、红⿊树的插⼊

红黑树的插入无非就是插入一个节点,如果父亲节点是黑色,那就直接插入,如果不是,然后在去维护它的规则。使其满足对应规则。
思考:当新插入一个节点时,是插入红色节点还是黑色节点呢?
注意这里插入的是红色节点
原因如果这里插入黑色节点时,那么就必会违背规则4,如果插入的是红色节点是有可能会违背规则3的。综合考虑,所以这里插入红色节点。

那怎样去维护红黑树的规则呢?这里我们通过变色和旋转去维护。不过要分为几种情况。这具体还是要看父亲的兄弟节点(uncle,这里方便我就称他为叔叔节点)。

1.叔叔节点(uncle)存在且为红

当叔叔节点亲存在且为红色时。这个时候就只需要变色就能维护红黑树的规则。
具体操作
把父亲和叔叔节点变成黑色,然后把父亲节点的父亲节点(grandpa)变成红色,然后再向上更新,如果上一节点为黑色,那就不用再向上更新。如果上一节点为红色,那就要进行下一操作了。

2.当叔叔节点不存在或则为黑

当叔叔节点不存在或则为黑时,这个时候就要旋转+变色了,但是这里旋转也要分情况:
第一种情况:单旋转+变色
满足条件:当新插入的节点在父亲节点的左边且叔叔节点不存在或则为黑
具体操作:对grandpa节点进行右旋转然后在把父亲节点变黑,grandpa节点变红。

当叔叔节点不存在时

当叔叔节点存在且为红,下面画的是抽象图。

第二种情况:双旋转+变色
满足条件:当新插入的节点在父亲节点的右边且叔叔节点不存在或则为黑
具体操作:先对parent节点进行左单旋然后在对grandpa节点进行右单旋最后在把cur变成黑色,grandpa变成红色

当叔叔节点存在且为黑,下面是抽象图

注意如果叔叔存在且为黑。那么cur节点一定不是新增节点,一定是从新增节点更新上来的。如果是新增节点的话那就不满足红黑树的规则4了。

四、红黑树的代码实现(只展示插入)

虽然红黑树的插入有很多种情况,但是代码实现起来还是比较简单的。这里说明一下就是我们把grandpa节点的称呼改成grandfather了。意思都一样的。只是小编在写代码的时候没有注意。

cpp 复制代码
#include<iostream>
#include<assert.h> 
#include<vector>
#include<list>
using namespace std;
enum Color
{
	RED,
	BLACK 
};
template<class T,class V>
struct RBTreeNode 
{
	pair<T, V> _ky;
	RBTreeNode<T, V>* left;
	RBTreeNode<T, V>* right;
	RBTreeNode<T, V>* parent; 

	Color _cor;

	RBTreeNode(const pair<T, V>& ky = pair<T,V>())       
		:_ky(ky) 
		,left(nullptr)
		,right(nullptr)
		,parent(nullptr)
	{}

};
template<class T,class V>
class RBTree
{
	typedef RBTreeNode<T, V> Node;
public:
	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 (pparent == nullptr)
		{
			_root = subR;
			subR->parent = nullptr;
		}
		else if (pparent->left == parent)
		{
			pparent->left = subR;
		}
		else if (pparent->right == parent)
		{
			pparent->right = subR;
		}
	}
	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 if (pparent->right == parent)
		{
			pparent->right = subL;
		}
		else assert(false);
	}
	bool Insert(const pair<T, V>& ky)//红黑树的插入
	{
		//按照二叉搜索树的规则插入
		if (_root == nullptr)
		{
			_root = new Node(ky);
			_root->_cor = BLACK;
			_root->parent = nullptr;
			return 0;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{

			if (cur->_ky.first < ky.first)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (cur->_ky.first > ky.first)
			{
				parent = cur;
				cur = cur->left;
			}
			else return false;
		}
		cur = new Node(ky);
		cur->_cor = RED;
		parent->_ky.first < ky.first ? parent->right = cur : parent->left = cur;
		cur->parent = parent;

		while (parent && parent->_cor == RED)
		{
			
			Node* grandfather = parent->parent;
			if (grandfather->left == parent)
			{
				//   g
			    // p   u
				Node* uncle = grandfather->right; 
				if (uncle && uncle->_cor == RED)//叔叔节点存在且为红
				{
					uncle->_cor = parent->_cor = BLACK;//变色
					grandfather->_cor = RED;
					//向上更新
					cur = grandfather;
					parent = cur->parent;
				}
				else//叔叔节点存在且为黑或者不存在
				{
					//    g 
		            //  p   u
				    //c
					if (cur == parent->left)//右单旋+变色
					{
						RotateR(grandfather);//右旋转
						parent->_cor = BLACK;
						grandfather->_cor = RED;
					}
					//    g
					//  p   u
					//    c
					else//左右双旋+变色
					{
						RotateL(parent);//左旋转
						RotateR(grandfather);//右旋转
						cur->_cor = BLACK; 
						grandfather->_cor = RED;
					}
					break;
				}
			}
			else//跟上面差不多的只是旋转的方向改变一下就行了。
			{
				//   g
			    // u   p
				Node* uncle = grandfather->left;
				if (grandfather->right == parent) 
				{
					if (uncle && uncle->_cor == RED) 
					{
						uncle->_cor = parent->_cor = BLACK; 
						grandfather->_cor = RED; 

						cur = grandfather;
						parent = cur->parent;  
					}
					else
					{
						if (cur == parent->right)//右单旋     
						{
							RotateL(grandfather); 
							parent->_cor = BLACK; 
							grandfather->_cor = RED; 
						}
						else
						{
							RotateR(parent);   
							RotateL(grandfather);     
							cur->_cor = BLACK;   
							grandfather->_cor = RED;   
						}
						break; 
					}
				}
			}
		}
		_root->_cor = BLACK; //防止根节点在更新的时候变成红色
		return true; 
	}
	void _InOrder()
	{
		InOrder(_root);
	}
private:
	void InOrder(Node*root)
	{
		if (root == nullptr)
		{
			return;
		}
		InOrder(root->left);
		cout << root->_ky.first << ":" << root->_ky.second << " ";
		InOrder(root->right);
	}
	Node* _root = nullptr;
};
int main()
{
	RBTree<int, int >v;
	int arr[] = { 1,5,3,6,4,8,2,9,7 };
	for (auto e : arr)
	{
		v.Insert({ e,e });
	}
	v._InOrder();
}

五、红黑树的查找

由于查找的代码和逻辑比较简单,这里小编就直接写了。我相信大家没有问题的。

cpp 复制代码
Node* Find(const pair<T,V>& key)  
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_ky.first < key.first ) 
		{
			cur = cur->right; 
		}
		else if (cur->_ky.first > key.first ) 
		{
			cur = cur->left; 
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

六 、红⿊树的验证

这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。

  • 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
  • 规则2直接检查根即可
  • 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
  • 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
  • 验证红黑树的代码演示
cpp 复制代码
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->_cor == RED && root->parent->_cor == RED)
	{
		cout << root->_ky.first << "存在连续的红色结点" << endl;
		return false;
	}
	if (root->_cor == BLACK)
	{
		blackNum++;
	}
	return Check(root->left, blackNum, refNum) && Check(root->right, blackNum, refNum);  
}
bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_cor == RED)
		return false;
	// 参考值
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_cor == BLACK)
		{
			++refNum;
		}
		cur = cur->left;
	}
	return Check(_root, 0, refNum);
}

思考:为什么红⿊树如何确保最⻓路径不超过最短路径的2倍的?

  • 分析

由红黑树的规则4得知:每条从根到NULL 的路径的黑色节点数量是相同的。所以在极端情况下,最短的路径就是两个黑色节点的路径。但是又由规则2,3得知,红黑树的所有路径中没有连续的红节点而且根节点为黑,所以在极端情况下最长的路径就是一黑一红。刚好是最短路径的2倍。


总结

今天就到这里吧,这里我给大家分享了红黑树的部分,但是还有一部分没有写。比如红黑树的删除。如果对红黑树的删除感兴趣的话可以去参考《算法导论》或者《STL源码剖析》中讲解。

相关推荐
ajassi20004 小时前
开源 C++ QT QML 开发(十七)进程--LocalSocket
c++·qt·开源
微露清风5 小时前
系统性学习C++-第五讲-内存管理
java·c++·学习
星夜钢琴手6 小时前
推荐的 Visual Studio 2026 Insider C++ 程序项目属性配置
c++·visual studio
kyle~7 小时前
Qt---setAttribute设置控件或窗口的内部属性
服务器·前端·c++·qt
周杰伦_Jay7 小时前
【Java集合体系】全面解析:架构、原理与实战选型
java·开发语言·数据结构·链表·架构
hsjkdhs7 小时前
C++之多态
开发语言·jvm·c++
kyle~8 小时前
C++STL---静态数组array
开发语言·c++
kk”8 小时前
C++ List
开发语言·c++
tkevinjd8 小时前
反转链表及其应用(力扣2130)
数据结构·leetcode·链表