红黑树详解

红黑树的起源

二叉树

满足以下两个条件的树就是二叉树:

  • 本身是有序树(若将树中每个结点的各子树看成是从左到右有次序的(即不能互换),则称该树为有序树(Ordered Tree))。
  • 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2。

简单地理解,二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。通常分支被称作“左子树”或“右子树”。

图片

二叉树具有以下几个性质:

  1. 二叉树中,第 i 层最多有 2i-1 个结点。
  2. 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
  3. 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。

二叉查找树

二叉查找树(Binary Search Tree),也称为二叉搜索树,是指一棵空树或者具有下列性质的二叉树。 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若任意节点的右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;任意节点的左、右子树也分别为二叉查找树。如下:

二叉查找树有个非常严重的问题,如果数据的插入是从大到小插入的,或者是从小到大插入的话,会导致二叉查找树退化成单链表的形式,如下:

为了解决该问题,引入平衡树,能够使得树趋向平衡,这种自平衡的树叫做平衡树。平衡树(Balance Tree,BT)指的是:任意节点的子树的高度差都小于等于 1。 常见的符合平衡树的有 AVL 树(二叉平衡搜索树),B 树(多路平衡搜索树,2-3 树,2-3-4 树中的一种),红黑树等。

AVL 树

指任意节点的两个子树的高度差不超过 1 的平衡树。又称自平衡二叉搜索树。AVL 树能解决上文二叉查找树中的退化单链表的问题,例如,插入数据依次为 {2,4,6,8,10},则如下图所示:

AVL 树会对不符合高度差的结构进行调整,从而使得二叉树趋向平衡。

2-3树

2-3 树是指每个具有子节点的节点(内部节点)要么有两个子节点和一个数据元素,要么有三个子节点和两个数据元素的自平衡的树,它的所有叶子节点都具有相同的高度。简单点讲,2-3 树的非叶子节点都具有两个分叉或者三个分叉,有两个分叉的节点包含一个数据元素,有三个分叉的节点包含两个数据元素所以,称作 2 叉3 叉树更容易理解,如下图:

另外一种说法,具有两个子节点和一个数据元素的节点又称作 2 节点,具有三个子节点和两个数据元素的节点又称作 3 节点,所以,整颗树叫做 2-3 树。2-3树的性质如下:

  • 所有叶子点都在树的同一层,一样高
  • 满足二叉搜索树的性质(左小于右)
  • 节点可以存放一个或两个元素
  • 每个节点有两个或三个子节点

2-3树的处理规则

向 2-节点(数据元素=4的节点)中插入元素:

向一颗只含有一个 3-节点(节点元素=3,4)的树中插入元素:

2-3-4树

同2-3树一样,2-3-4 树是指每个具有子节点的节点(内部节点)要么是2节点,要么是3节点,要么是4节点的自平衡的树,不同节点的子节点树和存储的数据元素数量不同,如下,它的所有叶子节点都具有相同的高度

  • 2 节点:包含两个子节点和一个数据元素。

  • 3 节点:包含三个子节点和两个数据元素。

  • 4 节点:包含四个子节点和三个个数据元素。

规则如下:

  • 加入新节点时,不会往空的位置添加节点,而是添加到最后一个叶子节点上。

  • 四节点可以被分解两个 2-节点和一个3-节点组成的树,并且分解后新树的根节点需要向上和父节点融合。

插入操作

原本的 2-3-4 树,如下图:

对于上图的 2-3-4 树,插入一个节点 17,由于规则 1,节点 17 不会加入节点 [16,18,20] 的子树,而是与该节点融合,如下:

由于规则 2,节点 [16,17,18,20] 是一个 4 节点,将该节点进行拆解成新的树,将 18 作为子树的根节点进行拆分。

此时树暂时失去了平衡,我们需要将拆分后的子树的根节点向上进行融合。

同理可得,由于规则 2,节点 [6,10,14,18] 是一个 4 节点,将该节点进行拆解成新的树,将 14 作为子树的根节点进行拆分,完成了 2-3-4 树的构建。

上述的插入节点的过程,无非也就为了符合两条规则,那么,2-3 树,2-3-4 树都有了,那是不是也有 2-3-4-5 树,2-3-4-5--...-n 树的存在呢?事实上是有的,世人把这一类树称为一个名字:B 树。

B 树

表示的是一类树:它允许一个节点可以有多于两个子节点,同时,也是自平衡的,叶子节点的高度都是相同的。所以,为了更好地区分一颗 B 树到底属于哪一类树,我们给它一个新的属性:阶数(Order):一个节点能有多少箭头指向其他节点。具有阶为 3 的 B 树,表示一个节点最多有三个子节点,也就是 2-3 树的定义。具有阶为 4 的 B 树,表示一个节点最多有四个子节点,也就是 2-3-4 树的定义。如下是阶为4的树。

拓展:在树中有2个属性描述

  • 度数:在树种,每个节点的子节点(子树)的个数就成为该节点的度(degree)
  • 阶数:阶定义为一个节点的子节点树目的最大值(order)

红黑树的定义

红黑树(Red Black Tree) 是一种自平衡二叉查找树,红黑树是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除(这里的n 是树中元素的数目)。因此,红黑树在业界应用很广泛,比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap、C++ STL 中的 map 均是基于红黑树结构实现的。红黑树的性质

  • 每个节点要么是黑色,要么是红色
  • 根节点是黑色
  • 每个叶子节点(NIL)是黑色,注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点(在Java中,叶子结点是为null的结点)
  • 如果一个节点是红色的,则它的子节点必须是黑色的(由于红黑树的每个节点都是由 2-3-4 树转化而来的,从而红色节点不能连续两个出现,不然会出现 4 节点的情况,导致违反了规则 2。而且红黑树的每一个黑节点都是 3 节点中的最中间的那个值,或者是 2 节点中其中一个值)
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点

如下图(省略了叶子节点都是黑色的 NIL 节点):



红黑树和2-3-4树的关系

如果把红黑树的红节点收缩到其父节点,那么红黑树就变成了2-3-4树,如上图,所有红色节点都与其父节点构成3或4节点,其它节点为2节点,所以红黑树的每一类型操作都与2-3-4树一一对应。黑色节点的个数(或者说位置)对应2-3-4树中的节点个数(或者说位置),这样可以很好的理解性质4(如果一个节点是红色的,则它的子节点必须是黑色的)和性质5(从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点)以及根节点到任意叶子节点的路径长度,最多相差一半。

此外,一个红黑树对应唯一形态的2-3-4树,但是一个2-3-4树可以对应多种形态的红黑树(主要是3节点可以对应两种不同的红黑树形态)。

红黑树的自平衡

红黑树的自平衡操作主要是以下几个操作:

  • 左旋:以某个节点作为支点(旋转节点),其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。如左旋图。
  • 右旋:以某个节点作为支点(旋转节点),其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。如幽玄菟。
  • 变色:结点的颜色由红变黑或由黑变红。

不考虑红黑树的颜色,旋转操纵不会影响旋转节点父节点以上的结构,左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。所以说旋转操作是局部操作,当然仅靠节点旋转无法保证红黑树的性质,还需要通过变色操作

红黑树的操作:

查找:因为红黑树是二叉平衡树,查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异,步骤如下:

  1. 从根节点开始查找,若根节点为null,返回null,若根节点不为null,把根节点设置为当前结点;
  2. 若当前节点不为null,用当前节点的数据元素跟查找的数据元素的值作比较;
  3. 若当前节点的值等于查找的值,返回当前节点;
  4. 若当前节点的值大于查找的值,把当前节点的左子节点设置为当前节点,回到步骤2;
  5. 若当前节点的值小于查找的值,把当前节点的右子节点设置为当前节点,回到步骤2;

插入:首先查询查找插入的位置;插入后自平衡。查找插入的父结点很简单,跟查找操作区别不大,查找操作找到后返回当前节点,插入操作查找到后更新当前节点的值。

  1. 从根节点开始查找,如果根节点为空,那么插入节点作为根节点,结束
  2. 如果根节点不为空,把根节点设置为当前节点
  3. 若当前节点为null,返回当前节点的父节点,结束,否则当前节点不为null,执行下一步
  4. 若当前节点数据元素等于查找的数据元素,则更新当前节点的数据元素,结束
  5. 若当前节点数据元素大于查找的数据元素,则把当前节点的左子节点设置为当前节点,回到步骤3
  6. 若当前节点数据元素小于查找的数据元素,则把当前节点的右子节点设置为当前节点,回到步骤3

此外,在插入操作中,如果没有找到相同数据元素的话,那就不是更新当前节点的值,此时需要插入新的值,插入的位置已经通过上面的操作确定,插入节点的颜色是红色,因为上述步骤3返回的当前节点(null)的父节点如果存在且为黑色,红黑树的黑色平衡没有被破坏,不需要自平衡操作,如果插入的节点是黑色,插入位置所在的子树的黑色节点总是多1,必须做自平衡。

插入场景分析:

1.红黑树为空树

直接将插入节点作为根节点即可,因为红黑树的根节点是黑色,还需要将插入的节点从红色变成黑色

2.插入节点的数据元素的值已存在

这个时候红黑树已经是平衡的且节点存在,只需要将插入节点设置为将要替换节点的颜色,在更新节点的值就完成插入。

3.插入节点的父节点为黑色

直接插入的节点为红色,不会影响平衡,直接插入即完成

4.插入节点的父节点为红色

4.1. 叔叔节点存在(存在一定是为红色),如下图1:根据红黑树性质可以判断祖父节点必为黑色,此时插入一个红色新节点N,会造成红黑树的的红色节点的子节点也是红色,不满足性质,故会进行变色操作,最简单的即将其改为红黑红,如图2;

这个时候PP节点变成红色,但是PP节点的父节点也是红色(根据红黑树性质),此时红黑树是不平衡的,还需要把PP节点当作新的插入节点,继续做插入和自平衡操作,直到平衡为止,但是如果此时PP的刚好是根节点或者PP自平衡时到顶部时将根节点变成了红色,我们必须重新将PP节点或者根节点重新设置成黑色,红黑树的结构变成黑黑红,从根节点到叶子节点的路径中,黑色节点增加了,这也是唯一一种会增加红黑树的黑色节点层数的场景。

可以看出红黑树的生长是自下而上的,不同于普通的二叉查找树的生长是自下而上的。

4.2  叔叔节点不存在(黑色Nil节点)并且插入节点的父节点是祖父节点的左子节点

4.2.1  插入结点是其父结点的左子结点,如下图1:

这种情况下,只需要将将P变成黑色,PP变成红色,对PP进行右旋,这样父节点颜色不变,同时也满足了红黑树的特性。当然也可以把P设为红色,I和PP设为黑色,这样作为子节点添加,显然又会出现上述的一些颜色不兼容情况,需要自底向上处理,反而更麻烦。

4.2.2 插入结点是其父结点的右子结点,如下图1:

首先对插入节点的父节点进行左旋,然后对祖父节点进行右旋,调整颜色即可。

4.3. 叔叔节点不存在(黑色Nil节点)并且插入节点的父节点是祖父节点的右子节点

4.3.1 插入节点是其父节点的右子节点,如下图1

4.3.2 插入节点是其父节点的左子结点,如下图1,操作如下:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值