红黑树的概念及重要算法实现

本文详细介绍了红黑树的定义、关键性质,探讨了为何这些性质确保了树的平衡性。讲解了红黑树的结构设计,包括左旋、右旋操作,以及插入节点后的调整过程。同时,深入解读了如何通过判断节点位置进行等价判断和插入过程图的简化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RB树定义:

概述:

  • 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

  • 它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

  • 下图是一个红黑树的例子。其中26是根节点;最下面是哨兵结点;每个叶节点的空指针指向哨兵结点。
    在这里插入图片描述

RB树性质:

  1. 红黑树是每个节点都带有颜色属性的二叉查找树,颜色是红色或黑色( 没有平衡二叉树(-1 +1 0)的平衡度高,左右孩子高度差可以超过1 )。
  2. 在二叉查找树强制一般要求(即中序遍历是由小到大)以外,对于任何有效的红黑树我们增加了如下的额外要求:
    • 每个节点是红色或黑色(即必须是红或黑的一种。其中新插入的结点必须着色成红色)。
    • 根节点是黑色。
    • 每个叶节点(指NIL哨兵结点)是黑色。
    • 每个红色节点的两个子节点都是黑色。(即从每个叶子到根的所有路径上不能有两个连续的红色节点;而当双亲是黑,对孩子没要求,一黑一红、两黑、两红都行(即黑色可以连续))
    • 从任一节点到其每个叶子(算上了哨兵结点)的所有路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

思考:

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍????

  • 每个红色节点的两个子节点都是黑色。
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
  • 最短路径为全黑,最长路径就是红黑节点交替(因为红色节点不能连续),每条路径的黑色节点相同,则最长路径、刚好是最短路径的两倍。
    在这里插入图片描述

结构设计:

import static Tree.RBTree.ColorType.BLACK;
import static Tree.RBTree.ColorType.RED;

class RBTree
{
    enum ColorType{RED ,BLACK};
    class rb_node
    {
        rb_node leftchild;
        rb_node parent;
        rb_node rightchild;
        ColorType color; // RED  BLACK   红黑树在插入的时候必须是红节点
        int key;
        public rb_node()
        {
            leftchild = parent = rightchild = null;
            color = RED;//枚举类型赋值
            key = 0;
        }
        public rb_node(int kx)
        {
            leftchild = parent = rightchild = null;
            color = RED;
            key = kx;
        }
    }

    rb_node root;//必须是黑色
    rb_node nil;//sentinel  哨兵结点(必须是黑色)
    rb_node cur;//insert时要用

    public RBTree()
    {
        root = null;
        nil = new rb_node();
        nil.color = BLACK;
    }
}

左旋:

同AVL树
在这里插入图片描述

 //面试考 画图+手写
    private void RotateLeft(rb_node ptr)
    {
        rb_node newroot = ptr.rightchild;
        newroot.parent = ptr.parent;//1
        ptr.rightchild = newroot.leftchild;
        if(newroot.leftchild != null)
        {
            newroot.leftchild.parent = ptr;//2
        }
        newroot.leftchild = ptr;
        rb_node pa = ptr.parent;
        if(pa == null)
        {
            root = newroot;
        }
        else {
            if(pa.leftchild == ptr)
            {
                pa.leftchild = newroot;
            }
            else
            {
                pa.rightchild = newroot;
            }
        }
        ptr.parent = newroot; // 3
    }

右旋:

在这里插入图片描述

 private void RotateRight(rb_node ptr)
    {
        rb_node newroot = ptr.leftchild;
        newroot.parent = ptr.parent; //1
        ptr.leftchild = newroot.rightchild;
        if(newroot.rightchild != null){
            newroot.rightchild.parent = ptr;
        }
        newroot.rightchild = ptr;
        rb_node pa = ptr.parent;
        if(pa == null) {
            root = newroot;
        }
        else {
            if (pa.leftchild == ptr) {
                pa.leftchild = newroot;
            } else {
                pa.rightchild = newroot;
            }
        }
        ptr.parent = newroot;
    }

插入:

同BST的插入,多了一个调整

   boolean Insert_Item(int kx)
    {
        // 同BST的插入,多了一个调整

        boolean res = true;
        //空树时
        if (root == null) {
            root = new rb_node(kx);
            return res;
        }

        cur = root;
        rb_node pa = null;
        //在合适的分支找到底为止
        while (cur != null && cur.key != kx) {
            pa = cur;//找cur的孩子,所以升级当爸了
            cur = kx < cur.key ? cur.leftchild : cur.rightchild;
        }
        //有该值,就不插入(false,因为不允许重复)
        if (cur != null && cur.key == kx) {
            res = false;
        } else {//没有该值
            cur = new rb_node(kx);
            cur.parent = pa;//子指向父
            cur.leftchild = cur.rightchild = nil;
            //将新结点挂在上面查找的最后一个位置的左或右孩子
            if (cur.key < pa.key) {
                pa.leftchild = cur;//父指向子
            } else {
                pa.rightchild = cur;
            }
            //挂好后开始调整
            Adjust(cur);
        }
        return res;
    }

调整:

在插入和删除之后,红黑属性可能变得违规。恢复红黑属性需要少量(O(log n))的颜色变更(这在实践中是非常快速的)并且在删除节点是不超过三次树旋转(对于插入不超过两次树旋转)。

等价

在这里插入图片描述

判断p在 双亲的双亲的 右边

在这里插入图片描述

插入过程图

图片过大,建议下载下来放大滑动观看!
在这里插入图片描述

/*
     子的颜色和双亲的颜色不能都为红色(即不能有两个连续的红色)
     根和叶(哨兵)为黑  结点新插入时为红
     插入前应该使红黑树已经是调节好的状态
     所有路径的黑色节点应该相同
     代码用到了上面定义的函数
  */
    void Adjust(rb_node p)
    {
        //双亲不为空  且 插入的结点和其父都是红色
        while(p.parent != null && p.parent.color == RED)
        {
            if(p.parent.parent.rightchild == p.parent)// 说明p插在 双亲的双亲的 右边(wps图)
            {
                rb_node left = p.parent.parent.leftchild ;
                if(left.color == RED)
                {
                    p.parent.color = BLACK;
                    left.color = BLACK;
                    p.parent.parent.color = RED;//例如:插入56时

                    p = p.parent.parent;//接着往上判断(例:插入78时,第一遍只需调色,然后继续进入while,执行了下面的else,左旋(将12转下来,34成根)+变色
                } else//即即双亲为红  且 双亲的双亲的左孩子不为红  且 p插在 双亲的双亲的 右边,没法通过变颜色,需要旋转+变色
                {
                    //例:插入75时
                    if(p.parent.leftchild == p)//注意:插入在双亲的左边
                    {
                        p = p.parent;
                        RotateRight(p);//即总共完成 先右后左双旋
                    }
                    //没进入上面if的话,p就是在双亲的右边

                    p.parent.color = BLACK;
                    p.parent.parent.color = RED;
                    RotateLeft(p.parent.parent);//例:插入67时,将45旋下来,56变为根
                    //注意没写 p = p.parent.parent;  即不再移动双亲
                }
            }else  // p插在双亲的双亲的左边   p自己为红  p的双亲为红(是while的条件)
            {//例:插入5
                rb_node right = p.parent.parent.rightchild;
                if(right.color == RED)
                {
                    //把这俩红都改成黑
                    p.parent.color = BLACK;
                    right.color = BLACK;
                    p.parent.parent.color = RED;

                    p = p.parent.parent;// 此种if没有旋转,但是 移动指针了
                }
                else//即双亲的双亲的右边为黑色,和 p的父亲 颜色不一样,需要 旋转+调色
                {
                    if(p.parent.rightchild == p)//即p在双亲的双亲的左边,但 在双亲的右边 时(类比上面)
                    {
                        p = p.parent;
                        RotateLeft(p);//旋转函数的实现并没有改变指针的指向
                    }
                    p.parent.color = BLACK;
                    p.parent.parent.color = RED;
                    RotateRight(p.parent.parent);
                }
            }
        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值