带你10分钟学会红黑树

前言:

     我们都知道二叉搜索树,是一种不错的用于搜索的数据结构,如果二叉搜索树越接近完全二叉树,那么它的效率就会也高,但是它也存在的致命的缺陷,在最坏的情况下,二叉搜索树会退化成为单链表,这时,二叉搜索树也就丧失了它的搜索能力。因此为了解决它的问题,后面就有人提出了两种改进结构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-BLACKenum 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此时走到nullptrreturn 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:红黑树中不存在要删除的节点,返回falsecout << "节点不存在" << 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:红黑树中不存在要删除的节点,返回falsecout << "节点不存在" << 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此时走到nullptrreturn 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.mzph.cn/news/94447.shtml

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

相关文章

字典与数组第七讲:工作表数据计算时为什么要采用数组公式(一)

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…

谷歌地球引擎GEE账户注册的快速、百分百成功方法

本文介绍免费注册谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;账户的方便、快捷的最新方法&#xff1b;基于这一方法&#xff0c;只要我们创建一个谷歌Cloud Project&#xff0c;就可以直接访问GEE。 GEE在原本&#xff08;大概前几年的时候&#…

Redis-缓存穿透,缓存击穿,缓存雪崩

缓存穿透&#xff0c;缓存击穿&#xff0c;缓存雪崩 缓存穿透处理方案解决方案1 缓存空数据解决方案2 布隆过滤器 缓存击穿处理方案解决方案 1 互斥锁解决方案2 逻辑过期 缓存雪崩处理方案解决方案 1 给不同的key的过期时间设置添加一个随机值&#xff0c;降低同一个时段大量ke…

处理机调度的概念,层次联系以及七状态模型

1.基本概念 当有一堆任务要处理&#xff0c;但由于资源有限&#xff0c;这些事情没法同时处理。 这就需要确定某种规则来决定处理这些任务的顺序&#xff0c;这就是“调度”研究的问题。 2. 三个层次 1.高级调度&#xff08;作业调度&#xff09; 高级调度&#xff08;作业…

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

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

ROS(5)PX4仿真安装及运行

1、配置&#xff0c;提升下载速度 启动 $ cd clash-for-linux$ sudo bash start.sh$ source /etc/profile.d/clash.sh$ proxy_on 关闭 $ cd clash-for-linux$ sudo bash shutdown.sh$ proxy_off 2、安装PX4开源无人机 git clone https://github.com/PX4/PX4-Autopilot.git…

【软考】系统集成项目管理工程师(六)项目整体管理【6分】

一、 前言 1、项目管理三从四得 2、ITO共性总结 1、上一个过程的输出大部分是下-个过程的输入 2、计划和文件是不一样的 (每个输入都有计划和文件) 3、被批准的变更请求约等于计划 4、在执行和监控过程产生新的变更请求(变更请求包括变什么和怎么变&#xff0c;这是变更请求和…

Spring三大核心组件

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

Junit的常用操作

注:本篇文章讲解的是junit5 目录 Juint是什么 Juint需要导入的依赖 Juint常用注解 Junit执行顺序 参数化 断言 测试套件 Juint是什么 Juint 是 Java 的一个单元测试框架. 也是回归测试框架. 使用 Junit 能让我们快速的完成单元测试。 注意&#xff1a;Junit 测试也是程序…

调用gethostbyname实现域名解析(附源码)

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

SystemUI导航栏

SystemUI导航栏 1、系统中参数项1.1 相关开关属性2.2 属性设置代码 2、设置中设置“三按钮”导航更新流程2.1 属性资源覆盖叠加2.2 SystemUI导航栏接收改变广播2.3 SystemUI导航栏布局更新2.4 时序图 android13-release 1、系统中参数项 1.1 相关开关属性 设置->系统->…

测试用例的编写(面试常问)

作者&#xff1a;爱塔居 专栏&#xff1a;软件测试 作者简介&#xff1a;不断总结&#xff0c;才能变得更好~踩过的坑&#xff0c;不能再踩~ 文章简介&#xff1a;常见的几个测试用例。 一、淘宝购物车 二、登录页面 三、三角形测试用例 abc结果346普通三角形333等边三角形334…

安装matplotlib_

安装pip 安装matplotlib 安装完毕 导入出现bug......

【LeetCode热题100】--35.搜索插入位置

35.搜索插入位置 使用二分查找&#xff1a; class Solution {public int searchInsert(int[] nums, int target) {int low 0,high nums.length -1;while(low < high){//注意每次循环完都要计算midint mid (low high)/2;if(nums[mid] target){return mid;}if(nums[mid]…

SpringCloudGateway网关中各个过滤器的作用与介绍

文章目录 RemoveCachedBodyFilterAdaptCachedBodyGlobalFilterNettyWriteResponseFilterForwardPathFilterRouteToRequestUrlFilterWebSocketRoutingFilterNettyRoutingFilterForwardRoutingFilterDispatcherHandler 是什么&#xff1f;⭐如何确定最后的路由路径&#xff1f;下…

聊天记录一句一句出现的视频制作,制作抖音聊天记录视频教程

聊天记录情感中视频制作工具是一款专注于将聊天记录转化为抖音视频的工具。它可以将平淡的聊天截图转化为生动有趣的视频&#xff0c;让你的回忆变得更加具有观赏性和情感共鸣。 首先&#xff0c;聊天记录一句一句出现的视频制作功能是该工具的一大特点。通过这个功能&#xf…

LLVM IR 文档 专门解释 LLVM IR

https://llvm.org/docs/LangRef.html#phi-instruction

[C++随想录] 优先级队列的模拟实现

优先级队列的模拟实现 底层结构初始化向下调整 && 向上调整push && poptop && empty && size源码 底层结构 namespace muyu {template <class T, class Continer std::vector<T>, class Compare less<T> >class priority_…

机器学习(监督学习)笔记

目录 总览笔记内容线性回归梯度下降特征缩放多输出线性回归 逻辑回归二分类与逻辑回归分类任务的性能指标&#xff08;召回率&#xff0c;精度&#xff0c;F1分数等&#xff09;支持向量机SVMK近邻朴素贝叶斯分类器朴素贝叶斯分类器进阶多分类逻辑回归二分类神经网络多分类神经…

代码随想录 Day10 栈与队列 LeetCode T239 滑动窗口的最大值 T347 前K个高频元素

简要介绍一下单调队列和优先级队列的不同 元素顺序的处理&#xff1a;单调队列中&#xff0c;元素的顺序是单调的&#xff0c;也就是说&#xff0c;队列中的元素按照特定的单调性&#xff08;递增或递减&#xff09;排列。这种特性使得单调队列在处理一些问题时非常高效&#…