当前位置: 首页 > news >正文

【高阶数据结构】红黑树详解

文章目录

  • 前言
  • 1. 红黑树的概念及性质
    • 1.1 红黑树的概念
    • 1.2 红黑树的性质
    • 1.3 已经学了AVL树,为啥还要学红黑树
  • 2. 红黑树结构的定义
  • 3. 插入(仅仅是插入过程)
  • 4. 插入结点之后根据情况进行相应调整
    • 4.1 cur为红,p为红,g为黑,u存在且为红(无需旋转,变色即可)
      • 情况分析及处理
      • 代码实现
    • 4.2 cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)
      • 情况分析及处理
      • 代码实现
    • 4.3 cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)
      • 情况分析及处理
    • 4.4 (单/双)旋转+变色 代码统一实现
  • 5. 红黑树的测试
    • 5.1 验证其为搜索二叉树
    • 5.2 验证其是否平衡且满足红黑树性质
    • 5.3 大量随机数构建红黑树进行测试
    • 5.4 插入相同数量随机数比较AVL树和红黑树的高度
  • 6. 红黑树的删除
  • 7. 红黑树的查找
  • 8. 红黑树与AVL树的比较
  • 9. 红黑树的应用
  • 10. 源码分享
    • 10.1 RBTree.h
    • 10.2 Test.cpp

前言

这篇文章我们再来学习一种平衡搜索二叉树——红黑树
红黑树和AVL树都是常见的自平衡二叉搜索树,它们都可以用于高效地支持插入、删除和查找等操作。虽然它们都能够保持树的平衡性,但在不同的应用场景下,红黑树和AVL树有各自的优势和适用性。

1. 红黑树的概念及性质

1.1 红黑树的概念

红黑树(Red-Black Tree)也是是一种自平衡的二叉搜索树,与AVL树不同的是它在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍(最长路径也不会超出最短路径的两倍,因此红黑树的平衡性要求相对宽松,没有AVL树那样严格),从而使搜索树达到一种相对平衡的状态。
在这里插入图片描述

1.2 红黑树的性质

红黑树具有以下特点

  1. 每个结点不是黑色就是红色
  2. 根结点必须是黑色的
  3. 红色结点的两个子结点必须都是黑色的,这保证了没有两个连续的红色节点相连
  4. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,也被称为NIL节点)
  5. 任意结点到其每个叶子结点的简单路径上,黑色节点的数量相同:确保了树的黑平衡性,即红黑树中每条路径上黑色结点的数量一致。

在这里插入图片描述

大家可以对照着看一下上面的图,看它是否满足这些性质。

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中结点个数不会超过最短路径结点个数的两倍?(其实不带第4条就可以,加不加第4条都不会影响每条路径黑色结点数量是否相等)

那通过上面的性质我们可以得知,红黑树中的结点要么是黑色,要么是红色,这没什么可说的;然后要求根结点一定是黑色的,红色结点不能连续出现,如果出现了红色结点,那它的子结点必须是黑色的,然后还要求每条路径黑色结点的数量必须相等。
那这样其实就可以保证一棵红黑树中最长路径不超高最短路径的两倍。
当然实际中不同的红黑树情况是不一样的,所以我们这里来分析一种极端的情况:
大家想,如果一棵红黑树有红有黑,它里面如果有一条全黑的路径,那这条全黑的路径一定就是最短路径;
如果有一条是一黑一红,一黑一红…,这样黑红相间的,那他就是最长的路径。
然后它们里面的黑色结点个数又是相同的的,所以最长路径最多是最短路径的两倍,不可能超过最短路径两倍。
所以这样红黑树的高度就能够保持在一个相对平衡的范围内,当然他就没有AVL树那么严格。

比如这样的
在这里插入图片描述

那另外:

其实分析上面的性质,红黑树是可以全黑的,全部黑色结点,只要满足上面的性质即可。

然后大家思考一个问题,上面第四条性质——每个叶子结点都是黑色的(此处的叶子结点指的是空结点,也被称为NIL节点),有什么用?

那大家先算一下这个红黑树有多少条路径?
在这里插入图片描述
如果不带空的话,我们可能会认为有5条,但是这里计算路径其实应该走到空(NIL)所以正确的应该是有11条路径。
所有我们可以认为这条规则就是为了更好的帮我们区分不同路径的。

然后再补充一个概念:

结点的黑高(黑色高度):从某结点出发(不含该结点)到达任一空叶结点的路径上经过的黑结点总数
在这里插入图片描述

1.3 已经学了AVL树,为啥还要学红黑树

然后我们再来分析一个问题:

大家想,对于一棵红黑树来说,如果它里面全部的黑色结点一共有N个的话,那它的最短路径长度就差不多是 l o g 2 ( N ) log_2 (N) log2(N)
然后整棵树的节点数量就是在【N,2N】之间。
所以最长路径长度就可以认为差不多是2 l o g 2 ( N ) log_2 (N) log2(N)
所以红黑树的查找最少是 l o g 2 ( N ) log_2 (N) log2(N)次,最多是2 l o g 2 ( N ) log_2 (N) log2(N)次,所以红黑树查找的时间复杂度是O( l o g 2 N log_2 N log2N),计算时间复杂度前面的常数项可以省略嘛。
而AVL树也是O( l o g 2 N log_2 N log2N),但AVL树是比较严格的O( l o g 2 N log_2 N log2N),而红黑树是省略了常数项。
所以严格来说,红黑树的查找效率是比不上AVL树的(但对于计算机来说是没什么差别的),但是它们是同一个数量级的,都是O( l o g 2 N log_2 N log2N)。

那既然好像都差不多,为什么我们已经学了AVL树了,还要学红黑树呢?红黑树有什么优势吗?

🆗,由于AVL树要求更加严格的平衡,所以在进行插入和删除操作时,可能需要更频繁地进行旋转操作来调整树的结构,以保持平衡。相比之下,红黑树的插入和删除操作需要旋转的次数相对较少,因此在插入和删除操作频繁的情况下,红黑树可能更加高效。
这个如果大家学完这篇文章或许能更好的理解。

所以综合而言:

红黑树其实更胜一筹,红黑树在实际应用中更为常用,STL中的map和set底层就是用的红黑树,我们后面也会用红黑树进行模拟实现。
当然红黑树和AVL树在不同的应用场景下有各自的优势和适用性,所以我们都有必要学习学习。

2. 红黑树结构的定义

那我们先来定义一下红黑树的结构:

这里结点的颜色我们可以用一个枚举
在这里插入图片描述
这些代码很简单,相信就不用给大家解释了。

然后大家看,这里结点的颜色我们给的是红色,为什么要选择红色呢?黑色不可以吗?

我们来分析一下如果我们插入一个新结点给的是黑色,那它一定会违反上面提到的红黑树的第5条性质——每条路径上黑色结点的数量一致。
在这里插入图片描述
因为原来每条路径黑色结点数量是相同的,现在新插入一个黑色结点的话,那插入的这条路径会增加一个黑色结点,但是其它路径数量不变,所以其它所有的路径黑色结点数量都和这一条不相等,这样就比较麻烦了。
那如果我们插入结点默认给红色呢?会违反规则吗?
那红色的话其实要看具体情况了:
如果插入一个红色结点,但是它的父亲也是红色,那就出事了,因为红黑树里面不能出现连续的红色结点,那这种情况就需要调整了。
但如果它的父亲是黑色,那就没问题,不需要进行什么处理。

所以我们新插入的结点给成红色,当然如果是第一次插入根结点我们可以直接给黑色,因为红黑树要求根结点必须是黑色嘛。

3. 插入(仅仅是插入过程)

由于红黑树也是一种搜索二叉树,是在二叉搜索树的基础上加上其平衡限制条件来实现平衡。

所以红黑树插入的逻辑也根搜索二叉树一样:

在这里插入图片描述

那插入一下就完事了吗?

当然不是,和AVL树一样,插入新结点之后,我们要去判断此时这棵树是否还满足是一棵红黑树,如果不满足就要进行相应的调整。

那红黑树又是如何进行调整的呢?

4. 插入结点之后根据情况进行相应调整

对于红黑树来说,插入新结点之后,我们要检查红黑树的性质是否遭到破坏,如果遭到破坏的话,就需要进行相应的调整。

因为新节点的默认颜色是红色,因此:

如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;
但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连续红色结点出现,此时需要对红黑树分情况来讨论:

约定:

当我们在红黑树中插入一个新结点的时候,cur为当前新插入结点,p(parent)为父结点,g(grandfather)为祖父结点,u(uncle)为叔叔节点
比如
在这里插入图片描述

我们下面分成了三种情况,但是有些地方分的比较细的话会分为五种:

第一种就是第一次插入,作为根结点,直接将它染成黑色就完了,那这种我们上面已经处理过了。
第二种就是插入的时候它的父亲是黑色的(新插入的结点是红色的),这种就不需要调整,插入完直接结束。
那第二种的话我们也不需要单去处理,因为第二种不进入我们下面讲的三种情况,所以就不会对他进行任何处理,最后直接结束。

最后全部写完大家可以自己捋一遍。

下面讲解剩下的三种情况:

4.1 cur为红,p为红,g为黑,u存在且为红(无需旋转,变色即可)

情况分析及处理

那这个我们就认为是情况三。

那首先我们还是来看一下该情况对应的一个抽象图:

在这里插入图片描述
这就是我们当前要讨论的情况,那大家看到这里我没有像AVL树那里的画成高度为h的树,因为红黑树这里就不太关注高度了,而是重点关注结点的颜色。
那这里a/b/c/d/e高度都为0的话其实就是cur就是新增的结点,如果不为0代表的情况其实就是下面插入之后调整更新上面变成这种情况,那他们的处理方法是统一的。
现在我们插入一个红色结点,那这里就出现了连续红色结点的情况,这是不行的。
那我们可以直接把p改成黑色吗?这样不就没有连续红色结点了
不可以,直接把p改成黑色的话,这条路径就增加了一个黑色结点,但是其它路径数量不变,那就不满足所有路径黑色结点数量相同的性质了。

那对于这种情况我们要如何去处理呢?

首先第一步:将p,u改为黑,g改为红
在这里插入图片描述
那这样处理过后就不存在连续红色结点的情况了,而且每条路径黑色结点的数量依然相等。

那这样就完了吗?

还没有。
如果g是根节点,调整完成后,需要将g改为黑色。
在这里插入图片描述
这样它才满足是一棵红黑树。

那上面是g是根结点的情况,那如果g是一棵子树呢?

比如这样:
在这里插入图片描述
当然这里不止在cur这个位置插入会引发这种情况(上面那个也是),p的两个孩子位置,u的两个孩子位置,在这4个位置新插入结点是不是都是这种情况啊。

那这种情况又如何处理呢?

如果g是子树,那前面的步骤还是一样——将p,u改为黑,g改为红
在这里插入图片描述
然后呢?如果g的父亲是黑色,就可以结束了,但现在g的父亲也是红色,所以:
继续向上调整,把g当成cur,重新确定p、u、g,进行同样的操作,一直往上走
在这里插入图片描述
直至某一次调整完遇到9的父亲为黑或者走到根停止

代码实现

那我们来写一下代码:

在这里插入图片描述
上面画图没有画父亲是祖父右孩子的情况,单处理方式是一样的,这里代码直接加上了。
代码我就不过多解释了,相信结合图和上面的分析大家很容易能看懂。

4.2 cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

情况四

情况分析及处理

那上面讨论的是u存在且为红的情况,那如果u不存在或者u存在且为黑呢?

先来看u不存在的情况:

像这样
在这里插入图片描述
cur是新插入的结点,它的叔叔u不存在。
怎么处理呢?
其实跟下面讨论的u存在且为黑的处理方法一样,下面统一说明。

来看u存在且为黑的情况:

如果出现u存在且为黑的情况一定是由上面的4.1情况3调整得到的。
为什么呢?
大家看,如果插入的时候就是这种情况
在这里插入图片描述
是不可能出现的,因为这样的话插入之前红黑树中不同路径的黑色结点数量就不相等了,他就不符合是一颗红黑树了。
所以出现这种情况一定是情况一转变得来的
在这里插入图片描述
就是这样子,这种情况的话c里面一定要有一个黑色结点,d/e要么为空,要么是一个红色结点,这样才满足是红黑树(在插入之前),我们就不具体画了,情况比较多。
那如何处理呢?
那对于这种情况我们要进行的旋转+变色(对于上面u不存在也是一样)
为什么要旋转?因为这种情况的话最长路径有可能已经超过最短路径的两倍了,比如上面这个图如果如果d/e是空的话就已经超过了。
所以要先旋转降高度,再去调整颜色使它符合红黑树。

进行什么旋转呢?
那就要看具体情况了,其实还是我们AVL树那里学习的四种情况。
当前我们是在较高左子树的左侧插入,所以要进行的旋转是右单旋
在这里插入图片描述
先旋转(对g这棵树)的目的就是让它变平衡。然后变色怎么变呢?
变色的话不论那种旋转是统一的:p、g变色–p变黑,g变红
在这里插入图片描述
然后大家看就重新符合红黑树了(上面说了c里面一定要有一个黑色结点,d/e要么为空,要么是一个红色结点)。

那上面说了u为空也是一样的处理,我们可以来画一下

在这里插入图片描述
首先这种情况也应该是右旋(较高左子树左侧插入)
在这里插入图片描述
是不是一样的处理啊。

这里变色的情况是一样的,关键在于判断并进行不同的旋转。

我们上面分析的情况是在较高左子树的左侧插入,所以先要进行右单旋,然后变色。
如果我们是在右侧插入(较高右子树的右侧)的话,那就是先进性左单旋,然后变色,这里变色是一样的。
比如这样:
在这里插入图片描述

代码实现

这里的代码我们等到后面和双旋的一起写

那上面我们说细分的话有5种情况,上面已经说了4种,那最后一种其实也是u不存在/u存在且为黑,与上一种情况的区别就是第5中是双旋+变色

4.3 cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

上面我们分析的是需要进行单旋然后变色的情况,那当然就有需要进行双旋调整高度,然后再变色的情况。
当然本质是因为插入的情况不同,所以需要不同的旋转来降高度。

情况分析及处理

那我们就来分析一下需要双旋+变色的情况:

首先前提条件根上面一样,还是cur为红,p为红,g为黑,u不存在/u存在且为黑

这里画图我就不分那么细了,因为跟上面其实差不多,只是要进行的旋转不同

我们来画一种左右双旋+变色的情况
在这里插入图片描述

那如果u不存在,那就是这样

在这里插入图片描述
那先进行一个左右双旋
在这里插入图片描述
这样高度就降下去了,然后怎么变色呢?
cur变黑,g变红(不论哪种双旋,变色是一样的)
在这里插入图片描述
这样就重新调整为红黑树了。

再给大家画一下u存在且为黑的情况吧:

同样的,如果这里出现u存在且为黑的情况,也一定是有4.1情况3调整更新得到的
比如这样
在这里插入图片描述
那然后其实和u为空一样,先进行一个左右双旋(因为我们这里画的还是较高左子树右侧插入的情况)
在这里插入图片描述
然后变色,还跟上面一样,cur变黑,g变红
在这里插入图片描述

4.4 (单/双)旋转+变色 代码统一实现

那接下来我们来写一下需要旋转+变色调整的几种情况的代码

首先我们来看左侧插入的情况(右单旋/左右单旋)

在这里插入图片描述
那我们来写一下:
在这里插入图片描述
这就是左侧插入的两种需要旋转的情况处理

那我们再来写一下右侧插入的:

在这里插入图片描述
叔叔为红我们上面写过了,这里还是写叔叔不存在或者存在且为黑的情况
在这里插入图片描述
就写好了。
左旋右旋的代码直接拷贝AVL树的就行,记得把平衡因子的更新删掉。

5. 红黑树的测试

5.1 验证其为搜索二叉树

首先我们还是先来验证他是否是二叉搜索树,看它中序是否有序就行了

在这里插入图片描述
测试一下
在这里插入图片描述是平衡的,没问题。
再换一组数据
在这里插入图片描述
没什么问题
ps:我在这个地方测试的时候修改了几处错误,都是判断的==写成=了,我都改了过来,上面代码的截图有错误的地方我也做了修改。
如果有遗漏的地方,大家发现了上面代码片段的截图有地方有错误的话,可以看我最后分享的源码。(不过应该都被我修改过了🤭)

5.2 验证其是否平衡且满足红黑树性质

那如何判断它是否满足是一棵红黑树呢?

其实就是去检查那几条规则(性质):

首先结点颜色要么是黑色,要么是红色,这没什么好检查的。
然后根结点必须是黑色,这个可以检查一下,如果根结点不是黑色(是红色)直接就不符合了
在这里插入图片描述
然后如果出现连续的红色结点,那也不符合
那怎么检查有没有出现连续红色结点呢?
我们可以去遍历这棵树,然后遇到红色结点判断它的孩子是不是红色结点,如果存在红色结点它的孩子也是红色,那就不符合。
这样确实可以,但是不太好,因为孩子的话要检查两个,而且结点的孩子有还有可能不存在,为空,那还得再加一个判断。
所以我们可以这样做:遍历遇到红色结点我们去看它的父亲,如果它的父亲也为红色那就不行。
而判断父亲的话,只有根结点没有父亲,但是根结点是黑色的也不会去检查它。
所以这样写会好一点
在这里插入图片描述
然后还剩一个,我们要检查每条路径黑色结点数量是否相等,存在不相等的情况就不符合。
那这个要如何检查呢?
🆗,我们可以先求出一条路径的黑色结点数量,把它作为基准值,然后再递归求出每条路径的黑色结点数量和它比较,如果存在不相等的情况,就不符合。
在这里插入图片描述
那就写好了

然后我们来验证一下:

在这里插入图片描述

5.3 大量随机数构建红黑树进行测试

下面我们来生成一些随机数构建一棵红黑树测试一下:

先来10万个
在这里插入图片描述
没有出现问题
来100万
在这里插入图片描述
没有问题。

5.4 插入相同数量随机数比较AVL树和红黑树的高度

然后我们AVL树写的求高度的函数拷贝过来,在AVL树和红黑树中插入相同数量的随机数,看看它们的高度会差多少:

在这里插入图片描述
我们看到插入相同数量随机数它们的高度是可以达到一样高的,当然这里产生的随机数可能会有很多重复值,所以实际不会插入那么多。
在这里插入图片描述
还是10万个,这次对产生的随机数都加个i(那产生的随机数不同的数量就多了),我们看到就有一些差异了
那让他们插入的值不相同呢?
在这里插入图片描述
大家看这次差异就大了,还是红黑树要高一点
那增加到100万个数据呢?
在这里插入图片描述
这次差的就多了。
那这个结果其实也证实了我们上面说的,就是AVL树对平衡的控制是比较严格的,而红黑树是相对宽松的。

6. 红黑树的删除

红黑树的删除呢我们这里不做详细讲解:

红黑树的删除比较复杂,要比插入还复杂好多。
但关键的是红黑树的删除在考研包括我们找工作笔试面试的时候一般是不会考察的,所以我们也没必要去学。
大家有兴趣的可以自己查找相关资料进行学习,可以参考《算法导论》或者《STL源码剖析》

7. 红黑树的查找

那红黑树的查找就也和搜索二叉树的查找一样,之前讲过,这里就不再说了。

8. 红黑树与AVL树的比较

红黑树和AVL树都是高效的自平衡搜索二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N)。
红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,所以AVL树的插入和删除操作比红黑树更耗时。
因为AVL树在插入和删除节点后,会进行更多的旋转操作以维持一个较为严格的平衡,所以插入和删除操作的时间复杂度更高。
相对而言,红黑树对平衡的控制比较宽松,降低了插入删除时需要旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
在实际应用中,红黑树的使用更广泛。许多编程语言和库都使用红黑树作为基础数据结构,例如C++ STL中的std::map和std::set就是基于
红黑树实现的。

9. 红黑树的应用

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

10. 源码分享

10.1 RBTree.h

#pragma onceenum Colour
{RED,BLACK,
};template <class T>
struct RBTreeNode
{RBTreeNode<T>* _parent;RBTreeNode<T>* _left;RBTreeNode<T>* _right;T _data;Colour _col;RBTreeNode(const T& data):_parent(nullptr), _left(nullptr), _right(nullptr), _data(data), _col(RED){}};template <class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:bool Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (data < cur->_data){parent = cur;cur = cur->_left;}else if (data > cur->_data){parent = cur;cur = cur->_right;}else{return false;}}//走到这里cur为空,就是key应该插入的位置cur = new Node(data);//链接if (data < parent->_data)parent->_left = cur;if (data > parent->_data)parent->_right = cur;//链接父亲指针cur->_parent = parent;//如果插入之后它的parent是红的,就需要进行调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//如果父亲是祖父的左孩子,那右孩子就是叔叔if (parent == grandfather->_left){Node* uncle = grandfather->_right;//这里处理的情况是叔叔存在且为红,变色+向上继续处理if (uncle && uncle->_col == RED){//将p,u改为黑,g改为红parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//更新cur为grandfather,判断它的父亲是什么情况://1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了//2.如果它的父亲存在且为红,重新循环进行调整cur = grandfather;parent = cur->_parent;}else//叔叔不存在/叔叔存在且为黑的情况{//     g//   p   u// c if (cur == parent->_left)//左左——右单旋+变色{RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//左右——左右双旋+变色{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//这里调整完就可以break了,因为颜色都变的合适了,而相关的链接关系旋转会帮我们处理break;}}//如果父亲是祖父的右孩子,那左孩子就是叔叔else //parent = grandfather->_right{Node* uncle = grandfather->_left;//这里处理的情况是叔叔存在且为红if (uncle && uncle->_col == RED){//将p,u改为黑,g改为红parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//更新cur为grandfather,判断它的父亲是什么情况://1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了//2.如果它的父亲存在且为红,重新循环进行调整cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right)//右右——左单旋+变色{//    g//  u   p//        cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//右左——右左双旋+变色{//    g//  u   p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//上面处理过程中有可能会把根变成红色,这里统一处理一下,把根置成黑_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBlance(){if (_root && _root->_col == RED){cout << "根结点是红色" << endl;return false;}//先求出一条路径黑色结点数量int mark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++mark;cur = cur->_left;}//检查是否出现连续红色结点及所有路径黑色结点数量是否相等return _Check(_root, 0, mark);}int TreeHeight(){return _TreeHeight(_root);}
private:int _TreeHeight(Node* root){if (root == nullptr)return 0;int RightH = _TreeHeight(root->_left);int leftH = _TreeHeight(root->_right);return RightH > leftH ? RightH + 1 : leftH + 1;}bool _Check(Node* root, int blackNum, int mark){if (root == nullptr){//走到空就是一条路径走完了,比较一下是否相等if (blackNum != mark){cout << "存在黑色结点数量不相等" << endl;return false;}return true;}if (root->_col == BLACK)++blackNum;if (root->_col == RED && root->_parent->_col == RED){cout << "出现连续红色结点" << endl;return false;}return _Check(root->_left, blackNum, mark)&& _Check(root->_left, blackNum, mark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << " ";_InOrder(root->_right);}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//旋转并更新_parent指针parent->_right = subRL;if (subRL)subRL->_parent = parent;//先保存一下parent->_parent,因为下面会改它Node* pparent = parent->_parent;//旋转并更新_parent指针subR->_left = parent;parent->_parent = subR;//若pparent为空则证明旋转的是一整棵树,因为根结点的_parent为空if (pparent == nullptr){//subR是新的根_root = subR;_root->_parent = nullptr;}//若pparent不为空,则证明旋转的是子树,parent上面还有结点else{//让pparent指向子树旋转之后新的根if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}//同时也让新的根指向pparentsubR->_parent = pparent;}}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//旋转并更新_parent指针parent->_left = subLR;if (subLR)subLR->_parent = parent;//先保存一下parent->_parent,因为下面会改它Node* pparent = parent->_parent;//旋转并更新_parent指针subL->_right = parent;parent->_parent = subL;//若parent等于_root则证明旋转的是一整棵树(这也是一种判断方法)if (parent == _root){_root = subL;_root->_parent = nullptr;}else{//让pparent指向子树旋转之后新的根if (parent == pparent->_left){pparent->_left = subL;}else{pparent->_right = subL;}//同时也让新的根指向pparentsubL->_parent = pparent;}}private:Node* _root = nullptr;
};

10.2 Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <time.h>
#include "RBTree.h"
void RBTest1()
{//int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int arr[] = { 95,47,32,29,7,7,2,50,74,30 };RBTree<int> t1;for (auto e : arr){t1.Insert(e);}t1.InOrder();cout << t1.IsBlance() << endl;}void RBTest2()
{srand((unsigned int)time(nullptr));const int N = 100000;RBTree<int> t;for (int i = 0; i < N; ++i){int x = rand() + i;t.Insert(x);}cout << t.TreeHeight() << endl;AVLTree<int, int> t2;for (int i = 0; i < N; ++i){int x = rand() + i;t2.Insert(make_pair(x, x));}cout << t2.TreeHeight() << endl;}
int main()
{RBTest2();return 0;
}

在这里插入图片描述

相关文章:

【高阶数据结构】红黑树详解

文章目录 前言1. 红黑树的概念及性质1.1 红黑树的概念1.2 红黑树的性质1.3 已经学了AVL树&#xff0c;为啥还要学红黑树 2. 红黑树结构的定义3. 插入&#xff08;仅仅是插入过程&#xff09;4. 插入结点之后根据情况进行相应调整4.1 cur为红&#xff0c;p为红&#xff0c;g为黑…...

树莓牌4B安装Centos8

准备工作 镜像&#xff1a;https://people.centos.org/pgreco/CentOS-Userland-8-stream-aarch64-RaspberryPI-Minimal-4/ 烧制工具&#xff1a;https://www.raspberrypi.com/software/ 初始化 将上述工具烧制好的SD卡插入树莓派&#xff0c;通电。通过网线将树莓派与电脑连…...

SQL Monitor Crack,PostgreSQL监控的传入复制图表

SQL Monitor Crack,PostgreSQL监控的传入复制图表  现在&#xff0c;您可以在从Estate页面导出的Microsoft Excel报告的摘要标题中看到UTC偏移量。 添加了PostgreSQL监控的传入复制图表。 Microsoft PowerShell API现在支持将使用New-SqlMonitorWindowsHost和New-SqlMonitorin…...

软件测试技术之单元测试—工程师 Style 的测试方法(3)

如何设计单元测试&#xff1f; 单元测试设计方法 单元测试用例&#xff0c;和普通测试用例的设计&#xff0c;没有太多不同&#xff0c;常见的就是等价类划分、边界值分析等。而测试用例的设计其实也是开发者应该掌握的基本技能。 等价类划分 把所有输入划分为若干分类&…...

Ubuntu中安装OpenSSL

文章目录 一、前期准备1.1 压缩包下载1.2 gcc, make等的安装二、安装配置 一、前期准备 1.1 压缩包下载 在安装openssl之前&#xff0c;我们需要下载对应的压缩包 https://www.openssl.org/source/openssl-3.0.1.tar.gz 此压缩包可以选择win上下载后解压再复制到本地虚拟机中…...

CW4-6A-S、CW4-10A-S、CW4-20A-S、CW4-30A-S螺栓式滤波器

CW3L2-3A-S、CW3L2-6A-S、CW3L2-10A-S、CW3L2-20A-S CW3-3A-S、CW3-6A-S、CW3-10A-S、CW3-20A-S、CW3-30A-S CW4EL2-3A-S、CW4EL2-6A-S、CW4EL2-10A-SCW4EL2-20A-S、CW4EL2-30A-S CW4E-3A-S、CW4E-6A-S、CW4E-10A-S、CW4E-20A-S、CW4E-30A-S CW4E-40A-S(001)、CW4E-50A-S(0…...

课程项目设计--项目设计--宿舍管理系统--vue+springboot完成项目--项目从零开始

写在前面&#xff1a; 本文是从项目设计到完成开始写的&#xff0c;本来这个项目基础功能是做完了的&#xff0c;但是之前时间紧张想从头做起了。之前一周写前端后端累死了. 设计是关键&#xff0c;这一篇主讲设计。可能后面会有修改&#xff0c;本人实力有限,学习的也是别人的…...

【Linux】Linux下常用搜索命令及其常用选项小结

0x00 前言 版本信息&#xff1a;Ubuntu 18.04.6 LTS 最后更新日期&#xff1a;2023.8.18 0x01 Linux下常用搜索命令及其常用选项小结 1.find &#xff08;1&#xff09;find path -name filename &#xff1a;在指定目录path查找名为filename 文件&#xff0c;文件名可用*匹…...

web APIs-练习五

5秒自动关闭的广告&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"…...

MySQL——基础——外连接

一、外连接查询语法&#xff1a;(实际开发中,左外连接的使用频率要高于右外连接) 左外连接 SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件...; 相当于查询表1(左表)的所有数据 包含 表1和表2交集部分的数据 右外连接 SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN …...

spring boot 实现Redisson分布式锁及其读写锁

分布式锁&#xff0c;就是控制分布式系统中不同进程共同访问同一共享资源的一种锁的实现。 1、引入依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.15.5</versio…...

java-IONIO

一、JAVA IO 1.1. 阻塞 IO 模型 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内 核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…...

Python学习笔记_基础篇(十一)_socket编程

python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心&#xff0c;它承担计算机的所有任务。 操作系统是运行在硬件之上的软件&#xff0c;是计算机的管理者&#xff0c;它负责资源的管理和分配、任务的调度。 程序是运行…...

C#8.0本质论第三章--更多数据类型

C#8.0本质论第三章–更多数据类型 3.1类型的划分 一个类型要么是值类型&#xff0c;要么是引用类型。区别在于拷贝方式&#xff1a;值类型数据总是拷贝值&#xff1b;引用类型的数据总是拷贝引用。 3.1.1值类型 3.1.2引用类型 引用类型的变量存储对数据存储位置的引用。 3.…...

浅拷贝与深拷贝

作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 浅拷贝与深拷贝 浅拷贝浅拷贝定义浅拷贝代码演示浅…...

背包 问题

1、背包问题 1.1、01背包 题目&#xff1a; 有n件物品和一个容量为m的背包&#xff0c;第i件物品的体积是v[ i ]&#xff0c;价值是w[ i ]&#xff0c;每件物品只有一件&#xff0c;求在不超过背包容量的前提下&#xff0c;可以放的物品的最大价值是多少 基本思路&#xff…...

蓝牙资讯|安卓将加强耳机音量监控,耳机查找功能将更加普遍

为了保护用户的听力健康&#xff0c;Android 14 将增加一项新功能&#xff0c;当用户使用耳机听音乐时&#xff0c;如果音量过高或持续时间过长&#xff0c;系统会发出警告&#xff0c;并自动降低音量。这个功能叫做“耳机音量过高警告&#xff08;headphone loud sound alert&…...

vue,element。监听快捷键粘贴图片,添加到el-upload的列表。

在①中&#xff0c;粘贴图片&#xff0c;图片能够自动添加到底下el-upload组件的文件列表②。 // 对应① <el-card><el-tooltip content"粘贴图片至此" placement"top"><input readonly class"pasteImg" paste.prevent"hand…...

时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍…...

编织梦想:SpringBoot AOP 教程与自定义日志切面完整实战

什么是 AOP AOP 是指通过预编译方式和运行期动态代理的方式&#xff0c;在不修改源代码的情况下对程序进行功能增强的一种技术。AOP 不是面向对象编程&#xff08;OOP&#xff09;的替代品&#xff0c;而是 OOP 的补充和扩展。它是一个新的维度&#xff0c;用来表达横切问题&a…...

AssignableTypeFilter 和 AnnotationTypeFilter什么区别?

在 Spring 框架中&#xff0c;AssignableTypeFilter 和 AnnotationTypeFilter 都是用于在组件扫描过程中进行过滤的工具类&#xff0c;用于筛选出特定类型或特定注解的类。它们的主要区别在于筛选的侧重点和使用方式。 AssignableTypeFilter&#xff1a; AssignableTypeFilte…...

TCP-事件模型

#include "main.h"VOID Server_write_error() {}/*1.打开网络库 * 2.校验网络库版本 * 3.创建SOCKET * 4.绑定IP地址和端口 * 5.开始监听 * 6.创建客户端socket/接受链接 * 7.与客户端收发消息 * 8.(6.7)两步的函数accept&#xff0c;send,recv 有堵塞&#xff0c;可…...

typescript 声明文件

作用 1、为已存在js库提供类型信息&#xff0c;这样在ts项目中使用这些库时候&#xff0c;就像用ts一样&#xff0c;会有代码提示、类型保护等机制 2、项目内共享类型&#xff1a;如果多个.ts文件中都用到同一个类型&#xff0c;此时可以创建.d.ts文件提供该类型&#xff0c;…...

BC96 有序序列判断

描述 输入一个整数序列&#xff0c;判断是否是有序序列&#xff0c;有序&#xff0c;指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)。 数据范围&#xff1a;3≤n≤50 序列中的值都满足1≤val≤100。 输入描述 第一行输入一个整数N(3≤N≤50)。 第二行…...

QT操作excel的两种方式 QT基础入门【Excel的操作】

QT操作excel的方式有两种&#xff1a;QAxObject 和QtXlsx QAxObject是通过调用office或者wps组件来实现对excel图表的操作的。只有装office软件或者wps软件就可以实现&#xff0c;但是 如果只装了office软件&#xff0c;有时可以用有时不可以用&#xff1b;如果只装wps软件&a…...

c++ qt--QString,弹出框(第二部分)

c qt–QString&#xff0c;弹出框&#xff08;第二部分&#xff09; 一.QString 1.所用头文件 #include<QString>2.功能 1.初始化 可以用字符&#xff0c;常量字符串、字符指针、字符数组等类型给QString进行初始化 QString str2"4567";//进行初始化2.拼…...

CSS自学框架之动画

这一节&#xff0c;自学CSS动画。主要学习了淡入淡出、淡入缩放、缩放、移动、旋转动画效果。先看一下成果。 优雅的过渡动画&#xff0c;为你的页面添加另一份趣味&#xff01; 在你的选择器里插入 animation 属性&#xff0c;并添加框架内置的 keyframes 即可实现&#xff0…...

RabbitMQ的5种消息队列

RabbitMQ的5种消息队列 1、七种模式介绍与应用场景 1.1 简单模式(Hello World) 一个生产者对应一个消费者&#xff0c;RabbitMQ 相当于一个消息代理&#xff0c;负责将 A 的消息转发给 B。 应用场景&#xff1a;将发送的电子邮件放到消息队列&#xff0c;然后邮件服务在队列…...

【C语言】选择排序

基本原理 先找到数组中最大的那个数&#xff0c;将最大的数放到数组最右端&#xff08;交换a[maxid]和a[len-1]这两个数的位置&#xff09;&#xff0c;然后继续从a[0]到a[len-2]中找到最大的数&#xff0c;然后交换a[maxid]和a[len-2]位置&#xff0c;依次查找交换&#xff0c…...

异步更新队列 - Vue2 响应式

前言 这篇文章分析了 Vue 更新过程中使用的异步更新队列的相关代码。通过对异步更新队列的研究和学习&#xff0c;加深对 Vue 更新机制的理解 什么是异步更新队列 先看看下面的例子&#xff1a; <div id"app"><div id"div" v-if"isShow&…...

【Unity的URP渲染管线下实现扩展后处理Volume组件_TemporalAntiAliasing(TAA)_抗锯齿(附带下载链接)】

【Unity的URP渲染管线下的TAA抗锯齿】 背景:1. Unity内置的抗锯齿只能够满足部分画面需求。展示一个锯齿示例。2. 在75寸大屏电视上跑通展示一个锯齿示例。- 在Camera上配置3. 安装了一个TAA组建,最后打包APK在安卓机上运行报错。- 经过测试排查,发现是没有将后处理的shader…...

NineData通过AWS FTR认证,打造安全可靠的数据管理平台

近日&#xff0c;NineData 作为新一代的云原生智能数据管理平台&#xff0c;成功通过了 AWS&#xff08;Amazon Web Service&#xff09;的 FTR 认证。NineData 在 FTR 认证过程中表现出色&#xff0c;成功通过了各项严格的测试和评估&#xff0c;在数据安全管理、技术应用、流…...

Qt应用开发(基础篇)——滚屏区域类 QScrollArea

一、前言 QScrollArea类继承于QAbstractScrollArea&#xff0c;QAbstractScrollArea继承于QFrame&#xff0c;是Qt滚动视图的常用部件。 滚屏区域基类 QAbstractScrollArea 框架类 QFrame QScrollArea类提供了对另一个小部件的滚动视图&#xff0c;基础功能、滚动条控制、界面策…...

安装最新版chromedriver 116,亲测可用

Version Selection...

html题库

什么是HTML? HTML的全称为 超文本标记语言 &#xff0c;是一种 标记语言 。 它包括一系列标签 &#xff0c;通过这些标签可以将网络上的文档格式统一&#xff0c;使分散的 Internet 资源连接为一个逻辑整体。 DOCTYPE 的作用是什么&#xff1f;标准模式与兼容模式&#xff08;…...

Android11 中 LED 使用-RK3568

文章目录 前言原理图设备树驱动前言 现在我们来学习点亮LED 原理图 然后对应在核心板原理图上查找 Working_LEDEN_H_GPIO0_B7,如下图所示: 那么我们只要控制 GPIO0_B7 即可控制 led 的亮灭。 设备树 leds: leds {compatible = "gpio-leds";work_led: work {gpi…...

BC77 有序序列插入一个数

描述 有一个有序数字序列&#xff0c;从小到大排序&#xff0c;将一个新输入的数插入到序列中&#xff0c;保证插入新数后&#xff0c;序列仍然是升序。 输入描述 第一行输入一个整数(0≤N≤50)。 第二行输入N个升序排列的整数&#xff0c;输入用空格分隔的N个整数。 第三…...

通过脚本使用Cppcheck做静态测试并生成报告(Windows)

1.安装cppcheck 先从cppcheck官方网站下载cppcheck的安装包。 注&#xff1a; &#xff08;1&#xff09;官网地址&#xff1a;https://sourceforge.net/projects/cppcheck &#xff08;2&#xff09;截止2023年8月&#xff0c;官方发布的最新版本是cppcheck-2.11-x64-Setup.…...

工业安全生产信息化平台的基本架构和关键功能分享

工业安全生产信息化平台是指利用信息技术手段&#xff0c;将工业安全生产管理与数据采集、传输、处理相结合&#xff0c;实现对工业安全生产全过程的数字化、信息化、智能化管理的平台。它通过集成多种信息系统和设备&#xff0c;实现对重大危险源监控预警、安全风险分级管控、…...

每日一道面试题之session 和 cookie 有什么区别?

Session和Cookie是两种在Web开发中用于跟踪用户状态的机制&#xff1a; 它们之间的区别如下&#xff1a; 存储位置&#xff1a;Cookie是存储在用户浏览器中的小型文本文件&#xff0c;而Session是存储在服务器上的数据结构。 数据安全性&#xff1a;Cookie中的数据可以被用户…...

SHELL 基础 显示字符颜色, 修改历史命令,Linux里的命令 执行顺序

echo 打印命令 &#xff1a; 显示字符串 &#xff1a; [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo "this is SHELL language" this is SH…...

Vue 和 JQuery 的区别在哪?为什么 JQuery 会被 Vue 取代?

在 Web 前端开发领域&#xff0c;我们经常会遇到一些不同的工具和框架&#xff0c;其中 Vue 和 JQuery, JQuery 是曾经备受欢迎的选择&#xff0c;而现在 Vue 是大多数人的选择。本文将探讨 Vue 和 JQuery 之间的区别&#xff0c;并讨论为什么越来越多的开发人员放弃 JQuery 而…...

Spring 中 Bean 注入与获取

Spring 中有哪些方式可以把 Bean 注入到 IOC 容器&#xff1f; 关于这个问题&#xff0c;我的回答入下&#xff1a;把 Bean 注入到 IOC 容器里面的方式有 7 种方式 1. 使用 xml 的方式来声明 Bean 的定义&#xff0c;Spring 容器在启动的时候会加载并解析这 个 xml&#xff0c;…...

STM32 中断复习

中断 打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断。 在确定时间内对相应事件作出响应&#xff0c;如&#xff1a;温度监控&#xff08;定时器中断&#xff09;。故障处理&#xff0c;检测到故障&#x…...

Django的模型

定义模型 from django.db import models class User(models.Model):# 类属性是表示表的字段username models.CharField(max_length50,uniqueTrue)password models.CharField(max_length200)create_time models.DateTimeField(auto_now_addTrue) # auto_now_add新增数据时间…...

非计算机科班如何丝滑转码

近年来&#xff0c;很多人想要从其他行业跳槽转入计算机领域。非计算机科班如何丝滑转码&#xff1f; 方向一&#xff1a;如何规划才能实现转码&#xff1f; 对于非计算机科班的人来说&#xff0c;想要在计算机领域实现顺利的转码并不是一件容易的事情&#xff0c;但也并非不…...

PyTorch深度学习实战(12)——数据增强

PyTorch深度学习实战&#xff08;12&#xff09;——数据增强 0. 前言1. 图像增强1.1 仿射变换1.2 亮度修改1.3 添加噪音1.4 联合使用多个增强方法 2. 对批图像执行图像增强3. 利用数据增强训练模型小结系列链接 0. 前言 数据增强是指通过对原始数据进行一系列变换和处理&…...

SpringCloud Ribbon中的7种负载均衡策略

SpringCloud Ribbon中的7种负载均衡策略 Ribbon 介绍负载均衡设置7种负载均衡策略1.轮询策略2.权重策略3.随机策略4.最小连接数策略5.重试策略6.可用性敏感策略7.区域敏感策略 总结 负载均衡通器常有两种实现手段&#xff0c;一种是服务端负载均衡器&#xff0c;另一种是客户端…...

04 qt功能类、对话框类和文件操作

一 QT中时间和日期 时间 ---- QTime日期 ---- QDate对于Qt而言,在实际的开发过程中, 1)开发者可能知道所要使用的类 ---- >帮助手册 —>索引 -->直接输入类名进行查找 2)开发者可能不知道所要使用的类,只知道开发需求文档 ----> 帮助 手册,按下图操作: 1 …...

安装软件包

安装软件包 创建一个名为 /home/curtis/ansible/packages.yml 的 playbook : 将 php 和 mariadb 软件包安装到 dev、test 和 prod 主机组中的主机上 将 RPM Development Tools 软件包组安装到 dev 主机组中的主机上 将 dev 主机组中主机上的所有软件包更新为最新版本 vim packa…...