带你10分钟学会红黑树

news2024/11/17 23:48:51

前言:

     我们都知道二叉搜索树,是一种不错的用于搜索的数据结构,如果二叉搜索树越接近完全二叉树,那么它的效率就会也高,但是它也存在的致命的缺陷,在最坏的情况下,二叉搜索树会退化成为单链表,这时,二叉搜索树也就丧失了它的搜索能力。因此为了解决它的问题,后面就有人提出了两种改进结构AVL树和红黑树,让我们一起来学习一下其中的一种改进方案:红黑树吧!

目录

1.什么是红黑树

2.红黑树的特点

3.红黑树的模拟实现

        3.1红黑树的节点结构

        3.2红黑色的插入操作

        3.3红黑色的验证

        3.4红黑树的查找

        3.5红黑树的删除 

                3.5.1删除详解 

                3.5.2删除的代码实现 

         3.6测试代码

        3.7全部代码 

4.红黑树与AVL树的比较  

5.红黑树的应用


        3.2红黑色的插入操作

        3.3红黑色的验证

        3.4红黑树的查找

4.红黑色与AVL树的比较  

5.红黑色的应用


1.什么是红黑树

         红黑树实际上就是一种二叉搜索树,但在每个节点上都增加了一个存储位表示节点的颜色可以是red或者black通过对任意一条从根节点到叶子节点的路径上各个节点着色方式的限定,红黑树确保没有一条路径比其它路径长出两倍。因而是接近平衡的。

        一颗二叉搜书树,越平衡搜索时效率就越高,显然红黑色的平衡性要比AVL树略差,但是经过大量的实验证明红黑色的效率还是不错的,仍能达到O(logN),这个我也不是特别了解只是在别的树上看到的,我没有做过这种类似的大量的实验来证明它的性能,总之:虽然红黑树不是严格的平衡树,但是它的效率也是很高的,不管怎么样你要知道它的时间复杂度是小于2O(logN) !

2.红黑树的特点

        1.每个节点不是红色就是黑色

        2.根节点是黑色

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

        4.对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。   

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

        一共有五条,但是归结起来就三点:1.根节点是黑色的,2.没有连续的红色节点,3. 叶子节点(空节点)是黑色的。

         咋一看红黑树怎么这么多要求呢,看的人头都是晕的,简单来分析一下,首先每个节点都存在两种颜色,或者是红色或者是黑色,凡是空节点都是黑色的也就是这里说的叶子节点。根节点是黑色的。这第1,2,5看起来还好理解,第3,4就不是很好理解了。第3:说明红色节点是不连续的,而第4:说明每个路径上面的黑色节点是相同的,比如:从根开始到1这个节点到叶子节点的黑色节点的个数是和过6这个节点到叶子节点的黑色节点的个数是相同的都是3个。因为如果满足上述的这些条件,那么就可以保证最长路径上的节点个数不会超过最短路径上的节点个数的两倍。

        它是如何保证的最长路径是小于等于最短路径的呢?因为叶子节点是黑色的,根节点也是黑色的,且没有连续的红色节点,那么最长的路径一定是黑红相间的,而最短的路径一定是都是由黑色节点组成的。 每条路径上黑色节点的个数是相同的,那么最长路径的节点数减去最短路径的节点数一定是最长路径上红色节点的个数,在上述的种种规定下,一条路径上的红色节点数一定是小于等于黑色节点的个数的,所以就保证了,最长路径上一定小于或者等于2倍的最短路径。

3.红黑树的模拟实现

       实现红黑树,对于平衡树的旋转操作是必须要了解的,所以如果对平衡树的旋转不了解的同学建议先话三分钟了解一下平衡树  的旋转。(剩下的6分钟应该还是可以学会红黑树的...)

        3.1红黑树的节点结构

               红黑树是二叉搜索树的结构,它的每个节点都是由节点存储的值,节点的指针和表示节点的颜色的枚举类型组成的。且树的结构是三叉链,这样结构方便从子节点逆着寻找父节点

当然还可以有其它的实现方式。 

    //红黑树的节点有两种状态
	//RED-BLACK
	enum Colour
	{
		BLACK,
		RED,
	};
	template<class K,class V>
	struct RBTreeNode
	{
		//红黑色是三叉链
		//+节点的状态+存储在节点中的指针
		RBTreeNode<K, V> _left;
		RBTreeNode<K, V> _right;
		RBTreeNode<K, V> _prev;

		pair<K, V> _Key;
		Colour _col;
		//构造函数
		RBTreeNode(const pair<K,V> data)
			:_left(nullptr)
			,_right(nullptr)
			,_prev(nullptr)
			,_Key(data)
			,_col(RED)
		{ }
	};

        3.2红黑色的插入操作

         由于性质的约束,红黑色新插入的节不点可以是黑色的,红黑树新插入的节点都是红色的为什么呢?如果新插入点节点是黑色的会破坏第4点性质,破坏后是不容易修正的,但是如果插入的节点是红色的节点,这时候如果它的父节点是红色的就破坏了第3点性质,所以这时候就要通过一些旋转和节点的变色来进行处理,使得它满足第3点性质。简而言之就是如果插入的是黑色的节点是一定会破坏的第4点的,插入的是红色的节点只是有可能会破坏第3点,并且要保证每条路径上有相同数量的黑色节点是不太容易的,而让红色的节点不连续是比较简单的,两害相权取其轻,所以在插入时选择插入红色的节点。以下我将会列出所出现的所有情况。另外为了叙述方便我们给新插入的节点标为C,父节点为P,祖父节点为G,叔叔节点为U

       

        情况1:树为空,此时需要将根节点赋值给新插入的节点,并且将根节点的颜色变黑,(恢复第1点性质)。

        情况2:插入节点后,这个节点的父节点是黑色的,此时不需要处理。结束插入。

        前面两种其实没有什么好看的,因为很简单,需要注意的是接下来的三种。

        情况3:插入节点后,C为红,P为红,如果U为红,此时G一定存在且为黑(不然在插入之前就不符合红黑色的特点了),这种情况一定破坏第三点,需要将P和U变为黑色,G变为红色,然后继续进行判断属于哪种情况,这种状态不需要旋转只要进行变色处理就可以了。 

        情况四:C为红,P为红,U不存在或者U为黑。C为P的左孩子,且P为G左孩子(或者C为P的右孩子,且P为G右孩子,总之是同向的),这时候进行右单旋左单旋)。然后将P变为黑色,G变为红色就结束了。如图:

        情况五:C为红,P为红,U不存在或者U为黑。C为P的左孩子,P是G的右孩子(或者C为P的右孩子,P为G的左孩子,总之它们是反向的),这时候进行进行两次变化操作,首先在P的位置进行左旋,这时候不变节点的颜色,(这时候实际上已经转换为情况4,按照情况4 的操作就行)然后再G的位置进行右旋,将G变为红色,将C变为黑色。

        如图:        实际上由于P,C都为红色,旋转1次后,不违反第4点性质。然后就变为情景4.

         插入节点后检测是否破坏红黑树的性质。 显然情况1,2是没有破坏红黑树的性质的。只有情况3,4,5破坏了红黑树的性质。

        实现代码:

	    bool Insert(const V& data)
		{
			//如果根节点为空直接插入并初始化根节点
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;//将根节点变黑
			}
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				//新插入的节点都是红色的,
				if (cur->_data == data)
				{
					//节点中存在data不需要进行插入
					return false;
				}
				else if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_left;
				}
			}
			//申请新节点进行插入
			cur = new Node(data);
			cur->_parent = parent;
			cur->_col = RED;
			if (cur->_data > parent->_data)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
		
			while (parent && parent->_col == RED)//插入cur之后只有当parent存在且为红色才需要处理
			{
				//如果parent存在且为红色,那么说明它的parent的父亲一定存在,且为黑色
				Node* grandfather = parent->_parent;
				//这时候需要根据uncle的颜色进行判断来区分是哪种情况然后再进行处理
				if (parent == grandfather->_left)
				{
					Node* uncle = grandfather->_right;
					if (uncle && uncle->_col == RED)
					{
						//如果舅舅存在且为红,需要将parent和uncle都变为黑色,然后再将grandfather变为红色
						//继续迭代处理看是属于哪种情况
						uncle->_col = parent->_col = BLACK;
						grandfather->_col = RED;
						//迭代
						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						//如果uncle不存在或者uncle存在且为黑色,就要进行其它的处理了
						if (cur == parent->_right)//parent == grandfather->_left
						{
							//这时候需要进行双旋
							//先进行一次左旋
							RotateL(parent);//在parent位置
							::swap(cur, parent);//交换两个指针
							//通过上面的处理可以将左右双旋变为右单旋
						}
						//这里处理的就是右单旋的情况,
						RotateR(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
				else//parent == grandfather->_right
				{
					Node* uncle = grandfather->_left;
					//这里也是一样的,分为uncle为红色和不为红色两种情况
					if (uncle && uncle->_col == RED)
					{
						//如果uncle为红色,就要将parent和uncle都变为黑色,将grandfather变为红色
						//然后迭代进行其它处理
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;
						//迭代
						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						//这里处理的是uncle为黑色或者uncle不存在的情况
						//需要旋转+变色进行处理
						if (cur == parent->_left)//parent == grandfather->_right
						{
							//右单旋
							RotateR(parent);
							::swap(cur, parent);
							//这样处理就可以将需要双旋的场景转化为单旋
						}
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;//不管怎么样最后将根节点变为黑色
			return true;
		}

		//左右单旋的代码
		void RotateL(Node*parent)
		{
			//左单旋
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if (subRL)//subRL有可能不存在
				subRL->_parent = parent;
			Node* pParent = parent;
			subR->_left = parent;
			parent->_parent = subR;
			//三叉链的链接会复杂一点
			if (parent == _root)//判断parent是否为根
			{
				//更新根节点
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				subR->_parent = pParent;
				if (parent == pParent->_left)
				{
					pParent->_left = subR;
				}
				else
				{
					pParent->_right = subR;
				}
			}
		}
		void RotateR(Node* parent)
		{
			//右单旋
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;
			Node* pParent = parent->_parent;
			subL->_right = parent;
			parent->_parent = subL;
			//三叉链的链接会复杂一点

			if (parent == _root)
			{
				//更新根节点
				_root = subL;
				_root->_parent = nullptr;
			}
			else
			{
				subL->_parent = pParent;
				if (parent == pParent->_left)
				{
					pParent->_left = subL;
				}
				else
				{
					pParent->_right = subL;
				}
			}
		}

        3.3红黑色的验证

        红黑树的检测分为两步:

        1.检测其是否满足二叉搜索树(中序遍历是否有序)

        2.检测其是否满足红黑树的性质

        void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_Inorder(root->_left);
			cout << root->_data << endl;
			_Inorder(root->_right);
		}
		void Inorder()
		{
            if(_root)
		    {
                _Inorder(_root);
            }
		}
        Node* GetRoot()//获取根节点
		{
			return _root;
		}
		bool IsValidRBTree()
		{
			Node* root = GetRoot();
			if (root == nullptr)
			{
				return true;//空树也是红黑树
			}
			if (_root->_col != BLACK)
			{
				cout << "违反性质2" << endl;
				return false;
			}
			//获取任何一条路径上的黑色节点数
			Node* cur = root;
			size_t blackSize = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
				{
					++blackSize;
				}
				cur = cur->_right;
			}
			size_t k = 0;
			return _IsValidRBTree(root, k, blackSize);
		}
		bool _IsValidRBTree(Node* pRoot, size_t k, size_t blackSize)
		{
			//走到空的时候判断k和black和是否相等
			if (pRoot == nullptr)
			{
				if (k != blackSize)
				{
					cout << "违反性质4" << endl;
					cout << k << " " << blackSize << endl;
					return false;
				}
				return true;
			}
			if (BLACK == pRoot->_col)
			{
				++k;
			}
			//检测当前节点与其双亲节点是否为红色
			Node* parent = pRoot->_parent;
			if (parent && parent->_col == RED && pRoot->_col == RED)
			{
				cout << "违反性质3:没有连在一起的红节点" << endl;
				return false;
			}
			return _IsValidRBTree(pRoot->_left, k, blackSize) 
				&& _IsValidRBTree(pRoot->_right, k, blackSize);
		}

        3.4红黑树的查找

        红黑树的查找和搜索树的查找一样只需要按照搜索树的规则进行查找就可以了。 

	    Node* Find(const K& key)
		{
			if (_root == nullptr)
			{
				return _root;
			}
			Node* cur = _root;
			while (cur)
			{
				if (cur->_data == key)
				{
					break;
				}
				else if (key > cur->_data)
				{
					//去右边找
					cur = cur->_right;
				}
				else
				{
					//去左边找
					cur = cur->_left;
				}
			}
			//找不到返回cur,cur此时走到nullptr
			return cur;
		}

        3.5红黑树的删除 

                3.5.1删除详解 

        为什么要将红黑树的删除放在最后呢,这是有原因的,如果将红黑树的删除放前面那么十分钟肯定就学不会了。

        因为红黑树的删除是有一定的难度的,所以要做好心理准备。 

        首先和插入一样,因为红黑树是平衡二叉树所以它的删除和二叉搜索树的删除是有些一样的地方的,如果不了二叉搜索树的删除可以参考:二叉搜索树

        和插入一样,首先删除也分为这样的几步:

        第一步:查找要删除的值data是否在红黑树中。

        第二步:如果在要删除值在红黑树中 ,就要判断这个值是位于红黑树的哪个位置,因为在搜索树中不同位置的值的处理是不同的。

        第三步:进行调整。

        这样看是不是觉得红黑树的删除挺简单的呢,至少看起来是不难的!

         

        接下来让我们先详解第二步,我们就将要删除的值称为del,(这样方便写,也方便看) del有以下几种情况:

        情况1del所在的节点左右子树都存在,那么这时候就需要寻找替代节点将,删除替代节点就行了。

        情况2del的左子树或者右子树是空的。

        情况3del是叶子节点。

        接下来我们就对这几种情况作具体的分析。

        情况1详解:如何寻找替代节点呢,其实如果了解搜索树的删除的朋友应该知道,无非就是去找del的前驱或者后继节点,然后用del的前驱或者后继节点的值,覆盖del就行了,那么下面要介绍两个概念,前驱节点和后继节点。

        前驱节点:就是比当前节点值小的节点中值最大的节点,也就是它左子树中的最右节点。

        后继节点:就是比当前节点值大的节点中值最小的节点,也就是它右子树中的最左节点。

        寻找替代节点的详解图: 

        这时候就将情况1转化为情况2或者情况3了。因为替代节点就两种情况 ,要么是一个子树为空的节点(情况2),要么是叶子节点(情况3)。

        情况2详解:

        如果左右子树中有一个为空,另一个不为空。

        如果左子树为空,右子树不为空,那么右子树肯定是红色节点,因为如果右子树为黑色的节点肯定会违背红黑树的性质:每条路径上都有相同路径的黑色节点。 

        右子树为空也是一样的,它左边的节点肯定是红色的节点,只需要将它不为空的节点作为替代节点,对替代节点进行操作就行了。

        图示:

        实际上这是将情况2转化为情况3。 

        情况3详解

        如果叶子节点是黑色的是不能直接删除的,需要进行处理。 也就是第三步进行调整。

        如果叶子节点是红色的,还需要进行处理吗?不需要的直接删除就行了 。

        第三步调整详解 

        看了这么多了,怎么样是不是感觉红黑树其实也是比较简单的呢,不过接下来的调整也要分为好几种情况,你可别把自己绕晕了。 

        调整大概可以分成三种情况,为了接下来好描述,我将当前需要调整的节点定义为delNode,简称D。它的兄弟定义为brother,简称B,它的双亲定义为parent,简称P。 它兄弟的左孩子简称为BL,它兄弟的右孩子简称为BR,怎么样这么一堆名称是不是已经让你晕了,其实记首字母就行了,在代码实现里面没有用缩写应该很容易看懂。

        如图:                情况1:

       这个节点是根节点,将根节点变黑就行了。

        如图:

                情况2:

        B是黑色的。

        这时候又要分成两种情况了。B的孩子存在(BL或者BR),或者B的孩子不存在。

                        B的孩子存在:

        如果B的孩子存在,要根据B的孩子和B的位置以及B的位置和P的位置的相对关系来判断。分为四种情况:

        情况1:B在P的左边,如果BL存在,此时在P的位置进行左旋:然后变色,将B变成P的颜色将P变为黑色,BL变成黑色。因为其实这种情况下P的颜色是不确定的。

         

        情况2:B在P的左边,如果BR存在,此时要进行两次旋转,在B的位置进行左旋,然后在P的位置进行右旋,将BR变为P的颜色,将P变为黑色。

        情况3:B在P的右边,如果BR存在,此时在P的位置进行左旋,然后将B的颜色变成P的颜色,将P和BR的颜色变成黑色。

         

        情况4:B在P的右边,如果BL存在,此时要进行两次旋转。在B的位置右旋,在P的位置左旋,将BL变为P的颜色,将P变为黑色。

 

                        B的孩子不存在:

         理论上这种是最复杂的因为B的孩子不存在,B为黑色,D也为黑色,那么此时P就有两种情况,P为红色和P为黑色。

        如果P为红色,那么此时将B变为红色,P变为黑色就结束了。

     如图:

          如果P为黑色,就需要以P为调整节点继续向上进行调整。我们需要将B和D都变为红色然后以P为调整节点,继续判断是属于哪种情况再进行调整。如图: 

        注意:这里要小心一种特殊情况,那就是P是根节点(_root),只需要将B变成红色结束即可。

         

        看着很复杂但是写成代码却是最简单的,这里卖个关子,我想你应该可以猜到是为什么。哈哈哈哈。 

                情况3: 

         B是红色的,那么必然存在BL和BR且都是黑色的。(为什么,根据红黑树的形状得来的后面如果不太理解,看图你就知道了) 

        这时候就需要判断B和P的关系了,如果B在P的左边,进行右旋就行了,如果B在P的右边进行左旋就行了,旋转完需要变色。(或者先变色再旋转也可以)变色情况具体看图分析:

        如图:

                3.5.2删除的代码实现 

        这里只贴了删除部分的代码,如果有不理解的地方可以参看注释,其实代码还可以简化一些的,但是那样子代码的可读性会降低很多,所以就没有那么写,好的代码不是越精简越好,可读性,可维护性等都很重要。 

//红黑树的删除,比插入还要难一些
//主要分为下面的几种情况
bool Erase(const K& data)
{
	//情况1:红黑树中不存在要删除的节点,返回false
	//情况2:只剩下根节点,删除根节点将根节点置空
	//情况3:要删除的节点左右子树都存在
	//情况4:要删除的节点只有左子树或者右子树
	//情况5:删除的是叶子节点
	Node* delNode = Find(data);//查找要删除的节点
	if (delNode == nullptr)
	{
		//情况1:红黑树中不存在要删除的节点,返回false
		cout << "节点不存在" << endl;
		return false;//这里判断红黑树中是否存在这个节点
	}
	if (_root == delNode
		&& _root->_left == nullptr
		&& _root->_right == nullptr)
	{
		//情况2:只剩下根节点,删除根节点将根节点置空
		delete _root;
		_root = nullptr;
		return true;
	}
	if (delNode->_left && delNode->_right)
	{
		//第三种情况是它的左右子树都不为空,那么这时候我们需要找替代节点,
		//然后将第三种情况转化为第四种或者第五种情况
		Node* replace = getReplaceNode(delNode);
		delNode->_data = replace->_data;
		delNode = replace;
	}
	if ((delNode->_left && delNode->_right == nullptr)
		|| (delNode->_left == nullptr && delNode->_right))
	{
		//第四种情况删除的这个节点有一个子节点为空另一个子节点不为空,
		//那么要删除的当前节点一定是黑色的节点,并且它的不为空的节点一定是红色节点,
		//否则的话将不满足红黑树的性质
		//将第四种情况转化为第五种情况
		Node* replace = delNode->_left != nullptr ? delNode->_left : delNode->_right;
		delNode->_data = replace->_data;
		delNode = replace;
	}
	if (delNode->_left == nullptr
		&& delNode->_right == nullptr)
	{
		//走到这里说明删除的这个节点是叶子节点,如果叶子节点是红色的那么直接删除就好了,
		//但是叶子节点是黑色的话就需要进行旋转处理了
		if (delNode->_col == BLACK)
		{
			//对黑色叶子节点进行处理
			AdjustDown(delNode);//调用函数进行处理
		}
		//删除节点
		Node* parent = delNode->_parent;
		if (parent->_left == delNode)
		{
			parent->_left = nullptr;
		}
		else
		{
			parent->_right = nullptr;
		}
		delNode->_parent = nullptr;
		delete delNode;
		delNode = nullptr;
	}
	return true;
}

//在删除的节点是叶子节点且节点是红色的情况下的调整逻辑
void AdjustDown(Node* delNode)
{
	//分成三种大的情况:
	//情况1:如果这个节点是根节点,将它变黑返回就行了
	//情况2:它的兄弟节点为黑色
	//情况3:它的兄弟节点为红色
	Node* parent = delNode->_parent;
	Node* brother = (delNode == parent->_left ? parent->_right : parent->_right);
	if (_root == delNode)
	{
		//情况1:如果这个节点是根节点,将它变黑返回就行了
		_root->_col = BLACK;
		return;
	}
	else if (brother && brother->_col == BLACK)
	{
		//情况2:它的兄弟节点为黑色
		//这个又要分为两种情况
		//情况1:brother不存在左右孩子
		//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点

		if (brother->_left == nullptr
			&& brother->_right == nullptr)//情况1:brother不存在左右孩子
		{
			//这时候又要分成两种情况
			//情况1:parent为红色,这时候只需要将brother变为红色,parent变为黑色即可
			//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整
			if (parent->_col == RED)
			{
				parent->_col = BLACK;
				brother->_col = RED;
			}
			else//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整
			{
				if (parent == _root && brother->_col == BLACK)
				{
					//说明此时只有三个节点且全部为黑
					//将brother变红然后返回
					brother->_col = RED;
					return;
				}
				else
				{
					//需要将brother变为红色,
					//以parent为调整节点继续调整
					brother->_col = RED;
					AdjustDown(parent);
				}
			}
		}
		else//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点
		{
			//这时候因为brother是红色的,delNode也是红色的,且brother存在孩子
			//那么这时候就有四种情况:
			//情况1:brother是parent的左孩子,并且brother存在左孩子
			//情况2:brother是parent的左孩子,并且brother存在右孩子
			//情况3:brother是parent的右孩子,并且brother存在右孩子
			//情况4:brother是parent的右孩子,且brother存在右孩子
			if (brother == parent->_left && brother->_left)
			{
				//情况1:brother是parent的左孩子,并且brother存在左孩子
				Node* brotherLeft = brother->_left;
				if (brotherLeft)
				{
					//在parent的位置右旋
					//brother的颜色变为parent的颜色
					//brotherLeft和parent都变为黑色
					brother->_col = parent->_col;
					parent->_col = brotherLeft->_col = BLACK;
					RotateR(parent);
				}
				else if (brother->_right) // 情况2:brother是parent的左孩子,并且brother存在右孩子
				{
					Node* brotherRight = brother->_right;
					//这时候要进行双旋,先左旋再右旋,在进行变色
					//在brother的位置进行左旋,然后在parent的位置进行右旋
					//brotherRight变成parent的颜色,parent变成黑色
					brotherRight->_col = parent->_col;
					parent->_col = BLACK;
					RotateL(brother);
					RotateR(parent);
				}
			}
			else if (brother == parent->_right && brother->_right)
			{
				//情况3:brother是parent的右孩子,并且brother存在右孩子
				Node* brotherRight = brother->_right;
				if (brotherRight)
				{
					//brother是parent的右孩子,且brother存在右孩子,进行左旋在parent的位置
					//brother的颜色变为parent的颜色,
					//parent和brotherRight变为黑色
					brother->_col = parent->_col;
					parent->_col = brotherRight->_col = BLACK;
					RotateL(parent);
				}
				else if (brother->_left)//情况4:brother是parent的右孩子,且brother存在左孩子
				{
					Node* britherLeft = brother->_left;
					//brother是parent的右孩子,且brother存在左孩子,
					//先在brither处右旋,再在parent处左旋
					//变色:parent变黑,brotherLeft变为parent的颜色
					britherLeft->_col = parent->_col;
					parent->_col = BLACK;
					RotateR(brother);
					RotateL(parent);
				}
			}
		}
	}
	else if (brother && brother->_col == RED)
	{
		//情况3:它的兄弟节点为红色
		//如果brother是红色的,那么它肯定存在两个孩子并且两个孩子都是黑色的,
		//不然就不符合红黑树的性质
		// 这时候又分成两种情况:
		//如果brother在parent的左边就右旋,然后将brother变黑,brother的right变红
		//如果brother在parent的右边就右旋,然后brother变黑,brother的left变红
		if (brother == parent->_left)
		{
			Node* brotherRight = brother->_right;
			brotherRight->_col = RED;
			brother->_col = BLACK;
			RotateR(parent);//右旋
		}
		else
		{
			Node* brotherLeft = brother->_left;
			brotherLeft->_col = RED;
			brother->_col = BLACK;
			RotateL(parent);
		}
	}
}
//找一个节点的前驱
Node* prevNode(Node* root)
{
	Node* cur = root->_left;//前驱是左子树中的最大节点,也就是左子树中的最右节点
	while (cur && cur->_right)
	{
		cur = cur->_right;
	}
	return cur;
}
//找一个节点的后继
Node* successNode(Node* root)
{
	Node* cur = root->_right;//后继是右子树中的最小节点,也就是右子树中的最左子树
	while (cur && cur->_left)
	{
		cur = cur->_left;
	}
	return cur;
}
//找替代节点
//替代节点可以选前驱节点中符合条件的,如果前驱节点不符合条件就返回后继节点
Node* getReplaceNode(Node* delNode)
{
	Node* replace = prevNode(delNode);
	if (replace->_col == RED)
	{
		//如果替代节点是红节点直接返回就可以了,这种情况说明这个找到的替代节点是叶子节点
		return replace;
	}
	else if (replace->_left)//如果这个替代节点是黑色的,但是它的左节点存在,
						//那么左节点一定是红色的,因为我们判断的是这棵子树的最右节点,
						//如果左孩子是黑色的,就不满足红黑色,每条路径黑节点的数量相等这个属性
	{
		return replace->_left;
	}
	return successNode(delNode);
}

        到这里红黑树的删除就结束了,不知道你用了多久看完它呢, 不知道看完它你是哪种状态呢,是大彻大悟还是模模糊糊呢,亦或者是感觉脑子有点痒痒的~

         3.6测试代码

         测试其实没有什么好说的。你就找一堆数据进行插入并且删除就好了,最好是足够多的数据这样可以查出一写影藏的BUG,这种树状的结构查看起来比较麻烦,所以我写了一个数层次结构的遍历,这样遇到问题的时候打印一下,找个地方画画看看是哪里出问题了就比较好分析,查看内存的话,能把你绕晕,这也是一种偷懒的做法吧,当然也有一些小工具可以进行测试,但是太麻烦了,懒得搞。

        //这里写一个红黑树的层次遍历,有bug调试起来真的太恶心了
		void Traversal()
		{
			_Traversal(_root);
		}
		void _Traversal(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			//借助队列
			queue<Node*> q;
			q.push(root);
			while (!q.empty())
			{
				Node* cur = q.front();//取队头的数据
				cout << cur->_data;
				Color(cur->_col);
				cout << "左孩子:";Data(cur->_left);
				cout << "右孩子:"; Data(cur->_right);
				cout << "父亲:"; Data(cur->_parent);
				cout << endl;
				q.pop();
				if (cur->_left)
				{
					//将左右子树入队
					q.push(cur->_left);
				}
				if (cur->_right)
				{
					//将右子树入队
					q.push(cur->_right);
				}
			}
		}
		void Data(Node* cur)
		{
			if (cur)
			{
				cout << cur->_data <<"	";

			}
			else
			{
				cout << "NULL" <<"	";
			}
		}
		//将枚举类型打印为颜色
		void Color(enum Colour&col)
		{
			if (col == BLACK)
			{
				cout << "黑色"<<"	";
			}
			else
			{
				cout << "红色" <<"	";
			}
		}
void TestRBTreeErase()
{
	qyy::RBTree<int> rb1;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 20, 30 };
	for (auto e : a)
	{
		rb1.Insert(e);
		cout << "插入" << e << "->" << endl;
	}
	rb1.Traversal();

	rb1.Inorder();
	cout << rb1.IsValidRBTree() << endl;
	
	for (auto e : a)
	{
		rb1.Erase(e);
		cout << "删除" << e << "->" << endl;
	}
	rb1.Inorder();

}

        3.7全部代码 

       // RBTree.hpp

         

#include<iostream>
#include<queue>
using namespace std;
namespace qyy
{
	//用来表示红黑色节点的颜色
	enum Colour
	{
		RED,
		BLACK,
	};
	template<class K>
	struct RBTreeNode
	{
		RBTreeNode<K>* _left;
		RBTreeNode<K>* _right;
		RBTreeNode<K>* _parent;
		K _data;
		Colour _col;
		//构造函数完成对节点的初始化
		RBTreeNode(const K& data)
			:_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_data(data)
			,_col(RED)
		{
		}
	};
	template<class K>
	class RBTree
	{
	public:
		typedef RBTreeNode<K> Node;
		RBTree()
			:_root(nullptr)
		{}
		bool Insert(const K& data)
		{
			//如果根节点为空直接插入并初始化根节点
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;//将根节点变黑
			}
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				//新插入的节点都是红色的,
				if (cur->_data == data)
				{
					//节点中存在data不需要进行插入
					return false;
				}
				else if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_left;
				}
			}
			//申请新节点进行插入
			cur = new Node(data);
			cur->_parent = parent;
			cur->_col = RED;
			if (cur->_data > parent->_data)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			//插入之后进行调整
			while (parent && parent->_col == RED)//插入cur之后只有当parent存在且为红色才需要处理
			{
				//如果parent存在且为红色,那么说明它的parent的父亲一定存在,且为黑色
				Node* grandfather = parent->_parent;
				//这时候需要根据uncle的颜色进行判断来区分是哪种情况然后再进行处理
				if (parent == grandfather->_left)
				{
					Node* uncle = grandfather->_right;
					if (uncle && uncle->_col == RED)
					{
						//如果舅舅存在且为红,需要将parent和uncle都变为黑色,然后再将grandfather变为红色
						//继续迭代处理看是属于哪种情况
						uncle->_col = parent->_col = BLACK;
						grandfather->_col = RED;
						//迭代
						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						//如果uncle不存在或者uncle存在且为黑色,就要进行其它的处理了
						if (cur == parent->_right)//parent == grandfather->_left
						{
							//这时候需要进行双旋
							//先进行一次左旋
							RotateL(parent);//在parent位置
							::swap(cur, parent);//交换两个指针
							//通过上面的处理可以将左右双旋变为右单旋
						}
						//这里处理的就是右单旋的情况,
						RotateR(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
				else//parent == grandfather->_right
				{
					Node* uncle = grandfather->_left;
					//这里也是一样的,分为uncle为红色和不为红色两种情况
					if (uncle && uncle->_col == RED)
					{
						//如果uncle为红色,就要将parent和uncle都变为黑色,将grandfather变为红色
						//然后迭代进行其它处理
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;
						//迭代
						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						//这里处理的是uncle为黑色或者uncle不存在的情况
						//需要旋转+变色进行处理
						if (cur == parent->_left)//parent == grandfather->_right
						{
							//右单旋
							RotateR(parent);
							::swap(cur, parent);
							//这样处理就可以将需要双旋的场景转化为单旋
						}
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
			}
			_root->_col = BLACK;//不管怎么样最后将根节点变为黑色
			return true;
		}
		//这里写一个红黑树的层次遍历,有bug调试起来真的太恶心了
		void Traversal()
		{
			_Traversal(_root);
		}
		void _Traversal(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			//借助队列
			queue<Node*> q;
			q.push(root);
			while (!q.empty())
			{
				Node* cur = q.front();//取队头的数据
				cout << cur->_data;
				Color(cur->_col);
				cout << "左孩子:";Data(cur->_left);
				cout << "右孩子:"; Data(cur->_right);
				cout << "父亲:"; Data(cur->_parent);
				cout << endl;
				q.pop();
				if (cur->_left)
				{
					//将左右子树入队
					q.push(cur->_left);
				}
				if (cur->_right)
				{
					//将右子树入队
					q.push(cur->_right);
				}
			}
		}
		void Data(Node* cur)
		{
			if (cur)
			{
				cout << cur->_data <<"	";

			}
			else
			{
				cout << "NULL" <<"	";
			}
		}
		//将枚举类型打印为颜色
		void Color(enum Colour&col)
		{
			if (col == BLACK)
			{
				cout << "黑色"<<"	";
			}
			else
			{
				cout << "红色" <<"	";
			}
		}
		//左右单旋的代码
		void RotateL(Node*parent)
		{
			//左单旋
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if (subRL)//subRL有可能不存在
				subRL->_parent = parent;
			Node* pParent = parent->_parent;//这里之前写错了,刚开始没事,但是插入到后面就会出错
			subR->_left = parent;
			parent->_parent = subR;
			//三叉链的链接会复杂一点
			if (parent == _root)//判断parent是否为根
			{
				//更新根节点
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				subR->_parent = pParent;
				if (parent == pParent->_left)
				{
					pParent->_left = subR;
				}
				else
				{
					pParent->_right = subR;
				}
			}
		}
		void RotateR(Node* parent)
		{
			//右单旋
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;
			Node* pParent = parent->_parent;
			subL->_right = parent;
			parent->_parent = subL;
			//三叉链的链接会复杂一点

			if (parent == _root)
			{
				//更新根节点
				_root = subL;
				_root->_parent = nullptr;
			}
			else
			{
				subL->_parent = pParent;
				if (parent == pParent->_left)
				{
					pParent->_left = subL;
				}
				else
				{
					pParent->_right = subL;
				}
			}
		}
		//红黑树的删除,比插入还要难一些
		//主要分为下面的几种情况
		bool Erase(const K& data)
		{
			//情况1:红黑树中不存在要删除的节点,返回false
			//情况2:只剩下根节点,删除根节点将根节点置空
			//情况3:要删除的节点左右子树都存在
			//情况4:要删除的节点只有左子树或者右子树
			//情况5:删除的是叶子节点
			Node* delNode = Find(data);//查找要删除的节点
			if (delNode == nullptr)
			{
				//情况1:红黑树中不存在要删除的节点,返回false
				cout << "节点不存在" << endl;
				return false;//这里判断红黑树中是否存在这个节点
			}
			if (_root == delNode  
				&& _root->_left == nullptr
				&& _root->_right == nullptr)
			{
				//情况2:只剩下根节点,删除根节点将根节点置空
				delete _root;
				_root = nullptr;
				return true;
			}
			if (delNode->_left && delNode->_right)
			{
				//第三种情况是它的左右子树都不为空,那么这时候我们需要找替代节点,
				//然后将第三种情况转化为第四种或者第五种情况
				Node* replace = getReplaceNode(delNode);
				delNode->_data = replace->_data;
				delNode = replace;
			}
			if ((delNode->_left && delNode->_right == nullptr)
				|| (delNode->_left == nullptr && delNode->_right))
			{
				//第四种情况删除的这个节点有一个子节点为空另一个子节点不为空,
				//那么要删除的当前节点一定是黑色的节点,并且它的不为空的节点一定是红色节点,
				//否则的话将不满足红黑树的性质
				//将第四种情况转化为第五种情况
				Node* replace = delNode->_left != nullptr ? delNode->_left : delNode->_right;
				delNode->_data = replace->_data;
				delNode = replace;
			}
			if(delNode->_left == nullptr
				&&delNode->_right == nullptr)
			{
				//走到这里说明删除的这个节点是叶子节点,如果叶子节点是红色的那么直接删除就好了,
				//但是叶子节点是黑色的话就需要进行旋转处理了
				if (delNode->_col == BLACK)
				{
					//对黑色叶子节点进行处理
					AdjustDown(delNode);//调用函数进行处理
				}
				//删除节点
				Node* parent = delNode->_parent;
				if (parent->_left == delNode)
				{
					parent->_left = nullptr;
				}
				else
				{
					parent->_right = nullptr;
				}
				delNode->_parent = nullptr;
				delete delNode;
				delNode = nullptr; 
			}
			return true;
		}

		//在删除的节点是叶子节点且节点是红色的情况下的调整逻辑
		void AdjustDown(Node* delNode)
		{
			//分成三种大的情况:
			//情况1:如果这个节点是根节点,将它变黑返回就行了
			//情况2:它的兄弟节点为黑色
			//情况3:它的兄弟节点为红色
			Node* parent = delNode->_parent;
			Node* brother = (delNode == parent->_left ? parent->_right : parent->_right);
			if (_root == delNode)
			{
				//情况1:如果这个节点是根节点,将它变黑返回就行了
				_root->_col = BLACK;
				return;
			}
			else if (brother && brother->_col == BLACK)
			{
				//情况2:它的兄弟节点为黑色
				//这个又要分为两种情况
				//情况1:brother不存在左右孩子
				//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点

				if (brother->_left == nullptr
					&& brother->_right == nullptr)//情况1:brother不存在左右孩子
				{
					//这时候又要分成两种情况
					//情况1:parent为红色,这时候只需要将brother变为红色,parent变为黑色即可
					//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整
					if (parent->_col == RED)
					{
						parent->_col = BLACK;
						brother->_col = RED;
					}
					else//情况2:parent为黑色,这时候就需要以parent为调整节点继续向上进行调整
					{
						if (parent == _root && brother->_col == BLACK)
						{
							//说明此时只有三个节点且全部为黑
							//将brother变红然后返回
							brother->_col = RED;
							return;
						}
						else
						{
							//需要将brother变为红色,
							//以parent为调整节点继续调整
							brother->_col = RED;
							AdjustDown(parent);
						}
					}
				 }
				else//情况2:brother存在左孩子或者右孩子,这时候brother的孩子肯定是红节点
				{
					//这时候因为brother是红色的,delNode也是红色的,且brother存在孩子
					//那么这时候就有四种情况:
					//情况1:brother是parent的左孩子,并且brother存在左孩子
					//情况2:brother是parent的左孩子,并且brother存在右孩子
					//情况3:brother是parent的右孩子,并且brother存在右孩子
					//情况4:brother是parent的右孩子,且brother存在右孩子
					if (brother == parent->_left && brother->_left)
					{
						//情况1:brother是parent的左孩子,并且brother存在左孩子
						Node* brotherLeft = brother->_left;
						if(brotherLeft)
						{
							//在parent的位置右旋
							//brother的颜色变为parent的颜色
							//brotherLeft和parent都变为黑色
							brother->_col = parent->_col;
							parent->_col = brotherLeft->_col = BLACK;
							RotateR(parent);
						}
						else if (brother->_right) // 情况2:brother是parent的左孩子,并且brother存在右孩子
						{
							Node* brotherRight = brother->_right;
							//这时候要进行双旋,先左旋再右旋,在进行变色
							//在brother的位置进行左旋,然后在parent的位置进行右旋
							//brotherRight变成parent的颜色,parent变成黑色
							brotherRight->_col = parent->_col;
							parent->_col = BLACK;
							RotateL(brother);
							RotateR(parent);
						}
					}
					else if(brother == parent->_right && brother->_right)
					{
						//情况3:brother是parent的右孩子,并且brother存在右孩子
						Node* brotherRight = brother->_right;
						if(brotherRight)
						{
							//brother是parent的右孩子,且brother存在右孩子,进行左旋在parent的位置
							//brother的颜色变为parent的颜色,
							//parent和brotherRight变为黑色
							brother->_col = parent->_col;
							parent->_col = brotherRight->_col = BLACK;
							RotateL(parent);
						}
						else if (brother->_left)//情况4:brother是parent的右孩子,且brother存在左孩子
						{
							Node* britherLeft = brother->_left;
							//brother是parent的右孩子,且brother存在左孩子,
							//先在brither处右旋,再在parent处左旋
							//变色:parent变黑,brotherLeft变为parent的颜色
							britherLeft->_col = parent->_col;
							parent->_col = BLACK;
							RotateR(brother);
							RotateL(parent);
						}
					}
				}
			}
			else if (brother && brother->_col == RED)
			{
				//情况3:它的兄弟节点为红色
				//如果brother是红色的,那么它肯定存在两个孩子并且两个孩子都是黑色的,
				//不然就不符合红黑树的性质
				// 这时候又分成两种情况:
				//如果brother在parent的左边就右旋,然后将brother变黑,brother的right变红
				//如果brother在parent的右边就右旋,然后brother变黑,brother的left变红
				if (brother == parent->_left)
				{
					Node* brotherRight = brother->_right;
					brotherRight->_col = RED;
					brother->_col = BLACK;
					RotateR(parent);//右旋
				}
				else
				{
					Node* brotherLeft = brother->_left;
					brotherLeft->_col = RED;
					brother->_col = BLACK;
					RotateL(parent);
				}
			}
		}
		//找一个节点的前驱
		Node* prevNode(Node* root)
		{
			Node* cur = root->_left;//前驱是左子树中的最大节点,也就是左子树中的最右节点
			while (cur && cur->_right)
			{
				cur = cur->_right;
			}
			return cur;
		}
		//找一个节点的后继
		Node* successNode(Node* root)
		{
			Node* cur = root->_right;//后继是右子树中的最小节点,也就是右子树中的最左子树
			while (cur && cur->_left)
			{
				cur = cur->_left;
			}
			return cur;
		}
		//找替代节点
		//替代节点可以选前驱节点中符合条件的,如果前驱节点不符合条件就返回后继节点
		Node* getReplaceNode(Node* delNode)
		{
			Node* replace = prevNode(delNode);
			if (replace->_col == RED)
			{
				//如果替代节点是红节点直接返回就可以了,这种情况说明这个找到的替代节点是叶子节点
				return replace;
			}
			else if(replace->_left)//如果这个替代节点是黑色的,但是它的左节点存在,
								//那么左节点一定是红色的,因为我们判断的是这棵子树的最右节点,
								//如果左孩子是黑色的,就不满足红黑色,每条路径黑节点的数量相等这个属性
			{
				return replace->_left;
			}
			return successNode(delNode);
		}
		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_Inorder(root->_left);
			cout << root->_data << endl;
			_Inorder(root->_right);
		}
		void Inorder()
		{
			_Inorder(_root);
		}
		Node* Find(const K& key)
		{
			if (_root == nullptr)
			{
				return _root;
			}
			Node* cur = _root;
			while (cur)
			{
				if (cur->_data == key)
				{
					break;
				}
				else if (key > cur->_data)
				{
					//去右边找
					cur = cur->_right;
				}
				else
				{
					//去左边找
					cur = cur->_left;
				}
			}
			//找不到返回cur,cur此时走到nullptr
			return cur;
		}
		Node* GetRoot()//获取根节点
		{
			return _root;
		}
		bool IsValidRBTree()
		{
			Node* root = GetRoot();
			if (root == nullptr)
			{
				return true;//空树也是红黑树
			}
			if (_root->_col != BLACK)
			{
				cout << "违反性质2" << endl;
				return false;
			}
			//获取任何一条路径上的黑色节点数
			Node* cur = root;
			size_t blackSize = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
				{
					++blackSize;
				}
				cur = cur->_right;
			}
			size_t k = 0;
			return _IsValidRBTree(root, k, blackSize);
		}
		bool _IsValidRBTree(Node* pRoot, size_t k, size_t blackSize)
		{
			//走到空的时候判断k和black和是否相等
			if (pRoot == nullptr)
			{
				if (k != blackSize)
				{
					cout << "违反性质4" << endl;
					cout << k << " " << blackSize << endl;
					return false;
				}
				return true;
			}
			if (BLACK == pRoot->_col)
			{
				++k;
			}
			//检测当前节点与其双亲节点是否为红色
			Node* parent = pRoot->_parent;
			if (parent && parent->_col == RED && pRoot->_col == RED)
			{
				cout << "违反性质3:没有连在一起的红节点" << endl;
				return false;
			}
			return _IsValidRBTree(pRoot->_left, k, blackSize) 
				&& _IsValidRBTree(pRoot->_right, k, blackSize);
		}
	private:
		Node* _root;
	};
}

4.红黑树与AVL树的比较  

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

5.红黑树的应用

        红黑树作为一种高效搜索的数据结构,在很多地方都有它的运用,比如在C++STL库里面的map/set,mutil_map/mutil_set。其他语言比如Java的库里面也用了红黑树,比如著名的开源操作系统linux中也用到了红黑树等等... 

 

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

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

相关文章

Spring三大核心组件

Spring架构图 Spring三大核心组件分别为&#xff1a;Core、Beans和Context 1. Core&#xff08;核心&#xff09;&#xff1a; 思想&#xff1a;Core组件的核心思想是控制反转&#xff08;IoC&#xff09;和依赖注入&#xff08;DI&#xff09;。它将对象的创建、组装和管理的…

【图像处理】使用各向异性滤波器和分割图像处理从MRI图像检测脑肿瘤(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

番外--Task2:

任务&#xff1a;root与普通用户的互切&#xff08;区别&#xff09;&#xff0c;启动的多用户文本见面与图形界面的互切命令&#xff08;区别&#xff09;。 输入图示命令&#xff0c;重启后就由图形界面转成文本登录界面&#xff1b; 输入图示命令&#xff0c;重启后就由文本…

Netron【.pt转.torchscript模型展示】

Netron是一个模型的展示工具&#xff0c;它有网页版和app版&#xff1a; 网页版&#xff1a;Netron app版&#xff1a;GitHub - lutzroeder/netron: Visualizer for neural network, deep learning, and machine learning models 直接用网页版吧&#xff0c;还不用安装。 它可…

【kubernetes】CRI OCI

1 OCI OCI(Open Container Initiative)&#xff1a;由Linux基金会主导&#xff0c;主要包含容器镜像规范和容器运行时规范&#xff1a; Image Specification(image-spec)Runtime Specification(runtime-spec)runC image-spec定义了镜像的格式&#xff0c;镜像的格式有以下几…

竞赛选题 机器视觉的试卷批改系统 - opencv python 视觉识别

文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…

【SpringBoot】配置文件详解

配置文件详解 一. 配置文件作用二. 配置文件的格式1. properties 配置文件说明①. properties 基本语法②. 读取配置⽂件③. properties 缺点 2. yml 配置⽂件说明①. yml 基本语法②. yml 使用进阶 3. properties VS yml 三. 设置不同环境的配置⽂件 一. 配置文件作用 整个项…

jsbridge实战1:xcode swift 构建iOS app

[[toc]] 环境安装 macOs: 10.15.5 xcode: 11.6 demo:app 创建 hello world iOS app 创建工程步骤 选择&#xff1a;Create a new Xcode project选择&#xff1a;iOS-> single View App填写&#xff1a; project name: swift-app-helloidentifer: smile 包名language: s…

基于Android的香格里拉美食分享APP/美食分享平台/基于android的美食平台

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的APP应运而生&#xff0c;各行各业相继进入信息管理时代&#x…

Youtube视频下载工具分享-油管视频,音乐,字幕下载方法汇总

YouTube视频下载方法简介 互联网上存在很多 YouTube 下载工具&#xff0c;但我们经常会发现自己收藏的工具没过多久就会失效&#xff0c;我们为大家整理的这几种方法&#xff0c;是存在时间较久并且亲测可用的。后续如果这些工具失效或者有更好的工具&#xff0c;我们也会分享…

【多线程进阶】死锁问题

文章目录 前言1. 什么是死锁1.1 死锁的三种典型情况 2. 死锁产生的必要条件3.如何解决死锁问题总结 前言 上文锁策略中, 当谈到可重入锁和不可重入锁时, 我们引入了一个 “死锁” 的概念, 当针对一把不可重入锁进行连续两次的加锁行为时, 就会产生死锁. 本文就重点来讲解一下…

QT调用python程序出现问题Failed to get function

问题描述&#xff1a; 1.python中程序运行正常但在QTC的配置中使用Python.h调用python程序时出现Failed to get function问题&#xff0c;去掉python中某个包的应用就可以&#xff0c;比如&#xff1a; python部分程序&#xff1a; import os.path import pandas as pd如果在…

第十二届2023软件杯国家二等奖赛后感想总结

一&#xff0c;相关链接 软件杯官网&#xff1a;软件杯大赛官网 (cnsoftbei.com) 金蝶赛道&#xff1a;金蝶云苍穹开发者门户 (kingdee.com) 二&#xff0c;个人介绍 首先我是个双非院校的学生&#xff0c;专业为计算机科学与技术&#xff0c;打这个比赛是在大二下的暑假开始的…

【10】c++设计模式——>依赖倒转原则

关于依赖倒转原则&#xff0c;对应的是两条非常抽象的描述&#xff1a; 1.高层模块不应该依赖低层模块&#xff0c;两个都应该依赖抽象。 2.抽象不应该依赖细节&#xff0c;细节应该依赖抽象。 先用人话解释一下这两句话中的一些抽象概念&#xff1a; 1.高层模块&#xff1a;可…

【算法练习Day11】滑动窗口最大值前 K 个高频元素

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 滑动窗口最大值前 K 个高频…

stm32之HAL库操作PAJ75620

一、模块简介 手势模块PAJ7620主要利用IIC或SPI协议来实现数据的传输&#xff0c;本实验用的模块是以IIC来进行信息传输。支持电压从2.8v到3.6v, 正常可以选择3.3v。检测的距离从5到15cm, 可以检测9种手势&#xff0c;包括 右&#xff1a;编码为 0x01左&#xff1a;编码为 0x0…

FDM3D打印系列——黄金弗利萨

大家好&#xff0c;我是阿赵。   国庆期间&#xff0c;在家打印了一个黄金弗利萨的3D模型&#xff0c;和大家分享一下。模型在创想云上面下载的。 使用的设备依然是创想三维的Sermoon V1 Pro&#xff0c;使用的打印材料是创想三维的PLA高速打印素材。 不得不说&#xff0c;…

经典场的量子化

专栏目录: 高质量文章导航-持续更新中-CSDN博客 前置:复指数引起的思考 往指数函数e上丢一个复矢量出来的也是一个复矢量 其定义参考之前的文章群论-李代数_GZVIMMY的博客-CSDN博客 进一步了解下复矢量空间 首先了解下复球面(用一个球表示了所有的复数) 形如[x,y]的二维实…

科普rabbitmq,rocketmq,kafka三者的架构比较

对比 架构对比 从架构可以看出三者有些类似&#xff0c;但是在细节上有很多不同。下面我们就从它们的各个组件&#xff0c;介绍它们&#xff1a; RabbitMQ&#xff0c;是一种开源的消息队列中间件。下面是RabbitMQ中与其相关的几个概念&#xff1a; 1.生产者&#xff08;P…

微软输入法如何打勾和箭头的符号

文章目录 一、打 “√” 符号二、打 “←” 和 “→” 符号 一、打 “√” 符号 选中 “表情包” 图标 选中 “Ω” 符号后&#xff0c;下拉找到 “√” 即可。 微软输入法打 “ ”这个符号直接输入拼音“cha”就行。 二、打 “←” 和 “→” 符号 拼音直接打 “zuo” 或 “…