数据结构与算法——19.红黑树

news2025/1/13 17:33:15

这篇文章我们来讲一下红黑树。

目录

1.概述

1.1红黑树的性质

2.红黑树的实现

3.总结


1.概述

首先,我们来大致了解一下什么是红黑树

红黑树是一种自平衡的二叉查找树,是一种高效的查找树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。因此,红黑树在业界应用很广泛,比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap。

1.1红黑树的性质

看过前面二叉查找树(即二叉搜索树)的同学都知道,普通的二叉查找树在极端情况下可退化成链表,此时的增删查效率都会比较低下。

为了避免这种情况,就出现了一些自平衡的查找树,比如 AVL,红黑树等。这些自平衡的查找树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态。前面讲的AVL树实现自平衡的机制是设置一个平衡因子。

以红黑树为例,红黑树通过如下的性质定义实现自平衡:

  • 所有节点都有两种颜色:红色或黑色。
  • 根节点是黑色。
  • 所有null视为黑色。
  • 每个红色节点必须有两个黑色的子节点,即红色节点不能相邻(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  • 从根节点到任意一个叶子节点,路径中的黑色节点数目一样(黑色完美平衡,简称黑高)。

有了上面的几个性质作为限制,即可避免二叉查找树退化成单链表的情况。

但是,仅仅避免这种情况还不够,这里还要考虑某个节点到其每个叶子节点路径长度的问题。

如果某些路径长度过长,那么,在对这些路径上的节点进行增删查操作时,效率也会大大降低。

这个时候性质4和性质5用途就凸显了,有了这两个性质作为约束,即可保证任意节点到其每个叶子节点路径最长不会超过最短路径的2倍。原因如下:

当某条路径最短时,这条路径必然都是由黑色节点构成。当某条路径长度最长时,这条路径必然是由红色和黑色节点相间构成(性质4限定了不能出现两个连续的红色节点)。而性质5又限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点。此时,在路径最长的情况下,路径上红色节点数量 = 黑色节点数量。该路径长度为两倍黑色节点数量,也就是最短路径长度的2倍。

下面看几个红黑树:

2.红黑树的实现

下面来看一下红黑树的实现

package Tree;
/**红黑树的相关操作*/
public class L4_RBTree {
    enum Color{RED,BLACK}

    private Node root;

    private static class Node {
        int key;
        Object value;
        Node left;
        Node right;
        Node parent;//父节点
        Color color = Color.RED;//颜色

        public Node(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        //是否是左孩子
        boolean isLeftChild(){
            return parent != null && parent.left == this;
        }

        //找叔叔结点
        Node isUncle(){
            if (parent == null || parent.parent == null){
                return null;
            }
            if (parent.isLeftChild()){
                return parent.parent.right;
            }else {
                return parent.parent.left;
            }
        }

        //找兄弟
        Node sibling(){
            if (parent == null){return null;}
            if (this.isLeftChild()){
                return parent.right;
            }else {
                return parent.left;
            }
        }

    }



    //判断是不是红色
    boolean isRed(Node node){
        return node != null && node.color == Color.RED;
    }
    //判断是不是黑色
    boolean isBlack(Node node){
        return  !isRed(node);
    }




    /**
     * 右旋
     * 要对parent进行处理
     * 选转后新根的父子关系
     * */
    private void rightRotate(Node node){
        Node nodeParent = node.parent;
        Node nodeLeft = node.left;
        Node nodeLeftRight = nodeLeft.right;
        if (nodeLeftRight != null){
            nodeLeftRight.parent = node;
        }
        nodeLeft.right = node;
        nodeLeft.parent = nodeParent;
        node.left = nodeLeftRight;
        node.parent = nodeLeft;
        if (nodeParent == null){
            root = nodeLeft;
        }else if (nodeParent.left == node){
            nodeParent.left = nodeLeft;
        }else {
            nodeParent.right = nodeLeft;
        }
    }
    //左旋
    private void leftRotate(Node node){
        Node nodeParent = node.parent;
        Node nodeRight = node.right;
        Node nodeRightLeft = nodeRight.left;
        if (nodeRightLeft != null){
            nodeRightLeft.parent = node;
        }
        nodeRight.left = node;
        nodeRight.parent = nodeParent;
        node.right = nodeRightLeft;
        node.parent = nodeRight;
        if (nodeParent == null){
            root = nodeRight;
        }else if (nodeParent.left == node){
            nodeParent.left = nodeRight;
        }else {
            nodeParent.right = nodeRight;
        }
    }



    /**新增或更新*/
    public void put(int key,Object value){
        Node p = root;
        Node parent = null;
        while (p != null){
            if (key < p.key){
                p = p.left;
            } else if(p.key < key){
                p = p.right;
            } else {
                p.value = value;
                return;
            }
        }
        Node inserted = new Node(key,value);

        if (parent == null){
            root = inserted;
        }else if (key < parent.key){
            parent.left = inserted;
            inserted.parent = parent;
        }else {
            parent.right = inserted;
            inserted.parent = parent;
        }
        fixRedRed(inserted);
    }
    /**节点调整*/
    private void fixRedRed(Node x){
        //插入节点是跟节点
        if (x == root){
            x.color = Color.BLACK;
            return;
        }
        //插入节点的父亲是黑色
        if (isBlack(x.parent)){
            return;
        }
        //红红相邻且叔叔为红
        Node parent = x.parent;
        Node uncle = x.isUncle();
        Node grandparent = parent.parent;
        if (isRed(uncle)){
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            grandparent.color = Color.RED;
            fixRedRed(grandparent);
            return;
        }
        //红红相邻且叔叔为黑
        if(parent.isLeftChild() && x.isLeftChild()){//LL
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        }else if (parent.isLeftChild() && !x.isLeftChild()){//LR
            leftRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (!parent.isLeftChild() && !x.isLeftChild()){//RR
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        }else {//RL
            rightRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        }
    }



    /**删除*/
    public void remove(int key){
        Node deleted = find(key);
        if (deleted == null){return;}
        doRemove(deleted);
    }
    private void doRemove(Node deleted){
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;
        if (replaced == null){//没有孩子
            //删除的是根节点
            if (deleted == root){
                root = null;
            }else {
                if(isBlack(deleted)){
                    //复杂调整
                    fixDoubleBlack(deleted);
                }else {
                    // 无需处理
                }
                if (deleted.isLeftChild()){
                    parent.left = null;
                }else {
                    parent.right = null;
                }
                deleted.parent = null;
            }
            return;
        }
        //有一个孩子
        if (deleted.left == null || deleted.right == null){
            //删除的是根节点
            if (deleted == null){
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            }else {
                if (deleted.isLeftChild()){
                    parent.left = replaced;
                }else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.left = deleted.right = deleted.parent = null;//有助于垃圾回收
                if (isBlack(deleted) && isBlack(replaced)){
                    //复杂处理
                    fixDoubleBlack(replaced);
                }else {
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }
        //有两个孩子(是一种转换的操作)
        int t = deleted.key;
        deleted.key = replaced.key;
        replaced.key = t;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;

        doRemove(replaced);
    }
    /**遇到双黑的不平衡的复杂处理
     * x表示待调整的结点
     * */
    private void fixDoubleBlack(Node x){
        if (x == root){
            return;
        }
        Node parent = x.parent;
        Node sibling = x.sibling();
        //被调整节点的兄弟节点为红色
        if (isRed(sibling)){
            if (x.isLeftChild()){
                leftRotate(parent);
            }else {
                rightRotate(parent);
            }
            parent.color = Color.RED;
            sibling.color = Color.BLACK;
            fixDoubleBlack(x);
            return;
        }
        //兄弟是黑色且两个侄子都是黑色
        if (sibling != null){
            if (isBlack(sibling.left) && isBlack(sibling.right)){
                sibling.color = Color.RED;
                if (isRed(parent)){
                    parent.color = Color.BLACK;
                }else {
                    fixDoubleBlack(parent);
                }
            }
            //兄弟是黑色但是侄子有红色
            else {
                //LL
                if(sibling.isLeftChild() && isRed(sibling.left)){
                    rightRotate(parent);
                    sibling.left.color = Color.BLACK;
                    sibling.color = parent.color;
                    parent.color = Color.BLACK;
                }
                //LR
                else if (sibling.isLeftChild() && isRed(sibling.right)){
                    sibling.right.color = parent.color;
                    leftRotate(sibling);
                    rightRotate(parent);
                    parent.color = Color.BLACK;
                }
                //RL
                else if (!sibling.isLeftChild() && isRed(sibling.left)){
                    sibling.left.color = parent.color;
                    rightRotate(sibling);
                    leftRotate(parent);
                    parent.color = Color.BLACK;
                }
                //RR
                else {
                    leftRotate(parent);
                    sibling.right.color = Color.BLACK;
                    sibling.color = parent.color;
                    parent.color = Color.BLACK;
                }
            }
        }else {
            fixDoubleBlack(parent);
        }

    }
    /**找到要删除的结点*/
    private Node find(int key){
        Node p = root;
        while (p != null){
            if (key < p.key){
                p = p.left;
            }else if (key > p.key){
                p = p.right;
            }else {
               return p;
            }
        }
        return null;
    }
    /**查找剩余结点*/
    private Node findReplaced(Node deleted){
        if (deleted.left == null && deleted.right == null){
            return null;
        }else if (deleted.left == null){
            return deleted.right;
        }else if(deleted.right == null){
            return deleted.left;
        }
        Node s = deleted.right;
        while (s.left != null){
            s = s.left;
        }
        return s;
    }
}

3.总结

有一说一,这红黑树确实比前面的几种树要难一点,主要是它的属性太多,限制太多。说实话,这篇文章中仅仅只是简单的介绍了一下红黑树,实现了一下红黑树的相关操作,但是红黑树的增加和删除中的一些操作没有细讲,这个有时间了我后面会单独再出一篇红黑树的补充文章然后细讲,这篇就这样了吧。

最后说一点,对于这种类似于链式的结构(实际是树形结构),我们要掌握它的定义,条件,然后画图,然后对照图来进行相关的操作,然后再用代码去实现,这样好一点,而不是凭空想象着去写,那样是写不出来的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1057996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫HTTP代理:获取多种类型数据的神器

爬虫HTTP代理是一种常用的数据采集工具&#xff0c;它可以帮助用户获取各种类型的数据。以下是爬虫HTTP代理能获取的一些常见数据&#xff1a; 网页数据 爬虫HTTP代理最常用的功能就是获取网页数据。通过代理服务器&#xff0c;用户可以获取到被封锁或限制访问的网站数据&…

pytorch之nn.Conv1d详解

自然语言处理中一个句子序列&#xff0c;一维的&#xff0c;所以使用Conv1d

Linux--socket编程

socket套接字编程 一、服务器和客户端的开发步骤&#xff1a; 1、创建套接字 2、为套接字添加信息&#xff08;ip地址和端口号&#xff09; 3、监听网络连接 4、监听到有客户端接入&#xff0c;接受连接&#xff08;如没有接入&#xff0c;会发生阻塞到&#xff09; 5、数据…

【题库】咸鱼之王答题挑战题库大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] [{name: , value: 未匹配到内容},# 第一期{name: 《三国演义》中&#xff0c;「大意失街亭」的是马谩&#xff1f;, value: 对},{name: 《三国演义》中&#xff0c;「挥泪斩马谩」的是孙权&#xff1f;, value: 错…

算法设计与分析第一周题目

Leetcode 4.寻找两个正序数组的中位数 题目描述&#xff1a; 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 解法1&#xff1a;先排序再查找 先…

上海建筑模板厂家:承载城市梦想的力量

上海&#xff0c;作为中国最具国际化和现代化的城市之一&#xff0c;拥有令人瞩目的建筑风貌和繁荣的建筑业。在这座充满活力的城市里&#xff0c;建筑模板作为建筑施工的重要组成部分&#xff0c;发挥着不可或缺的作用。而上海建筑模板厂家作为支撑城市发展的重要力量&#xf…

北大硕士7年嵌入式学习经验分享

阶段 1 大一到大三这个阶段我与大多数学生相同&#xff1a; 学习本专业知识&#xff08;EE专业&#xff09;&#xff0c;学习嵌入式软件开发需要的计算机课程&#xff08;汇编原理&#xff0c;计算机组成原理&#xff0c;操作系统&#xff0c;C语言等&#xff09;&#xff0c…

Git使用【中】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析3 目录 &#x1f449;&#x1f3fb;分支管理分支概念git branch&#xff08;查看/删除分…

MacOS怎么安装Nacos(附带:Windows系统)

MacOS安装Nacos&#xff08;一定要配置JDK的环境变量&#xff0c;后面告诉你为什么&#xff1f;&#xff09; &#xff08;1&#xff09;进入Nacos官网&#xff0c;前往githubhomehomehttp://nacos.io/zh-cn/ &#xff08;2&#xff09;点击右下角的releases 然后点击Tags 选择…

【算法】排序——选择排序和交换排序(快速排序)

主页点击直达&#xff1a;个人主页 我的小仓库&#xff1a;代码仓库 C语言偷着笑&#xff1a;C语言专栏 数据结构挨打小记&#xff1a;初阶数据结构专栏 Linux被操作记&#xff1a;Linux专栏 LeetCode刷题掉发记&#xff1a;LeetCode刷题 算法头疼记&#xff1a;算法专栏…

React18入门(第一篇)——JSX、TSX语法详解

文章目录 一、JSX 语法简介二、和 HTML 标签的几点不同三、JSX 属性四、JSX 事件4.1 简单点击事件4.2 类型限制4.3 带参数&#xff0c;箭头函数 五、插入 JS 变量六、JSX 中使用条件判断七、循环 一、JSX 语法简介 JSX - 是 JS 的扩展&#xff0c;写在 JS 代码里面&#xff0c…

【Docker】Docker的应用包含Sandbox、PaaS、Open Solution以及IT运维概念的详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

Windows下Tensorflow docker python开发环境搭建

前置条件 windows10 更新到较新的版本&#xff0c;硬件支持Hyper-V。 参考&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/install 启用WSL 在Powershell中输入如下指令&#xff1a; dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsys…

数据挖掘(2)数据预处理

一、数据预处理 1.1概述 数据预处理的重要性 杂乱性&#xff1a;如命名规则。重复性&#xff1a;同一客观事再不完整性&#xff1a;噪声数据&#xff1a;数据中存在错误或异常的现象。 数据预处理的常见方法 数据清洗&#xff1a;去掉数据中的噪声&#xff0c;纠正不一致。数…

【C语言】循环结构程序设计 (详细讲解)

前言&#xff1a;前面介绍了程序中常常用到的顺序结构和选择结构&#xff0c;但是只有这两种结构是不够的&#xff0c;还有用到循环结构(或者称为重复结构)。因为在日常生活中或是在程序所处理的问题中常常遇到需要重复处理的问题。 【卫卫卫的代码仓库】 【选择结构】 【专栏链…

C语言数组和指针笔试题(五)(一定要看)

这里写目录标题 指针运算笔试题解析题目1解析结果 题目2解析结果 题目3解析结果 题目4解析结果 题目5解析结果 题目6解析结果 题目7解析结果 题目8解析结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412;个人主页 &a…

【智能家居项目】裸机版本——设备子系统(LED Display 风扇)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 输入子系统中目前仅实现了按键输入&#xff0c;剩下的网络输入和标准输入在以后会逐步实现&am…

生鲜蔬果同城配送社区团购小程序商城的作用是什么

生鲜蔬果行业作为市场主要支撑之一&#xff0c;从业商家众多的同时消费者也从不缺&#xff0c;尤其对中高城市&#xff0c;生鲜蔬果除了传统线下超市、市场经营外&#xff0c;线上更是受到大量消费者信任&#xff0c;而很多商家也是自建了生鲜蔬果商城多场景生意经营。 那么通…

家具商家通过商城小程序发展的作用是什么

家具商品覆盖床具、桌椅茶几、沙发等多个细分种类&#xff0c;市场需求较高&#xff0c;而传统消费者也是通过线下方式购买配送&#xff0c;但随着线下经营痛点显现&#xff0c;如流量匮乏拓客难、无法满足同城外地客户随时购物需求、营销难、经营难等&#xff0c;因此不少商家…

第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和

第五十六天| 第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和 一、1143. 最长公共子序列 题目链接&#xff1a; 题目介绍&#xff1a; 思路&#xff1a; 本题和“最长重复子数组”区别在于**这里不要求是连续的了&#xff0c;但要有相对顺序*…