【数据结构】红黑树实现详解

在本篇博客中,作者将会带领你使用C++来实现一棵红黑树,此红黑树的实现是基于二叉搜索树AVLTree一块来讲的,所以在看本篇博客之前,你可以先看看下面这两篇博客

【C++】二叉搜索树-CSDN博客

【数据结构】AVLTree实现详解-CSDN博客

在这棵红黑树中,我们用到了二叉搜索树的插入规则AVLTree的旋转,所以在本篇博客中,在旋转部分可以直接看AVLTree的博客

目录

一.什么是红黑树 

 1.红黑树的性质

二.红黑树的实现 

1.红黑树结点的定义

2.红黑树类的定义

3.红黑树的插入 

①按二叉搜索树的规则进行插入 

②判断红黑树的性质是否被破坏

③如果被破坏,则进行调整 

cur为红,cur_parent为红,grandparent为黑,uncle为红

cur为红,cur_parent为红,grandparent为黑,uncle为黑\uncle不存在

uncle不存在 

uncle存在且为黑

cur为红,cur_parent为红,grandparent为红,uncle为黑\uncle不存在 

uncle不存在

uncle存在且为黑 

④ 调整代码

⑤最终插入代码 

⑥代码分析

4.红黑树的查找

5.红黑树的修改 

三.测试

四.所有源代码

blog_RBTree.h

test.cpp 

一.什么是红黑树 

红黑树是一棵二叉搜索树,它的结点不是黑的就是红的,其中它有一个非常重要的特点:

通过对任何一条从根到叶子的路径上各个结点的着色控制,保证了红黑树没有一条路径会比最短路径长出两倍,达到接近平衡的特点。 

看到这句话,可能你还云里雾里的,但是不要怕,简单的来说,红黑树的特点就是:找出树中最短的路径最长的路径,其中这条最长路径的长度不会大于最短路径长度的两倍

如下图所示:

在这棵红黑树中,最短的路径是最左边的那条,长度为3,最长的路径是最右边的那条,长度为4,即4 < 2*3,最长的路径不会大于最短路径的两倍

 1.红黑树的性质

那么红黑树是怎么做到上面的这个特点的呢?

那是因为红黑树需要遵循下面这5条性质

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点


对于这5条性质来说,可能有一些很难理解,但是不用怕,我们来解释一下。

对于1、2这两条性质来说,很好理解就字面意思,这里就不做过多的解释,其中最重要的是3、4这两条性质,在下面的红黑树插入讲解中,我们都将会围绕着3、4这两条性质来进行


3.如果一个结点是红色的,那么它的两个孩子一定是黑色的。

        其实这个也很好理解,但是这个很重要。其规则如下图所示。


4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

         这条规则说的是,从任意一个结点开始一直往下走,走遍所有可能,这其中会有很多条可能的路径,但是这些路径都有一个特点,就是每条路径上的黑色结点的个数相同。如下图所示。


5.每个叶子结点都是黑色的(这里的叶子结点指的是空结点),如下图所示。 但是其实这个并不是那么重要,理解就行了。


那么为什么只要遵循上面这五条性质,就能做到红黑树的特点的呢?红黑树的特点是,最长路径的长度不会大于最短路径的长度的两倍

既然红黑树的特点与最短路径最长路径有关,那么我们来把这两条路径分析一下。

最短路径:通过这五条性质,我们可以知道在一棵红黑树中,最短路径上一定全是黑结点。不会再有情况比这种情况要短了。

最长路径: 对于最长路径来说,由于有性质4,所以在最长路径上,它的黑色结点的个数一定和最短路径的黑色结点个数相同。又由于有性质3,对于最长的路径来说,它一定是黑结点,红结点交替的。所以最长的路径的长度不会大于最短路径的两倍

如下图所示

二.红黑树的实现 

知道了红黑树的性质和特点,接下来我们可以来实现一棵红黑树了。 

1.红黑树结点的定义

在定义红黑树的结点时,与普通的树结点不同的是,这里多了一个_parent指针一个记录结点颜色的变量。 

	enum Color
	{
		BLACK,
		RED
	};

	template<class T>
	struct TreeNode
	{
		//成员变量
		T _data;//数据
		TreeNode<T>* _left;
		TreeNode<T>* _right;
		TreeNode<T>* _parent;
		Color _col;//保存结点的颜色

		//构造函数
		TreeNode(const T& data)
			:_data(data)
			,_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_col(RED)//每生成一个新结点,都将结点的颜色默认给红色,至于为什么,后面会解释
		{}
	};

2.红黑树类的定义

对于红黑树类的定义,我们先简单的给出一个构造函数,并将_root根结点赋值为nullptr即可。 

	template<class T>
	class RBTree
	{
		typedef TreeNode<T> Node;
	public:
		//构造函数
		RBTree()
			:_root(nullptr)//给根结点一个nullptr
		{}

	private:
		Node* _root;
	};

3.红黑树的插入 

接下来就是最重要的部分了,红黑树的插入,对于红黑树的插入,我们可以分为两个过程: 

1.按二叉搜索树的插入规则把新结点插入进去先

2.插入新结点后,判断红黑树的性质有没有被破坏,如果没有被破坏,则插入直接结束,如果有被破坏,则进行调整处理


由于插入结点的函数有点长,所以这里把插入函数分开来实现 。下面是函数的名称以及参数

bool insert(const T& data)

①按二叉搜索树的规则进行插入 

二叉搜索树的插入规则就是,先不管三七二十一,先把新结点插入进去再说。 

对于二叉搜索树的插入规则,这里不在多讲,不是很理解的,可以看下面这篇博客,这里面讲到了二叉搜索树的插入实现。我们在这里直接给出代码。

【C++】二叉搜索树-CSDN博客

			//如果树为NULL,则直接将新结点给到_root
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
				return true;
			}

			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)
			{
				if (data > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (data < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else//说明该值已经存在,不进行插入
				{
					return false;	
				}
			}

			//将新结点插入
			Node* newnode = new Node(data);
			if (cur_parent->_data > data)
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}

②判断红黑树的性质是否被破坏

当插入完结点后,就要判断红黑树的性质是否被破坏,如没有,则插入结束,如果有则进行调整处理。 

对于前者没有什么好讨论的,我们在着重于讨论后者。


红黑树的性质:

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色的

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点)

首先我们来思考一个问题,如果新增了一个结点,你希望这个新增的结点是红色还是黑色的呢?

其实我们希望这个新增的结点是红色的,为什么,我们来从红黑树的性质来分析。

当我们新插入一个结点的时候,性质1、2、5是绝对不会被破坏的,这应该很好理解。

被破坏的只有3或者4,当我们插入一个红结点,有可能会破坏性质3,当我们插入一个黑结点,会破坏性质4,如下图所示。

既然不管插入的是红结点还是黑结点,都会破坏红黑树的性质,那么为什么我们要选择插入红结点呢?

其实大家凭感觉来判断,应该都会感觉性质3的破坏更好的调整,而性质4的破坏不好调整,因为对于性质4的破坏,我们去给其他路径新增黑结点显然不合理,所以只有给当前路径减少一个黑结点来解决,那么我们把这个新增的结点给成红色即可。


既然这里已经确定了新增的结点一定为红色,所以对于红黑树的调整,我们都围绕着这个来讲解。 

③如果被破坏,则进行调整 

对于性质3的破坏,我们可以分为下面着三种情况。 

cur为红,cur_parent为红,grandparent为黑,uncle为红

上图是其中的一种插入的情况,其中cur刚好是叶子,但是cur也有可能不是叶子,所以我们可以给出这种情况的抽象图。

对于这种情况,我们的调整过程是这样的:

cur_parentuncle的颜色变为黑色grandparent的颜色变为红色。如下图所示。

但是这样的处理后,还会有可能发生性质破坏,例如grandparent的父结点还是红色,对于这样的情况,我们只需要把grandparent给cur继续向上处理即可。如下图所示。  

cur为红,cur_parent为红,grandparent为黑,uncle为黑\uncle不存在

在这种情况中,又可以分为两种小情况,一种是uncle存在且为黑,一种是uncle不存在

uncle不存在 

先来说说第一种情况,uncle不存在的情况,我们先来看图。 

对于这种情况来说,cur一定是新插入的结点,因为如果cur不是新插入的结点,那么说明是由情况一调整而来的,那么既然是从情况一调整而来的,那么cur的孩子肯定是黑色,但是这样就不符合性质4:从任意结点开始,每条路径的黑色结点的数量相同。 

uncle存在且为黑

接着第二种情况,uncle存在且为黑色,我们先来看图。 

对于这种情况来说,cur一定不是新插入的结点,因为如果cur是新插入的结点,那么在插入cur之前,这棵红黑树违反了性质4:从任意结点开始,每条路径的黑色结点的个数相同。 


基于这两种情况,我们可以给出这它们的抽象图,同时它们的调整方法是一样的。 

对于情况二,我们的处理是对grandparent进行一个右单旋,后调整cur_parent和grandparent的颜色即可。如下图所示。

 

cur为红,cur_parent为红,grandparent为红,uncle为黑\uncle不存在 

情况三看上去与情况二类似,其实可以说是,也可以说不是,首先同样的,情况三也可以分为两种情况,一种是uncle不存在,一种是uncle存在且为黑色。 

uncle不存在

我们直接来看图。

也情况二不同的是,它是一个折线。同样的如果uncle不存在,那么cur一定是新插入的结点。 

uncle存在且为黑 

同样的,我们直接来看图。 

对于这种情况来说,cur一定不是新插入的结点,它是由情况一调整过来的 


基于这两种情况,我们可以给出它的抽象图处理,如下图所示。  

对于情况三,我们的处理是,先对cur_parent进行一个左单旋,左单旋后,要交换cur和cur_parent,后再对grandparent进行一个右单旋,最后再调整颜色,如下图所示。

 


 

走到这里,我们所有的情况都已经讲完了,接下来可以编写我们的调整代码了。 

④ 调整代码

注意,我们上面讲到的三种情况没有覆盖到全部情况,因为还有另外三种情况是上面这三种情况的反方向,如情况一的反方向,如下图所示。 

同样的情况二情况三也有它们的反方向,这里就不在多讲了,逻辑都是一样的。接下来我们可以来编写我们的调整代码了,我们的调整代码可以分为如上图所示的正反向反方向来编写。   

			//调整结点代码
			while (cur_parent != nullptr && cur_parent->_col == RED)
			{
				Node* grandparent = cur_parent->_parent;
				if (grandparent->_left == cur_parent)//正方向
				{
					Node* uncle = grandparent->_right;
					if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;
						//继续向上处理
						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//情况二和情况三
					{
						//这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
						if (cur_parent->_right == cur)//这种情况就是情况三
						{
							//先进行一个左单旋
							RotateL(cur_parent);
							swap(cur, cur_parent);//这里一定要进行交换,不然如果是情况二的,会出现逻辑错误
						}
						//代码走到这里就一定是情况二了
						RotateR(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
				else if (grandparent->_right == cur_parent)//反方向
				{
					Node* uncle = grandparent->_left;
					if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;

						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//反方向的情况二和情况三
					{
						if (cur_parent->_left == cur)
						{
							RotateR(cur_parent);
							swap(cur, cur_parent);
						}
						RotateL(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;

⑤最终插入代码 

		//插入结点
		bool insert(const T& data)
		{
			//如果树为NULL,则直接将新结点给到_root
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
				return true;
			}

			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)
			{
				if (data > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (data < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else//说明该值已经存在,不进行插入
				{
					return false;	
				}
			}

			//将新结点插入
			cur = new Node(data);
			if (cur_parent->_data > data)
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}

			//调整结点代码
			while (cur_parent != nullptr && cur_parent->_col == RED)
			{
				Node* grandparent = cur_parent->_parent;
				if (grandparent->_left == cur_parent)//正方向
				{
					Node* uncle = grandparent->_right;
					if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;
						//继续向上处理
						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//情况二和情况三
					{
						//这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
						if (cur_parent->_right == cur)//这种情况就是情况三
						{
							//先进行一个左单旋
							RotateL(cur_parent);
							swap(cur, cur_parent);
						}
						//代码走到这里就一定是情况二了
						RotateR(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
				else if (grandparent->_right == cur_parent)//反方向
				{
					Node* uncle = grandparent->_left;
					if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;

						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//反方向的情况二和情况三
					{
						if (cur_parent->_left == cur)
						{
							RotateR(cur_parent);
							swap(cur, cur_parent);
						}
						RotateL(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;
		}

⑥代码分析

在我们的插入函数的调整部分中,有下面这段逻辑

			//调整结点代码
			while (cur_parent != nullptr && cur_parent->_col == RED)
			{
				Node* grandparent = cur_parent->_parent;
				if (grandparent->_left == cur_parent)//正方向
                {
                    
                  
                }

有的人可能会有个疑问,就是在这里的grandparent指针中,没有进行做判空处理不会出现grandparent是个空指针的错误吗?

答案是不会的,原因如下: 


首先我们可以确定的是,curcur_parent指针不会是空指针,不然这个while循环也不会进入,且进去这个循环后curcur_parent结点颜色一定为红色。那么既然cur_parent为红色,那么cur_parent就一定不是根结点,因为在红黑树的性质中,根结点一定是黑色的,但是这个时候cur_parent是红色的,说明cur_parent一定有父结点,所以不会出现grandparent为空指针的情况。

4.红黑树的查找

红黑树的查找就比较简单了,就是普通的二叉搜索树的查找方式,这里就不在多解释,不了解的可以去看前面的二叉搜索树的博客。 


		//查找结点
		Node* find(const T& data)
		{
			Node* cur = _root;
			if (data > cur->_data)
			{
				cur = cur->_right;
			}
			else if (data < cur->_data)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
			return nullptr;
		}

5.红黑树的修改 

不是所有红黑树的结点都是可以修改的,只有<key,value>模型的红黑树才能修改,例如STL中的map,而key模型的红黑树是不能修改的,例如STL中的set,我们这里默认实现的是key模型的红黑树,所以在这里是不能修改的,但是如果想要做到修改,可以把这棵红黑树改成<key,value>模型即可,即在TreeNode结构体中,多加一个参数即可。 

三.测试

看到这里,我们的这棵红黑树除了删除都已经搞定了,那么我们如果判断我们写的代码是对的呢?即我们写的这棵红黑树到底是不是红黑树,判断这棵树是不是红黑树的方法就是用红黑树的性质去检查它即可。 


首先,我们再来看一下红黑树的五条性质: 

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色的

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点) 


通过下面这个代码,可以测试一个红黑树是否正确。 

		//判断是否符合红黑树的性质
		bool JudgeTree()
		{
			//空树也是红黑树
			if (_root == nullptr)
			{
				return true;
			}
			//性质1:树结点不是红的就是黑的
			//这条性质就没必要检查的了

			//性质2:根结点一定是黑色的
			if (_root->_col != BLACK)
			{
				cout << "违反性质2:根结点一定是黑色的" << endl;
				return false;
			}

			//性质5:所有叶子结点都是黑色的
			//这个性质也没必要检查

			size_t blackcount = 0;
			Node* cur = _root;
			while (cur != nullptr)//先求出一条路径中的黑色结点的个数
			{
				if (cur->_col == BLACK)
				{
					blackcount++;
				}
				cur = cur->_left;
			}
			size_t k = 0;//用来存储黑色结点的个数
			return _JudgeTree(_root, k, blackcount);//判断性质3和性质4

			//性质3:如果一个结点是红色的,那么它的孩子一定是黑色的
			//性质4:每条路径上的黑色结点的个数都相同
		}

		//判断是否红黑树的子函数
		bool _JudgeTree(Node* root, size_t k, size_t blackcount)
		{
			//当root走到NULL的时候,判断这条路径的黑色结点个数是否==blackcount
			if (root == nullptr)
			{
				if (k == blackcount)
				{
					return true;
				}
				else
				{
					cout << "违反性质4:每条路径上的黑结点个数相同" << endl;
					return false;
				}			
			}

			//如果结点是红色的,判断它的父结点是否也为红色
			Node* root_parent = root->_parent;
			if (root_parent != nullptr && root->_col == RED)
			{
				if (root_parent->_col == RED)
				{
					cout << "违反性质3:如果一个结点是红色的,那么它的孩子一定是黑色的" << endl;
					return false;
				}
			}

			//如果结点是黑色的,则k++
			if (root->_col == BLACK)
			{
				k++;
			}

			return _JudgeTree(root->_left, k, blackcount) && _JudgeTree(root->_right, k, blackcount);
		}

四.所有源代码

blog_RBTree.h

#pragma once
#include<iostream>
#include<time.h>
using namespace std;
namespace blog_RBTree
{
	enum Color
	{
		BLACK,
		RED
	};

	template<class T>
	struct TreeNode
	{
		//成员变量
		T _data;//数据
		TreeNode<T>* _left;
		TreeNode<T>* _right;
		TreeNode<T>* _parent;
		Color _col;//保存结点的颜色

		//构造函数
		TreeNode(const T& data)
			:_data(data)
			,_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_col(RED)//每生成一个新结点,都将结点的颜色默认给红色,至于为什么,后面会解释
		{}
	};

	template<class T>
	class RBTree
	{
		typedef TreeNode<T> Node;
	public:
		//构造函数
		RBTree()
			:_root(nullptr)//给根结点一个nullptr
		{}

		//插入结点
		bool insert(const T& data)
		{
			//如果树为NULL,则直接将新结点给到_root
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
				return true;
			}

			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)
			{
				if (data > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (data < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else//说明该值已经存在,不进行插入
				{
					return false;	
				}
			}

			//将新结点插入
			cur = new Node(data);
			if (cur_parent->_data > data)
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}

			//调整结点代码
			while (cur_parent != nullptr && cur_parent->_col == RED)
			{
				Node* grandparent = cur_parent->_parent;
				if (grandparent->_left == cur_parent)//正方向
				{
					Node* uncle = grandparent->_right;
					if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;
						//继续向上处理
						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//情况二和情况三
					{
						//这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
						if (cur_parent->_right == cur)//这种情况就是情况三
						{
							//先进行一个左单旋
							RotateL(cur_parent);
							swap(cur, cur_parent);
						}
						//代码走到这里就一定是情况二了
						RotateR(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
				else if (grandparent->_right == cur_parent)//反方向
				{
					Node* uncle = grandparent->_left;
					if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
					{
						cur_parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;

						cur = grandparent;
						cur_parent = cur->_parent;
					}
					else//反方向的情况二和情况三
					{
						if (cur_parent->_left == cur)
						{
							RotateR(cur_parent);
							swap(cur, cur_parent);
						}
						RotateL(grandparent);
						cur_parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;
		}

		//查找结点
		Node* find(const T& data)
		{
			Node* cur = _root;
			if (data > cur->_data)
			{
				cur = cur->_right;
			}
			else if (data < cur->_data)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
			return nullptr;
		}

		//判断是否符合红黑树的性质
		bool JudgeTree()
		{
			//空树也是红黑树
			if (_root == nullptr)
			{
				return true;
			}
			//性质1:树结点不是红的就是黑的
			//这条性质就没必要检查的了

			//性质2:根结点一定是黑色的
			if (_root->_col != BLACK)
			{
				cout << "违反性质2:根结点一定是黑色的" << endl;
				return false;
			}

			//性质5:所有叶子结点都是黑色的
			//这个性质也没必要检查

			size_t blackcount = 0;
			Node* cur = _root;
			while (cur != nullptr)//先求出一条路径中的黑色结点的个数
			{
				if (cur->_col == BLACK)
				{
					blackcount++;
				}
				cur = cur->_left;
			}
			size_t k = 0;//用来存储黑色结点的个数
			return _JudgeTree(_root, k, blackcount);//判断性质3和性质4

			//性质3:如果一个结点是红色的,那么它的孩子一定是黑色的
			//性质4:每条路径上的黑色结点的个数都相同
		}

	private:
		//左单旋	
		void RotateL(Node* cur_parent)
		{
			Node* cur = cur_parent->_right;
			Node* cur_left = cur->_left;

			//改变指针的链接关系
			cur_parent->_right = cur_left;
			if (cur_left != nullptr)
			{
				cur_left->_parent = cur_parent;
			}

			cur->_left = cur_parent;
			Node* cur_parent_parent = cur_parent->_parent;
			cur_parent->_parent = cur;

			//旋转完成后要判断cur_parent是否为根
			if (cur_parent_parent != nullptr)//说明cur_parent不是根
			{
				if (cur_parent_parent->_data < cur_parent->_data)
				{
					cur_parent_parent->_right = cur;
					cur->_parent = cur_parent_parent;
				}
				else
				{
					cur_parent_parent->_left = cur;
					cur->_parent = cur_parent_parent;
				}
			}
			else//说明cur_parent是根
			{
				_root = cur;
				cur->_parent = nullptr;
			}

		}

		//右单旋
		void RotateR(Node* cur_parent)
		{
			Node* cur = cur_parent->_left;
			Node* cur_right = cur->_right;

			cur_parent->_left = cur_right;
			if (cur_right != nullptr)
			{
				cur_right->_parent = cur_parent;
			}

			cur->_right = cur_parent;
			Node* cur_parent_parent = cur_parent->_parent;
			cur_parent->_parent = cur;

			if (cur_parent_parent != nullptr)
			{
				if (cur_parent_parent->_data > cur_parent->_data)
				{
					cur_parent_parent->_left = cur;
					cur->_parent = cur_parent_parent;
				}
				else
				{
					cur_parent_parent->_right = cur;
					cur->_parent = cur_parent_parent;
				}
			}
			else
			{
				_root = cur;
				cur->_parent = nullptr;
			}

		}

		//获取根节点
		Node* GetRoot()
		{
			return _root;
		}

		//判断是否红黑树的子函数
		bool _JudgeTree(Node* root, size_t k, size_t blackcount)
		{
			//当root走到NULL的时候,判断这条路径的黑色结点个数是否==blackcount
			if (root == nullptr)
			{
				if (k == blackcount)
				{
					return true;
				}
				else
				{
					cout << "违反性质4:每条路径上的黑结点个数相同" << endl;
					return false;
				}
					

			}

			//如果结点是红色的,判断它的父结点是否也为红色
			Node* root_parent = root->_parent;
			if (root_parent != nullptr && root->_col == RED)
			{
				if (root_parent->_col == RED)
				{
					cout << "违反性质3:如果一个结点是红色的,那么它的孩子一定是黑色的" << endl;
					return false;
				}
			}

			//如果结点是黑色的,则k++
			if (root->_col == BLACK)
			{
				k++;
			}

			return _JudgeTree(root->_left, k, blackcount) && _JudgeTree(root->_right, k, blackcount);
		}

	private:
		Node* _root;
	};

	void Test1()
	{
		srand(time(0));
		RBTree<int> root;
		const int n = 10000;
		for (int i = 0; i < n; i++)
		{
			int input = rand();
			root.insert(input);
		}
		cout << root.JudgeTree() << endl;
	}
}

test.cpp  

#include"blog_RBTree.h"

int main()
{
	blog_RBTree::Test1();

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

填坑-celery正常启动后能收到任务但不执行任务的解决办法

场景 Flask开发中用celery 6正常启动后能收到任务但不执行任务的解决办法&#xff0c;也没有错误提示…… INFO/MainProcess] Task app.add_together[ce406ed8-71b3-49e6-8556-f44bfe66549c] received [2024-06-20 19:38:10,632: INFO/SpawnPoolWorker-36] child process 2244…

【安防天下】模拟视频监控系统——模拟监控系统的构成视频采集设备

文章目录 1 模拟监控系统的构成2 视频采集设备2.1 摄像机相关技术2.1.1 摄像机的工作原理2.1.2 摄像机的分类2.1.3 摄像机的主要参数 2.2 镜头相关介绍2.2.1 镜头的主要分类2.2.2 镜头的主要参数 1 模拟监控系统的构成 模拟视频监控系统又称闭路电视监控系统&#xff0c; 一般…

基于SpringBoot+Vue电影推荐系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

Day 28:2748. 美丽下标对的数目

Leetcode 2748. 美丽下标对的数目 给你一个下标从 0 开始的整数数组 nums 。如果下标对 i、j 满足 0 ≤ i < j < nums.length &#xff0c;如果 nums[i] 的 第一个数字 和 nums[j] 的 最后一个数字 互质 &#xff0c;则认为 nums[i] 和 nums[j] 是一组 美丽下标对 。 返回…

探索FlowUs息流:个人和团队知识管理解决方案|FlowUs稳定保障你的笔记安全无忧

FlowUs息流&#xff1a;稳定运营保障你的笔记安全无忧 在知识管理工具的选择上&#xff0c;稳定性是用户最关心的问题之一。FlowUs息流以其稳定的运营记录&#xff0c;为用户提供了一个可靠的工作环境。我们深知&#xff0c;一个知识管理平台的稳定性直接影响到团队的生产力和…

ACS自助借还服务端模拟工具(3M SIP2协议)

点击下载《ACS自助借还服务端模拟工具&#xff08;源代码&#xff09;》 1. 前言 在当今科技迅猛发展的时代&#xff0c;自助服务系统已成为提升用户体验和运营效率的关键。为了满足自助借还软件辅助开发的需求&#xff0c;我们精心打造了一款功能强大的ACS服务端模拟软件。这…

Ranger配置图片及json文件预览

文章目录 前言下载apt下载pip下载 配置使用json文件预览方法一 修改scope用cat预览方法二 安装jq预览配置ranger 图片文件预览方法一 使用img2txt预览方法二 使用fim预览配置ranger 总结 前言 本文主要讲解Ranger12如何配置json及图片的预览设置&#xff0c;如下是ranger的介绍…

UniApp 开发微信小程序教程(二):下载安装微信开发者工具

文章目录 一、微信开发者工具简介二、下载安装微信开发者工具1. 下载微信开发者工具步骤&#xff1a; 2. 安装微信开发者工具Windows 系统&#xff1a;Mac 系统&#xff1a; 3. 配置微信开发者工具登录微信开发者工具&#xff1a;新建项目&#xff1a; 4. 预览和调试预览&#…

基于一种改进熵方法的旋转机械故障诊断模型(MATLAB)

熵的概念起源于热力学&#xff0c;1884年&#xff0c;玻尔兹曼定义熵&#xff0c;用以描述分子热运动的无序性和混乱度。1948年&#xff0c;Shannon在其发表的《AMathematicalTheoryofCommunication》中提出香农熵&#xff0c;首次将“熵”引入信息度量范畴&#xff0c;为信息论…

眼见不一定为实之MySQL中的不可见字符

目录 前言 一、问题的由来 1、需求背景 2、数据表结构 二、定位问题 1、初步的问题 2、编码是否有问题 3、依然回到字符本身 三、深入字符本身 1、回归本质 2、数据库解决之道 3、代码层解决 四、总结 前言 在开始今天的博客内容之前&#xff0c;正在看博客的您先来…

ENVI实战—一文搞定非监督分类

实验1&#xff1a;使用isodata法分类 目的&#xff1a;学会使用isodata法开展非监督分类 过程&#xff1a; ①导入影像&#xff1a;打开ENVI&#xff0c;按照“文件→打开为→光学传感器→ESA→Sentinel-2”的顺序&#xff0c;打开实验1下载的哨兵2号数据。 图1 ②区域裁剪…

【Oracle篇】Oracle数据库坏块处理:rman修复坏块实践与案例分析(第七篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

分层Agent

分层Teams 分层Agent创建tool研究团队工具文档编写团队工具 通用能力定义Agent团队研究团队文档编写团队 添加图层 分层Agent 在前面的示例&#xff08;Agent管理&#xff09;中&#xff0c;我们引入了单个管理节点的概念&#xff0c;用于在不同工作节点之间路由工作。 但是&a…

ppt转换word文档怎么操作?6个软件让你自己轻松转换文件

ppt转换word文档怎么操作&#xff1f;6个软件让你自己轻松转换文件 将PPT文件转换为Word文档是一项常见的任务&#xff0c;可以通过多种软件和在线工具来实现。以下是六款常用的软件和工具&#xff0c;它们可以帮助您轻松地将PPT文件转换为Word文档&#xff1a; 1.迅捷PDF转换…

②-Ⅱ单细胞学习-组间及样本细胞比例分析(补充)

数据加载 ①单细胞学习-数据读取、降维和分群_subset函数单细胞群-CSDN博客‘ #2024年6月20日 单细胞组间差异分析升级# rm(list = ls()) library(Seurat)#数据加载(在第一步已经处理好的数据) load("scedata1.RData")#这里是经过质控和降维后的单细胞数据 tabl…

STM32单片机-FLASH闪存

STM32单片机-FLASH闪存 一、FLASH简介二、FLASH工作原理三、读写内部FLASH四、读取芯片ID 一、FLASH简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程读写FLASH的用途&…

2024年6月20日 (周四) 叶子游戏新闻

超市播音系统: 定时播放不同音乐 强制卸载软件: 一款强制卸载软件 免费多人沙盒游戏《宝藏世界》推出更新“潮起潮落”&#xff0c;带来全新克苏鲁风冒险准备好迎接一场超凡的冒险吧&#xff0c;MMORPG发行商gamigo宣布《宝藏世界》的最新更新&#xff1a;“潮起潮落”。这次更…

计算机编码以及URL转码

目录 一、计算机编码 1.ASCII编码 2. GB2312编码 3.GBK编码 4.UTF-8编码 二、URL转码 1.encodeURI和decodeURI 2.encodeURIComponent 和 decodeURIComponent 三、Base64 一、计算机编码 在计算机中&#xff0c;所有的数据在存储和运算时都要使用二进制数表示&#xf…

Redis 网络模型

一、用户空间和内核空间 1.1 linux 简介 服务器大多采用 Linux 系统&#xff0c;这里我们以 Linux 为例来讲解&#xff0c;下面有两个不同的 linux 发行版&#xff0c;分别位 ubuntu 和 centos&#xff0c;其实发行版就是在 Linux 系统上包了一层壳。 任何 Linux 发行版&#…

【ARM】PK51如何将BL51链接器切换成LX51链接器

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决客户在使用PK51进行项目研发的时候&#xff0c;想要使用LX51链接器进行使用。 2、 问题场景 客户在使用51芯片进行开发的时候&#xff0c;发现工程中使用的是BL51链接器&#xff0c;而不是LX51链接器&#xff…