红黑树原理分析及代码实现


前言:
因为前两天在看STL源码刨析和牛客网的C++招聘,面试题就是问STL中的set和map是基于红黑树而实现的关联容器,你能说说红黑树嘛?红黑树,对于本菜鸟来说显得既陌生又熟悉,熟悉是因为听别人谈及的很多,陌生是因为这个对于本菜鸟来说只是个宽泛的概念,自己并不了解(哭了呜呜呜)。好吧,废话说了这么多,开始正题吧。

为了说明红黑树,我们应该用2-3树作为铺垫来讲解,因为直接讲红黑树,会让第一次接触红黑树的人难以接受的,思维跳跃太大也会跟不上。

1、2-3查找树

为了保证查找树的平衡性,我们需要一些灵活性,因此在这里我们允许树的一个结点保存多个键。也就是我们会引入3-结点(含有两个键和三条链接),普通的一个二叉查找树中的结点称位2-结点(含有一个键和两条链接)。

二叉查找树(二叉排序树)定义如下:
1)子树上所有结点的值均小于或等于它的根结点的值
2)子树上所有结点的值均大于或等于它的根结点的值
3)左、右子树也分别为二叉排序树
如下图:
在这里插入图片描述

1.1、2.3查找树的定义

2-3查找树的定义如下:一颗2-3查找树或为一颗空树,或由以下结点组成:

1)2-结点,带有一个键(以及其对应的值)和两个链接,左链接指向的2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。
2)3-结点,带有两个键(以及其对应的值)和三个链接,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间右链接指向的2-3树中的键都大于该结点。
在这里插入图片描述
一颗完美平衡的2-3查找树中的所有空链接到根节点的距离都应该是相同的。

1.2、2-3树的搜索

为了确定一个键是否在2-3树中,我们将它与根结点中的键进行比较。如果它和其中任意一个相等,查找命中;否则我们就根据比较的结果找到指向相应区间的链接,并在其指向的子树中递归地继续查找了。如果这是一个空链接,查找为命中。
在这里插入图片描述

1.3、向2-结点中插入新键

要在2-3树中插入一个新的结点,我们可以和二叉查找树先进行一次未命中的查找,然后把新结点挂在树的底部。但是这样的话树就无法保持完美平衡性了。我们使用2-3的原因在于它能够插入后继续保持平衡,如果与未命中的查找结束一个2-结点,事情就很好办了:我们只要把这个2-结点替换为一个3-结点就行了。
在这里插入图片描述

1.4、向一颗只含有一个3-结点的树中插入新键

假设我们想插入一个只有一个3结点的2-3小树。这样的树有两个键,但在其一个结点中没有新键的空间。为了能够执行插入,我们暂时将新键放入一个4节点,这是我们结点类型的自然扩展,它有三个键和四个链接。创建4结点很方便,因为它很容易将其转换为由3个2-结点组成的2-3树,一个用中间键(在根处),一个用三个键中最小的一个(指向通过根的左侧链接,以及一个具有三个键中最大的一个(由根的右侧链接指向)。
在这里插入图片描述

1.5、向一个父结点为3-结点的3-结点中插入新值

插入3结点,其父结点是3结点。 现在假设搜索在父结点为3结点的结点处结束。再次,我们如上所述创建一个临时的4-结点,然后将其拆分并将其中间键插入父结点。父结点是一个3-结点,所以我们用一个包含4-结点分割中间键的临时新4-结点替换它。然后,我们在该结点上执行完全相同的转换。那就是我们拆分新的4-结点并将其中间键插入其父结点。扩展到一般情况很明显:我们继续向上,分割4个结点并将其中间键插入其父结点,直到达到2结点,我们将其替换为不会进一步拆分的3结点,或者直到达到根结点的3结点。
在这里插入图片描述

1.6、分解根结点

如果我们在从插入点到根的整个路径上有3个结点,那么我们最终会在根处建立一个临时的4结点。在这种情况下,我们将临时4结点分成三个2-结点。
在这里插入图片描述
局部变换:
2-3树插入算法的基础是所有这些转换都是纯粹本地的:除了指定的节点和链接之外,不需要检查或修改2-3树的任何部分。每次转换更改的链接数由一个小常量限制。每个转换将其中一个键从一个4节点传递到树中的节点父节点,然后相应地重新构造链接,而不触及树的任何其他部分。

全局性质:
这些局部变换不会影响到树的全局有序性和平衡性:任意空链接到根节点的路径长度都是相等的。

在具有N个键的2-3树中搜索和插入操作最多访问lgN个结点。

在最坏情况下,2-3树也会有较好的性能。每个操作中处理每个结点的时间都不会超过一个很小的常数,且这两个操作都只会访问一个路径上的结点,所以任何查找或者删除操作肯定不会超过对数级别。
例如:含有10亿个结点的一个2-3树的高度仅在19-30之间。我们最多只需要访问30个结点就能够在10亿键中进行任意查找和插入操作,所以这个是很惊人的。
在这里插入图片描述

2、红黑二叉查找树

刚刚描述的2-3树的插入算法不难理解。我们考虑一种名为红黑二叉树的简单数据结构来表达并实现它。

2.1、红黑二叉查找树的特征

它主要有两个特征:
1)节点都有颜色;
2)在插入和删除的过程中,要遵循保持这些颜色的不同排列的规则。首先第一个特征很好解决,在节点类中添加一个数据字段,例如 bool型变量,以此来表示节点的颜色信息。第二个特征比较复杂,红-黑树有它的几个规则,如果遵循这些规则,那么树就是平衡的。

红-黑树的主要规则如下:
算法导论的定义:

1)每个节点不是红色就是黑色的
2)根节点总是黑色的
3)如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
4)从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

算法4的等价定义:
1)红链接都是左连接
2)没有任何一个结点同时和两条红链相连
3)该树是完美黑色平衡的,即任意空链接到根节点的路径上的黑链接数量相同

2.2、替换3-结点

红黑二叉树的其实就是一颗标准的二叉查找树(完全由2-结点构成的)和一些额外的信息(替换3-结点)。红链接的产生就是将2-3树的3-结点改成两个2-结点,3-结点中值小的作为左子树,并用红链接链接较大的结点,ab的子结点保持2-结点就行了。其实也很好理解,红链就是3-结点拆分成两个2-结点产生的,然后就是将两个2-保存平衡。
在这里插入图片描述

3.3、1-1对应

给定任何2-3树,我们可以立即导出相应的红黑BST,只需按指定转换每个节点。相反,如果我们在红黑BST中水平绘制红色链接,则所有空链接与根相同距离,如果我们将由红色链接连接的节点折叠在一起,则结果为2-3树。
在这里插入图片描述

2.4、颜色表示

由于每个结点都由一个链接(来自其父结点)指向,我们通过向我们的Node数据类型添加布尔实例变量颜色来编码结点中链接的颜色,如果来自父结点的链接为红色,则为true。如果它是黑色则为假。按照惯例,空链接是黑色的。
在这里插入图片描述

2.5、旋转

在实现红黑树的过程中,可能会出现连续使用右侧红色链接或两个红色链接,遇到这些情况,我们就需要将它转换为左侧红链接了。首先,假设我们有一个右倾红色链接需要旋转到向左倾斜。此操作称为左旋转。实现将左倾红色链接转换为右倾红色链接的右旋转相当于相同的代码,左右交换。

2.6、在旋转之后重置父结点的链接

颜色翻转操作将两个红色子链翻转为黑色,将黑色父结点的颜色翻转为红色。

在这里插入图片描述

2.7、整个插入过程如下

在这里插入图片描述

代码如下:

算法4:Java实现红黑二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值