前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据结构:红黑树

数据结构:红黑树

原创
作者头像
HLee
修改2021-09-08 10:47:04
6450
修改2021-09-08 10:47:04
举报
文章被收录于专栏:房东的猫

简介

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉排序树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树

红黑树的特性:

  • 所有节点都是红色或者黑色
  • 根节点为黑色
  • 所有的 NULL叶子节点都是黑色
  • 如果该节点是红色的,那么该节点的子节点一定都是黑色
  • 所有的NULL节点到根节点的路径上的黑色节点数量一定是相同的

例如,Java集合中的TreeSet、TreeMap、HashMap都是通过红黑树去实现的。

红黑树查找

红黑树插入

1. 左旋

对x进行左旋,意味着"将x变成一个左节点"

理解左旋之后,看看下面一个更鲜明的例子:

2. 右旋

对x进行右旋,意味着"将x变成一个右节点"

理解右旋之后,看看下面一个更鲜明的例子:

仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。

左旋示例图(以x为节点进行左旋):

代码语言:javascript
复制
                               z
   x                          /                  
  / \      --(左旋)-->       x
 y   z                      /
                           y

对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”

右旋示例图(以x为节点进行右旋):

代码语言:javascript
复制
                               y
   x                            \                 
  / \      --(右旋)-->           x
 y   z                            \
                                   z

对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”

左旋:让目标结点成为其右孩子的左结点

右旋:让目标结点成为其左孩子的右结点

3. 插入

(1) 每个节点或者是黑色,或者是红色。

(2) 根节点是黑色。

(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]

(4) 如果一个节点是红色的,则它的子节点必须是黑色的。

(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。

① 情况说明:被插入的节点是根节点。

    处理方法:直接把此节点涂为黑色。

② 情况说明:被插入的节点的父节点是黑色。

    处理方法:什么也不需要做。节点被插入后,仍然是红黑树。

③ 情况说明:被插入的节点的父节点是红色。

    处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。

现象说明

处理策略

Case 1

当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

(1) 将“父节点”和“叔叔节点”设为黑色。 (2) 将“祖父节点”设为“红色”。 (3) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

Case 2

当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

(1) 将“父节点”设为“黑色”。 (2) 将“祖父节点”设为“红色”。 (3) 以“祖父节点”为支点进行右旋。

Case 3

当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

(1) 将“父节点”作为“新的当前节点”。 (2) 以“新的当前节点”为支点进行左旋。

上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。下面对它们详细进行介绍。

Case 1-叔叔是红色

当前节点(即,被插入节点)的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

  • 处理策略

(01) 将“父节点”设为黑色。

(02) 将“叔叔节点”设为黑色。

(03) 将“祖父节点”设为“红色”。

(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

“当前节点”和“父节点”都是红色,违背“特性(4)”。所以,将“父节点”设置“黑色”以解决这个问题。 但是,将“父节点”由“红色”变成“黑色”之后,违背了“特性(5)”:因为,包含“父节点”的分支的黑色节点的总数增加了1。  解决这个问题的办法是:将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”。关于这里,说明几点:第一,为什么“祖父节点”之前是黑色?这个应该很容易想明白,因为在变换操作之前,该树是红黑树,“父节点”是红色,那么“祖父节点”一定是黑色。 第二,为什么将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;能解决“包含‘父节点’的分支的黑色节点的总数增加了1”的问题。这个道理也很简单。“包含‘父节点’的分支的黑色节点的总数增加了1” 同时也意味着 “包含‘祖父节点’的分支的黑色节点的总数增加了1”,既然这样,我们通过将“祖父节点”由“黑色”变成“红色”以解决“包含‘祖父节点’的分支的黑色节点的总数增加了1”的问题; 但是,这样处理之后又会引起另一个问题“包含‘叔叔’节点的分支的黑色节点的总数减少了1”,现在我们已知“叔叔节点”是“红色”,将“叔叔节点”设为“黑色”就能解决这个问题。 所以,将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;就解决了该问题。

按照上面的步骤处理之后:当前节点、父节点、叔叔节点之间都不会违背红黑树特性,但祖父节点却不一定。若此时,祖父节点是根节点,直接将祖父节点设为“黑色”,那就完全解决这个问题了;若祖父节点不是根节点,那我们需要将“祖父节点”设为“新的当前节点”,接着对“新的当前节点”进行分析。

Case 2-叔叔是黑色,且当前节点是右孩子

当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

  • 处理策略

(01) 将“父节点”作为“新的当前节点”。

(02) 以“新的当前节点”为支点进行左旋。

下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

首先,将“父节点”作为“新的当前节点”;接着,以“新的当前节点”为支点进行左旋。 为了便于理解,我们先说明第(02)步,再说明第(01)步;为了便于说明,我们设置“父节点”的代号为F(Father),“当前节点”的代号为S(Son)。

为什么要“以F为支点进行左旋”呢?根据已知条件可知:S是F的右孩子。而之前我们说过,我们处理红黑树的核心思想:将红色的节点移到根节点;然后,将根节点设为黑色。既然是“将红色的节点移到根节点”,那就是说要不断的将破坏红黑树特性的红色节点上移(即向根方向移动)。 而S又是一个右孩子,因此,我们可以通过“左旋”来将S上移!

按照上面的步骤(以F为支点进行左旋)处理之后:若S变成了根节点,那么直接将其设为“黑色”,就完全解决问题了;若S不是根节点,那我们需要执行步骤(01),即“将F设为‘新的当前节点’”。那为什么不继续以S为新的当前节点继续处理,而需要以F为新的当前节点来进行处理呢?这是因为“左旋”之后,F变成了S的“子节点”,即S变成了F的父节点;而我们处理问题的时候,需要从下至上(由叶到根)方向进行处理;也就是说,必须先解决“孩子”的问题,再解决“父亲”的问题;所以,我们执行步骤(01):将“父节点”作为“新的当前节点”。

Case 3-叔叔是黑色,且当前节点是左孩子

当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

  • 处理策略

(01) 将“父节点”设为“黑色”。

(02) 将“祖父节点”设为“红色”。

(03) 以“祖父节点”为支点进行右旋。

下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比) 为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“叔叔节点”为U(Uncle),“父节点”为F(Father),祖父节点为G(Grand-Father)。

S和F都是红色,违背了红黑树的“特性(4)”,我们可以将F由“红色”变为“黑色”,就解决了“违背‘特性(4)’”的问题;但却引起了其它问题:违背特性(5),因为将F由红色改为黑色之后,所有经过F的分支的黑色节点的个数增加了1。那我们如何解决“所有经过F的分支的黑色节点的个数增加了1”的问题呢? 我们可以通过“将G由黑色变成红色”,同时“以G为支点进行右旋”来解决。

提示:上面的进行Case 3处理之后,再将节点"120"当作当前节点,就变成了Case 2的情况。

红黑树删除

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

红黑树和二叉搜索树的删除类似,只不过加上颜色属性(这里的子节点均指非NULL节点):

  1. 无子节点时,删除节点可能为红色或者黑色; 1.1 如果为红色,直接删除即可,不会影响黑色节点的数量; 1.2 如果为黑色,则需要进行删除平衡的操作了;
  2. 只有一个子节点时,删除节点只能是黑色,其子节点为红色,否则无法满足红黑树的性质了。 此时用删除节点的子节点接到父节点,且将子节点颜色涂黑,保证黑色数量。
  3. 有两个子节点时,与二叉搜索树一样,使用后继节点的值作为替换的删除节点的值,情形转至为1或2处理。 3.1后继结点是叶子节点,对应情况1.1或1.2 3.2后继结点有一个子结点,对应情况2

所以我们发现,红黑树节点删除后的修复操作都可以转换为1.1或1.2这两种情况,而1.1不需要修复,所以我们只需要研究1.2这种情况如何修复就行了。

参考

【1】https://www.cnblogs.com/skywang12345/p/3245399.html

【2】https://zhuanlan.zhihu.com/p/22800206

【3】https://www.jianshu.com/p/84416644c080

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 红黑树
    • 红黑树查找
      • 红黑树插入
        • 1. 左旋
        • 2. 右旋
        • 3. 插入
      • 红黑树删除
      • 参考
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档