高级数据结构——红黑树

news2024/11/15 21:38:57

目录

1. 红黑树的概念

2. 红黑树的性质

3.  红黑树

6. 红黑树的验证

7. 红黑树的删除

8. 红黑树与AVL数的比较

9. 红黑树的应用

10. 完整代码

10.1 RBTree.h

10.2 test.cpp

1. 红黑树的概念

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

 同是二叉搜索平衡树,但是AVL树控制的比红黑树严格的多,AVL树要是每个节点的平衡因子绝对值不超过1,就会导致不断的去旋转调整,付出相对较高的代价,而这里红黑树更像是一种近似平衡,条件没有这么苛刻。

如下一棵树,站在红黑树的角度看是平衡的,站在AVL树的角度看就是不平衡的,需要旋转调整:

 但是从搜索效率的角度看AVL树还是好一点,因为它的平衡标准高,就导致其更加平衡,相同数量的节点情况下AVL树的高度会更低,加上存100w个数据,AVL树大概有20层(log100w),而红黑树最坏就能达到40层,显然AVL树的搜索效率高。但是在内存里找20次和找40次没有什么区别,因为CPU足够的快,这里简单提一下。

2. 红黑树的性质

1、每个结点不是红色就是黑色。
2、根节点必须是黑色的
3、如果一个节点是红色的,则它的两个孩子结点是黑色的(没有连续的红色节点
4、对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径都包含相同数量的黑色节点
5、每个叶子结点都是黑色的(此处的叶子结点指的是空结点 -》NIL节点)

根据这些规则,红黑树是如何保证最长路径不超过最短路径的2倍的呢?

首先我们根据规则分析得知,我们假设一条路径的黑色节点的个数为N个,则最长路径和最短路径的情况如下:

  • 最短路径:全黑

  • 最长路径:一黑一红间隔

 而这里一黑一红间隔的原因在于红黑树不允许出现连续的红节点,为了能最大程度的保证最长节点数,唯有一黑一红间隔的方式才能达到最长,综上当黑节点个数固定为N时,最短路径节点个数为N,最长路径节点个数为2N。

3.  红黑树

这里节点的实现相较于AVL树我们依旧是创建成KV模型、三叉链结构,唯一有所改变的是这里要通过枚举的方式把红色和黑色定义好,并在节点类内部定义变量_col表示节点颜色,最后记得写上构造函数

enum Colour
{
	Red,
	Black,
};
//节点类
template <class K, class V>
struct RBTreeNode
{
	//三叉链结构
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	//存储的键值对
	pair<K, V> _kv;
	//节点的颜色
	Colour _col;
	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(Red)
	{}
};

为什么插入的节点在构造函数这里要处理成红色?

  1. 如果处理成黑色,则一定导致新插入节点的那条路径多出一个黑色节点,不再满足各个路径黑色节点个数相同的性质,一定破坏性质4,此时很难维护。
  2. 如果处理成红色,则可能父亲节点也是红色,此时就出现了连续的红色节点,破坏性质3,不过此时我们向上调整即可,但如果父亲节点是黑色,那就无需操作了,不违反任何性质。

综合利弊,插入黑色节点一定会破坏性质4,而插入红色节点可能破坏性质3,因此处理成红色为宜。

5. 红黑树的插入操作

红黑树的插入操作主要分为这几大步骤:

  • 1、一开始为空树,直接new新节点;
  • 2、一开始非空树,寻找插入的合适位置;
  • 3、找到插入的合适位置后,进行父亲与孩子的双向链接;
  • 4、检测新节点插入后,红黑树的性质是否造到破坏。

接下来对其进行逐个分析:

  • 1、一开始为空树,直接new新节点:

因为树为空的,所以直接new一个新插入的节点,将其作为根_root即可,接着更新颜色_col为黑色。

  • 2、一开始非空树,寻找插入的合适位置:

这里和二叉搜索树的寻找合适的插入位置的思想一样,都要遵循以下几步:

  1. 插入的值 > 节点的值,更新到右子树查找
  2. 插入的值 < 节点的值,更新到左子树查找
  3. 插入的值 = 节点的值,数据冗余插入失败,返回false

当循环结束的时候,就说明已经找到插入的合适位置,即可进行下一步链接。

  • 3、找到插入的合适位置后,进行父亲与孩子的双向链接:

注意这里节点的构成为三叉链,因此最后链接后端孩子和父亲是双向链接,具体操作如下:

  1. 插入的值 > 父亲的值,把插入的值链接在父亲的右边
  2. 插入的值 < 父亲的值,把插入的值链接在父亲的左边
  3. 因为是三叉连,插入后记得双向链接(孩子链接父亲)

走到这,说明节点已经插入完毕,接下来就要对红黑树的颜色进行调整了

  • 4、检测新节点插入后,红黑树的性质是否造到破坏:

不是所有的情况都是需要进行调整的,当插入节点的父亲为黑色(新节点的默认颜色是红色),那么就不需要进行调整,因为没有破坏红黑树的任何一条性质。

只有当插入节点的父亲为红色时(新节点的默认颜色也是是红色),才需要进行调整,因为此时插入的节点和父亲都是红色节点,但是红黑树不允许出现连续的红色节点,此时就要进行调整。

注意这里既然插入节点cur的父亲p是红色,那么根据红黑树的性质(根结点是黑色的),其父亲的父亲g也就是祖父必然存在且一定是黑色,那么其父亲的兄弟节点u(可能不存在)也就是新插入节点cur的叔叔。因此我们约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

这里调整的办法主要是看叔叔节点的颜色如何,叔叔节点的不同,会导致三种不同的情况需要调整:

  • 情况一:cur为红,p为红,g为黑,u存在且为红
  • 情况二:cur为红,p为红,g为黑,u不存在
  • 情况三:cur为红,p为红,g为黑,u存在且为黑

接下来分别进行讨论:

  • 情况一:cur为红,p为红,g为黑,u存在且为红

为了避免出现连续的红色节点,我们可以把父节点p变黑,但是为了保证每条路径的黑色节点个数相同,我们需要把祖父节g点变红(不影响其它路径黑节点的个数),再把叔叔节点u变黑。

调整并未结束,此时祖父节点g为红色,但是如果这棵树本就是一颗完整的树呢?也就是g为根节点,那么只需要把节点g变成黑色即可。

如果这棵树是一棵树的子树,那么刚好把祖父节点g作为新插入的节点cur向上继续调整(继续判断父亲、叔叔如何……),直至调整结束。

补充:情况一不关心左右关系,只变色不旋转,所以 p、u是g的左或右是无所谓的,cur是p的左或右也是无所谓的。

接下来分析情况2:

  • 情况二:cur为红,p为红,g为黑,u不存在

 如果节点u不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。

此时就是一个很经典的右单旋结构(新节点插入较高左子树的左侧)我们可以先对其进行一个右单旋,再来更新颜色。具体步骤如下:

  1. 让祖父g变成父亲p的右子树。
  2. 父亲p作为根节点。
  3. 更新父亲节点p为黑色。
  4. 更新祖父g为红色。

  • 补充:

如若p为g的右孩子,cur为p的右孩子,则针对p做左单旋转,示例:

如若祖孙三代的关系是折线(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。示例:

 综上:

  1. p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红;
  2. p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红;
  3. p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红;
  4. p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红。

下面进入情况三

  • 情况三:cur为红,p为红,g为黑,u存在且为黑

此情况绝非单独存在,绝不可能是真的新节点cur插入,然后还会出现p为红,g为黑,u存在且为黑的情况,如果存在,那么只能说明先前插入节点或者构造函数就有问题,因为插入前就不符合红黑树的性质啊(每个路径的黑节点个数均相同)。

既然情况三出现了,那么一定是合理的,它就是建立在情况一的基础上继续往上调整从而出现的一种特殊情况,具体咱就是画图演示:

 此时就是很明显的一个情况3了,cur为红,pp为红,gg为黑,u存在且为黑,由此证明,情况三是通过情况一向上继续调整演化出来的。并且此新节点一定是从p和x任意一颗左右子树插入或演化上来的,才引发后续的cur从黑变红。

此时就是一个很经典的右单旋结构(cur在较高左子树的左侧)我们可以先对其进行一个右单旋,再来更新颜色。具体步骤如下:

  1. 让p的右子树变成g的左子树;
  2. 让p变成根节点位置;
  3. p的右子树指向g;
  4. 更新p的颜色为黑色;
  5. 更新g的颜色为红色。

 补充:

如若p为g的右孩子,cur为p的右孩子,则进行左单旋 + 调色,示例:

若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折线),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。示例:

综上:

  • p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红;
  • p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红;
  • p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红;
  • p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红.

情况二和情况三旋转 + 变色后,这颗子树不违反红黑树规则,相比插入前,且黑色节点的数量不变,不会影响上层,处理结束了。

代码如下:

bool Insert(const pair<K, V>& kv)
{
	//1、一开始为空树,直接new新节点
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = Black;//新插入的节点处理成黑色
		return true;
	}
	//2、寻找插入的合适位置
	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;//插入的值 = 节点的值,数据冗余插入失败,返回false
		}
	}
	//3、找到了插入的位置,进行父亲与插入节点的链接
	cur = new Node(kv);
	cur->_col = Red;//插入的节点处理成红色
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
	}
	else
	{
		parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
	}
	cur->_parent = parent;//三叉链,要双向链接
 
//4、检测新节点插入后,红黑树的性质是否造到破坏
	while (parent && parent->_col == Red)//存在连续的红色节点
	{
		Node* grandfather = parent->_parent;
		assert(grandfather);
		//先确保叔叔的位置
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
				{
					//		  g
					//     p
					// cur
					RotateR(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
				{
					//		  g
					//	 p
					//	     cur
					RotateLR(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
		else//grandfather->_right == parent
		{
			Node* uncle = grandfather->_left;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
				{
					//	g
					//	   p
					//	     cur
					RotateL(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
				{
					//   g
					//	      p
					//	cur
					RotateRL(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
	}
	_root->_col = Black;//暴力处理把根变成黑色
	return true;
}
//1、左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* ppNode = parent->_parent;//提前保持parent的父亲
	//1、建立parent和subRL之间的关系
	parent->_right = subRL;
	if (subRL)//防止subRL为空
	{
		subRL->_parent = parent;
	}
	//2、建立subR和parent之间的关系
	subR->_left = parent;
	parent->_parent = subR;
	//3、建立ppNode和subR之间的关系
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;//三叉链双向链接关系
	}
}
//2、右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* ppNode = parent->_parent;
	//1、建立parent和subLR之间的关系
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//2、建立subL和parent之间的关系
	subL->_right = parent;
	parent->_parent = subL;
	//3、建立ppNode和subL的关系
	if (parent == _root)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;//三叉链双向关系
	}
}
//3、左右双旋
void RotateLR(Node* parent)
{
	RotateL(parent->_left);
	RotateR(parent);
}
//4、右左双旋
void RotateRL(Node* parent)
{
	RotateR(parent->_right);
	RotateL(parent);
}

6. 红黑树的验证

红黑树的验证主要分为两大步骤:

  • 1、检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  • 2、检测其是否满足红黑树的性质

接下来分别演示:

  • 1、检测其是否满足二叉搜索树(中序遍历是否为有序序列):

这里只需要递归写一个中序遍历,并判断测试用例的结果是否为一个有序序列即可判断二叉搜索树:

//验证是否为一颗搜索二叉树
void InOrder()
{
	_InOrder(_root);//调用中序遍历子树
	cout << endl;
}
//中序遍历的子树
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_kv.first << " ";
	_InOrder(root->_right);
}
  • 2、检测其是否满足红黑树的性质:

这里只要判断是否满足红黑树的5大规则即可,具体操作如下:

  • 1、根节点是否为黑色;
  • 2、任意一条路径黑色节点数是否相同(递归每一条和确定的一条比较是否相同);
  • 3、递归检测是否违反性质三从而出现连续的红节点。
bool IsBalanceTree()
{
	Node* pRoot = _root;
	// 空树也是红黑树
	if (pRoot == nullptr)
		return true;
	// 检测根节点是否满足情况
	if (pRoot->_col != Black)
	{
		cout << "违反红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
	size_t blackCount = 0;
	Node* pCur = pRoot;
	while (pCur)
	{
		if (pCur->_col == Black)
			blackCount++;
		pCur = pCur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
	//走到null之后,判断k和black是否相等
	if (pRoot == nullptr)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	// 统计黑色节点的个数
	if (pRoot->_col == Black)
		k++;
	// 检测当前节点与其双亲是否都为红色
	Node* pParent = pRoot->_parent;
	if (pParent && pParent->_col == Red && pRoot->_col == Red)
	{
		cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
		return false;
	}
	return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
}

7. 红黑树的删除

红黑树的删除这里和AVL树一样就不做过多演示了,具体可参考《算法导论》或者《STL源码剖析》。大佬博文:红黑树插入删除操作

8. 红黑树与AVL数的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

9. 红黑树的应用

  •  1、C++ STL库 -- map/set、mutil_map/mutil_set
  • 2、Java 库
  • 3、linux内核
  • 4、其他一些库 

10. 完整代码

10.1 RBTree.h

#pragma once
#include<iostream>
#include<queue>
#include<vector>
#include<assert.h>
using namespace std;
enum Colour
{
	Red,
	Black,
};
//节点类
template <class K, class V>
struct RBTreeNode
{
	//三叉链结构
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	//存储的键值对
	pair<K, V> _kv;
	//节点的颜色
	Colour _col;
	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(Red)
	{}
};
//红黑树的类
template <class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
	//1、一开始为空树,直接new新节点
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = Black;//新插入的节点处理成黑色
			return true;
		}
	//2、寻找插入的合适位置
		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;//插入的值 = 节点的值,数据冗余插入失败,返回false
			}
		}
	//3、找到了插入的位置,进行父亲与插入节点的链接
		cur = new Node(kv);
		cur->_col = Red;//插入的节点处理成红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
		}
		else
		{
			parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
		}
		cur->_parent = parent;//三叉链,要双向链接

	//4、检测新节点插入后,红黑树的性质是否造到破坏
		while (parent && parent->_col == Red)//存在连续的红色节点
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			//先确保叔叔的位置
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
					{
						//		  g
						//     p
						// cur
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
					{
						//		  g
						//	 p
						//	     cur
						RotateLR(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
					{
						//	g
						//	   p
						//	     cur
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
					{
						//   g
						//	      p
						//	cur
						RotateRL(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
		}
		_root->_col = Black;//暴力处理把根变成黑色
		return true;
	}
//验证是否为一颗搜索二叉树
	void InOrder()
	{
		_InOrder(_root);//调用中序遍历子树
		cout << endl;
	}
//验证是否为红黑树
	bool IsBalanceTree()
	{
		Node* pRoot = _root;
		// 空树也是红黑树
		if (pRoot == nullptr)
			return true;
		// 检测根节点是否满足情况
		if (pRoot->_col != Black)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
		// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (pCur->_col == Black )
				blackCount++;
			pCur = pCur->_left;
		}
		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}

//求一棵树的高度
	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}
private:
	//1、左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;//提前保持parent的父亲
		//1、建立parent和subRL之间的关系
		parent->_right = subRL;
		if (subRL)//防止subRL为空
		{
			subRL->_parent = parent;
		}
		//2、建立subR和parent之间的关系
		subR->_left = parent;
		parent->_parent = subR;
		//3、建立ppNode和subR之间的关系
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;//三叉链双向链接关系
		}
	}
	//2、右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;
		//1、建立parent和subLR之间的关系
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//2、建立subL和parent之间的关系
		subL->_right = parent;
		parent->_parent = subL;
		//3、建立ppNode和subL的关系
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;//三叉链双向关系
		}
	}
	//3、左右双旋
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);
		RotateR(parent);
	}

	//4、右左双旋
	void RotateRL(Node* parent)
	{
		RotateR(parent->_right);
		RotateL(parent);
	}
//中序遍历的子树
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
//求一棵树的最长路径的子树
	int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}
//求一棵树的最短路径的子树
	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);
		return lh < rh ? lh + 1 : rh + 1;
	}
//求是否满足红黑树性质的子树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (pRoot == nullptr)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		// 统计黑色节点的个数
		if (pRoot->_col == Black)
			k++;
		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && pParent->_col == Red && pRoot->_col == Red)
		{
			cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
	}
public:
		//层序遍历(非必须)
	vector<vector<int>> levelOrder() {
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
		queue<Node*> q;
		int levelSize = 1;
		q.push(_root);
		while (!q.empty())
		{
			// levelSize控制一层一层出
			vector<int> levelV;
			while (levelSize--)
			{
				Node* front = q.front();
				q.pop();
				levelV.push_back(front->_kv.first);
				if (front->_left)
					q.push(front->_left);

				if (front->_right)
					q.push(front->_right);
			}
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
			// 上一层出完,下一层就都进队列
			levelSize = q.size();
		}
		return vv;
	}

private:
	Node* _root = nullptr;
};

10.2 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"
void TestRBTree1()
{
	//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.levelOrder();
	t.InOrder();
	t.Height();
}

void TestRBTree2()
{
	const size_t N = 1024 * 1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());
		v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	//cout << endl;
	cout << "Ƿƽ? " << t.IsBalanceTree() << endl;
	t.Height();

	//t.InOrder();
}
int main()
{
	//TestRBTree1();
	TestRBTree2();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

49天精通Java,第37天,可变参数列表

目录 一、可变参数列表二、可变参数列表的优缺点1、优点2、缺点 三、可变参数列表的适用场景1、函数重载2、命令行参数解析3、集合操作4、函数式编程 大家好&#xff0c;我是哪吒。 &#x1f3c6;本文收录于&#xff0c;49天精通Java从入门到就业。 全网最细Java零基础手把手…

SpringBoot 如何使用 @ResponseStatus 注解处理异常状态码

SpringBoot 如何使用 ResponseStatus 注解处理异常状态码 在 SpringBoot 应用程序中&#xff0c;异常处理是一个非常重要的话题。当应用程序出现异常时&#xff0c;我们需要对异常进行处理&#xff0c;以保证应用程序的稳定性和可靠性。除了使用异常处理器外&#xff0c;Sprin…

重新理解微服务之终究绕不过这4个坎之(一)

写在前头 大家曾经有没有遇过日常技术交流的时候&#xff0c;会讨论某某技术之间的关系是什么&#xff0c;某些技术是否应该用到微服务。我相信热爱技术交流的您&#xff0c;就算不是在微服务这里领域&#xff0c;或多或少都会跟其他同行会做一些争议话题的探讨&#xff0c;而…

华为OD机试真题B卷 JavaScript 实现【字符串分隔】,附详细解题思路

一、题目描述 输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff0c;长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 二、输入描述 连续输入字符串(每个字符串长度小于等于100)。 三、输出描述 依次输出所有分割后的长度…

k8s使用ceph存储

文章目录 初始化操作k8s使用ceph rbdvolumePV静态pv动态pv k8s使用cephfsvolume静态pv 初始化操作 ceph创建rbd存储池 ceph osd pool create k8s-data 32 32 replicated ceph osd pool application enable k8s-data rbd rbd pool init -p k8s-dataceph添加授权&#xff0c;需…

指针和数组--指针数组及其应用

目录 一、指针数组用于表示多个字符串 二、指针数组用于表示命令行参数 一、指针数组用于表示多个字符串 一维数组可存储一个字符串&#xff0c;二维数组可存储多个字符串。 二维数组的元素在内存中是连续存放的&#xff0c;存完第一行后&#xff0c;再存第二行&#xff0c;以…

多线程之JUC

写在前面 本文一起看下jdk并发包的相关内容。 1&#xff1a;JUC包提供了哪些功能 先通过包结构看下JUC提供的功能&#xff1a; 接下来分别看下。 1.1&#xff1a;锁 JUC中的锁机制提供了比synchronized&#xff0c;wait/notify更加灵活的同步控制&#xff0c;在java.util.…

大数据基础平台实施及运维进阶

1、完全分布式部署介绍 完全分部式是真正利用多台Linux主机来进行部署Hadoop&#xff0c;对Linux机器集群进行规划&#xff0c;使得Hadoop各个模块分别部署在不同的多台机器上。 2、nameNode HA完全分布式部署 2.1、nameNode切换方法 分别处于Active和Standby中 hadoop可以…

操作系统复习笔记4

1、queueType队列类型 队列中的数据也呈线性排列。虽然与栈有些相似&#xff0c;但队列中添加和删除数据的操作分别是在两端进行的。 线性表有顺序存储和链式存储&#xff0c;队列作为一种特殊的线性表&#xff0c;也同样存在这两种存储方式。 1.1 顺序队列 用数组存储队列…

C语言学习(二十五)---指针练习题(一)

在上一节内容中&#xff0c;我们学习了递归与冒泡排序法的有关内容&#xff0c;今天我们将继续往下学习&#xff0c;主要内容为指针练习题&#xff0c;好了&#xff0c;话不多说&#xff0c;开整&#xff01;&#xff01;&#xff01; 在之前的第18—22的内容中&#xff0c;我…

lnmp框架的应用

目录 应用一 nginx访问状态统计 1.先查看http_stub_status有没有安装 2.进入nginx的配置文件改配置 3.nginx-检查配置 重启服务 最后这个20就是显示的状态统计 应用二 给网站加密 1.首先安装http-tools软软件 2.把nginx设置锁也要有执行权限 3.进入nginx配置文件 4. 检查…

【Windows个性化设置篇】StartAllBack更改win11任务栏设置

【Windows个性化设置篇】StartAllBack更改win11任务栏设置 Windows11目前不支持更改任务栏位置固定的修改&#xff0c;因为想把任务栏固定到旁边&#xff0c;从而充分利用电脑屏幕位置。之前试过TranslucentTB可以把任务栏透明化&#xff0c;很漂亮&#xff0c;但在分屏操作时…

【Vue3】Vue3+Vite+TS使用npm包引入百度地图

文章目录 Vue3ViteTS引入百度地图一、注册二、安装依赖包三、参考文档四、全局注册五、局部导入六、断网地图的使用八、项目使用成功图片九、使用卫星图 Vue3ViteTS引入高德地图npm包查找地图依赖包 Vue3ViteTS引入百度地图 一、注册 官网&#x1f449;百度地图开放平台 注册…

python---案例分析(1)

标准库 python自带的 第三方库 其他人做出来的 例1: 实现一个日期计算器 EG: 计算2012年2月14日和2016年2月3日之间的差值 使用datetime 1.根据日期构造出datetime类型的变量 2.把两个变量进行相减,得到的结果即为所求 1) 2) 3) 例2: 实现单词逆序 翻转单词顺序 i am a s…

MySQL数据库表的操作

创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 说明&#xff1a; field 表示列名。 datatype 表示列的类型。 character set 字符集&#xff0c;如果没有指…

hutool包下的BeanUtil工具使用、SQL中的and和OR的优先级

SQL中的and和OR的优先级 首先and的优先级大于or&#xff0c;通俗理解其实or查询其实会把条件分为左右两边来查。 如select * from user where id 1 and status 2 or status 3,本来想查询user表中id为1的状态为2或者3的数据&#xff0c;其实只会这样执行&#xff0c;and比or…

大数据分析案例-基于LightGBM算法构建航空公司满意度预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

DOD Digital Engineering Vision 数字工程策略与实施

DOD Digital Engineering Vision DOD的数字工程策略与实施&#xff0c;仅供个人学习使用&#xff0c;不代表个人意见和观点&#xff01;&#xff01;&#xff01; Digital Engineering Strategy and Implementation Ms. Philomena Zimmerman Office of the Under Secretary …

day1

在linux内核中&#xff0c;当用户打开设备文件时&#xff0c;内核中的VFS层会调用设备驱动中的sys_open()函数&#xff0c;在sys_open()函数中&#xff0c;内核会根据文件的inode号判断文件是否存在于文件系统中&#xff0c;如果存在&#xff0c;内核会找到这个文件的文件信息结…

Python:使用钉钉dingtalk发送通知消息

通过钉钉的开放API接口&#xff0c;可以很容易的将消息发送到钉钉dingtalk&#xff0c;比起邮件发送更稳定&#xff0c;及时 文档 官网&#xff1a;https://www.dingtalk.com/API Explorer调试 https://open-dev.dingtalk.com/apiExplorer 目录 方式一&#xff1a;webhook方式…