阅读 110

数据结构之红黑树

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

红黑树的定义

红黑树是一棵二叉搜索树,其在每个结点上增加了一个存储位用于表示颜色。通过对根到叶子的每条路径上的颜色的管理,实现了近似平衡(红黑树确保一条路径不会比另外一条路径高出两倍),其可以保证在最坏条件下动态集合操作的时间复杂度为 O(lgN)O(lgN).

image.png

二叉搜索树

先简单介绍下二叉搜索树:二叉搜索树可以使用一个链表来进行表示,其中每个结点就是一个对象。除了key等一些数据外,其还包括属性leftright、和p,分别指向左子结点右子结点双亲。如果不存在则指向NIL

二叉搜索树满足对于其任意结点X,左子树的关键字最大不能超过X.key而右子树不能小于X.key,大部分搜索树操作的最坏时间都和树高成正比

image.png

红黑树的性质

介绍完二叉搜索树,接下来我们来介绍下红黑树满足的几个性质:

  1. 每个节点要么黑色要么红色
  2. 根节点必须是黑色
  3. 每个叶子节点(叶子结点是NIL结点)是黑色的。(一个结点没有子结点或者父结点,该结点对应相应的指针就指向NIL)
  4. 每个红色结点的两个子结点都是黑色的
  5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑结点

根据性质4我们可以得到,从根到叶节点的简单路径上,至少有一半的结点为黑结点。

红黑树不是完美平衡的二叉查找树,但是其任意一个结点到每个叶子结点的路径都包含相同数量的黑色结点。所以我们叫红黑树的这种平衡为黑色完美平衡

红黑树维持平衡的方式

红黑树为了保持平衡有三种操作:

左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父节点.右子结点的左子结点变成旋转结点的右子结点,左子结点保持不变。

右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父节点.左子结点的右子结点变成旋转结点的左子结点,右子结点保持不变。

变色:结点颜色由红色变成黑色或者由黑色变成红色

红黑树就是通过旋转和变色实现自平衡的。

image.png

从下图中可以发现,以11结点为旋转结点,其右子结点18变成父结点,然后结点18的左子结点14变成11结点的右子结点。

image.png

红黑树的查找

红黑树就是一棵二叉搜索树,所以其查找结点的方式和普通二叉搜索树一样,但是因为红黑树平衡性比较好,所以它查找的最坏时间复杂度为O(2lgN)O(2lgN)

红黑树的插入

红黑树的插入时间复杂度为O(lgn)O(lgn),红黑树的插入和二叉搜索树的插入类似,不过红黑树会判断相应结点颜色,并给该结点上色。下面上一段伪代码:

RB-INSERT(T,z)  // T为红黑树、z为要插入的结点
// y设为nil结点
y = T.nil
// x设为根节点
x = T.root
while x != T.nil // x不为空
    y = x 
    if z.key< x.key  // 插入的结点比该树小
        x = x.left
    else 
        x = x.right
z.p = y  // 设置插入结点的父结点为 y
if y == T.nil  // 该树为空
    T.root = z
else if z.key < y.key
    y.left = z
else
    y.right = z
// 将插入的结点的左右子结点设为nil
z.left = T.nil
z.right = T.nil
z.color = RED
RB-INSERT-FIXUP(T,z)  // 调整着色
复制代码
while z.p.color == RED
    if z.p == z.p.p.left  // 判断父结点是否是祖父结点的左结点
        y = z.p.p.right  // y设为祖父结点的右节点
        if y.color = RED  // 判断y的颜色   
            z.p.color = BLACK  // 将z的父结点的颜色设为黑色  (情况1)
            y.color = BLACK  // 将z的叔父结点的颜色设为黑色  (情况1)
            z.p.p.color = RED  // 将z的祖父结点的颜色设为红色  (情况1)
            z = z.p.p  // 将z结点设为其祖父结点  (情况1)
        else if z == z.p.right  // 如果z结点是父结点的右子结点
            z = z.p  // 将z结点设为z的父结点  (情况2)
            // 对结点进行左旋  
            LETF-ROTATE(T,z)  (情况2)
        z.p.color = BLACK  // 将z的父结点颜色设为黑色  (情况3)
        z.p.p.color = RED  // 将z的祖父结点的颜色设为红色  (情况3)
        // 对结点进行右旋
        RIGHT-ROTATE(T,z.p.p)  (情况3else  // 与上面的情况类似
        y = z.p.p.left   
        if y.color = RED
            z.p.color = BLACK
            y.color = BLACK
            z.p.p.color = RED
            z = z.p.p
        else if z == z.p.left
            z = z.p
            // 对结点进行左旋
            RIGHT-ROTATE(T,z)
        z.p.color = BLACK
        z.p.p.color = RED
        // 对结点进行右旋
        LEFT-ROTATE(T,z.p.p)
T.root.color = BLACK
复制代码

具体左旋和右旋的代码在这我就不贴出了,如果想了解自己可以去网上了解一下。

如下图:具体可以对照代码进行了解,开始先是判断该结点的父结以及叔父结点的颜色,进行判断插入结点的颜色是否满足,不满足则进行调整,调整完颜色再进行相应的左旋和右旋。

image.png

红黑树的删除

虽然红黑树的删除比插入还要复杂一点,但是为了节省篇幅就不再贴代码了。红黑树的删除操作需要的时间复杂度也是O(lgN)O(lgN)

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

具体步骤就不详细展开了,之后再写一篇关于红黑树删除的文章特地进行分析。

结语

第一篇数据结构-堆

该篇是我记录的数据结构篇的第二篇,由于本人技术水平有限,可能写的没有那么准确,如有什么问题,希望大家能够帮我指出,万分感谢!

文章分类
后端
文章标签