二叉平衡树 之 红黑树 (手动模拟实现)

news2024/11/17 20:32:21

目录

1、红黑树的概念

2、红黑树的性质

 3、红黑树节点的定义

4、红黑树的插入

5、红黑树验证

代码汇总

6、红黑树的删除(了解)

7、红黑树的应用

8、红黑树 VS AVL树


1、红黑树的概念

        红黑树,就是一种特殊的二叉搜索树,每个节点会增加一个存储位来表示颜色,可以是红色或者黑色(Red/Black)

        通过对于任何一条从根节点到叶子节点的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其路径长出两倍,因此是红黑树是接近平衡的


2、红黑树的性质

  • 最长路径最多是最短路径的2倍
  • 每个节点不是红色就是黑色
  • 根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子节点是黑色的【没有2个连续的红色节点
  • 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
  • 每个叶子结点都是黑色的【这里的叶子节点指的是空节点】

例如一个红黑树:

 

最长路径最多是最短路径的2倍:


 3、红黑树节点的定义

public class RBTree {
    static class RBTreeNode {
        public RBTreeNode left;
        public RBTreeNode right;
        public RBTreeNode parent;
        public int val;
        public COLOR color;

        public RBTreeNode(int val) {
            this.val = val;
            //默认新创建的节点为红色
            this.color = COLOR.RED;
        }
    }
    
    public RBTreeNode root;
    

}

其中COLOR是一个枚举类型:

public enum COLOR {
    RED,BLACK
}

4、红黑树的插入

        同AVL树类似理解,他们都是在二叉搜索树上加上平衡限制条件,so?红黑树的插入也是分为两步:

  • 按照二叉搜索树的规则插入新节点
  • 检测新节点插入后,如果红黑树的性质遭到破坏,则对其进行修改

二叉搜索树的插入:

    /**
     * 插入节点
     * @param val
     * @return
     */
    public boolean insert(int val) {
        RBTreeNode node = new RBTreeNode(val);
        RBTreeNode cur = root;
        RBTreeNode parent = null;
        while (cur != null) {
            if(cur.val > val) {
                parent = cur;
                cur = cur.left;
            } else if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else {
                return false;
            }
        }
        if(parent.val < val) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        
        node.parent = parent;
        cur = node;
        
        //红黑树,调整颜色
        //...
    }

        因为加入的新节点其颜色默认是红色,因此:

  • 如果它的双亲节点的颜色是黑色,则没有违反红黑树的任何规则,就不需要对其调整;
  • 如果新加入节点的双亲节点颜色为红色时,就违反了性质【红色节点不能连在一起】,需要进行调整

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

如果不懂左右单旋的伙伴们,可以回顾一下: http://t.csdn.cn/3PyVW

  • 情况一:cur为红,p为红,g为黑,u存在且为红

  • 如果g是根节点,调整完成后,需要将g改为黑色 
  • 如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整

 代码:

parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
//继续向上修改
cur = grandFather;
parent = cur.parent;
  • 情况2:cur为红,p为红,g为黑,u不存在/u为黑

 代码:

rotateRight(grandFather);
grandFather.color = COLOR.RED;
parent.color = COLOR.BLACK;
  • 情况三:cur为红,p为红,g为黑,u不存在/u为黑

代码:

                    //情况三:
                    if(cur == parent.right) {
                        rotateLeft(parent);
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                    }//情况三变到了情况二
                    //情况二:
                    rotateRight(grandFather);
                    grandFather.color = COLOR.RED;
                    parent.color = COLOR.BLACK;

5、红黑树验证

类比于AVL树,红黑树的验证分两步:

  • 检测是否满足二叉搜索树(中序遍历是否有序)
  • 检测是否满足红黑树的各个性质

代码我就直接放在了代码汇总里~~~


代码汇总

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-02-01
 * Time:12:45
 */
enum COLOR {
    RED,BLACK
}
public class RBTree {
    static class RBTreeNode {
        public RBTreeNode left;
        public RBTreeNode right;
        public RBTreeNode parent;
        public int val;
        public COLOR color;

        public RBTreeNode(int val) {
            this.val = val;
            //默认新创建的节点为红色
            this.color = COLOR.RED;
        }
    }

    public RBTreeNode root;


    /**
     * 插入节点
     * @param val
     * @return
     */
    public boolean insert(int val) {
        RBTreeNode node = new RBTreeNode(val);
        RBTreeNode cur = root;
        RBTreeNode parent = null;
        while (cur != null) {
            if(cur.val > val) {
                parent = cur;
                cur = cur.left;
            } else if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else {
                return false;
            }
        }
        if(parent.val < val) {
            parent.right = node;
        } else {
            parent.left = node;
        }

        node.parent = parent;
        cur = node;

        //红黑树,调整颜色
        //...

        while (parent != null && parent.color == COLOR.RED) {
            RBTreeNode grandFather = parent.parent;//该引用不会是空的
            if(parent == grandFather.left) {
                RBTreeNode uncle = grandFather.right;
                if(uncle != null && uncle.color == COLOR.RED) {
                    //情况一:
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;

                    //继续向上修改
                    cur = grandFather;
                    parent = cur.parent;
                } else {
                    //uncle不存在 或者 uncle是黑色的
                    //情况三:
                    if(cur == parent.right) {
                        rotateLeft(parent);
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                    }//情况三变到了情况二
                    //情况二:
                    rotateRight(grandFather);
                    grandFather.color = COLOR.RED;
                    parent.color = COLOR.BLACK;
                }
            } else {
                //parent == grandFather.right
                RBTreeNode uncle = grandFather.left;
                if(uncle != null && uncle.color == COLOR.RED) {
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;
                    //继续向上修改
                    cur = grandFather;
                    parent = cur.parent;
                } else {
                    //情况三:
                    if(cur == parent.left) {
                        rotateRight(parent);
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                    }//情况三变到情况二
                    //情况二:
                    rotateLeft(grandFather);
                    grandFather.color = COLOR.RED;
                    parent.color = COLOR.BLACK;
                }
            }
        }
        root.color = COLOR.BLACK;
        return true;
    }

    /**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(RBTreeNode parent) {
        RBTreeNode subR = parent.right;
        RBTreeNode subRL = subR.left;

        parent.right = subRL;

        subR.left = parent;
        if(subRL != null) {
            subRL.parent = parent;
        }

        RBTreeNode pParent = parent.parent;
        parent.parent = subR;

        if(root == parent) {
            root = subR;
            root.parent = null;
        }else {
            if(pParent.left == parent) {
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
    }


    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(RBTreeNode parent) {

        RBTreeNode subL = parent.left;
        RBTreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        //没有subLR
        if(subLR != null) {
            subLR.parent = parent;
        }
        //必须先记录
        RBTreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查 当前是不是就是根节点
        if(parent == root) {
            root = subL;
            root.parent = null;
        }else {
            //不是根节点,判断这棵子树是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
    }

    /**
     * 判断当前树 是不是红黑树
     *  得满足 红黑树的性质
     * @return
     */
    public boolean isRBTree() {
        if(root == null) {
            //如果一棵树是空树,那么这棵树就是红黑树
            return true;
        }

        if(root.color != COLOR.BLACK) {
            System.out.println("违反了性质:根节点必须是黑色的!");
        }

        //存储当前红黑树当中 最左边路径的黑色的节点个数
        int blackNum = 0;
        RBTreeNode cur = root;
        while (cur != null) {
            if(cur.color == COLOR.BLACK) {
                blackNum++;
            }
            cur = cur.left;
        }
        //检查是否存在两个连续的红色节点  && 每条路径上黑色的节点的个数是一致的
        return checkRedColor(root) && checkBlackNum(root,0,blackNum);
    }

    /**
     *
     * @param root
     * @param pathBlackNum 每次递归的时候,计算黑色节点的个数  0
     * @param blackNum 事先计算好的某条路径上的黑色节点的个数   2
     * @return
     */
    private boolean checkBlackNum(RBTreeNode root,int pathBlackNum,int blackNum) {
        if(root == null) return true;
        if(root.color == COLOR.BLACK) {
            pathBlackNum++;
        }
        if(root.left == null && root.right == null) {
            if(pathBlackNum != blackNum) {
                System.out.println("违反了性质:每条路径上黑色的节点个数是不一样的!");
                return false;
            }
        }
        return checkBlackNum(root.left,pathBlackNum,blackNum) &&
                checkBlackNum(root.right,pathBlackNum,blackNum);
    }

    /**
     * 检查是否存在两个连续的红色节点
     * @param root
     * @return
     */
    private boolean checkRedColor(RBTreeNode root) {
        if(root == null) return true;
        if(root.color == COLOR.RED) {
            RBTreeNode parent = root.parent;
            if(parent.color == COLOR.RED) {
                System.out.println("违反了性质:连续出现了两个红色的节点");
                return false;
            }
        }
        return checkRedColor(root.left) && checkRedColor(root.right);
    }

    public void inorder(RBTreeNode root) {
        if(root == null) {
            return;
        }
        inorder(root.left);
        System.out.print(root.val+" ");
        inorder(root.right);
    }

}

6、红黑树的删除(了解)

找替代节点【替代节点的左子树、右子树一定有一个为空】


7、红黑树的应用

 1. java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树
2. C++ STL库 -- map/set、mutil_map/mutil_set
3. linux内核:进程调度中使用红黑树管理进程控制块,epoll在内核中实现时使用红黑树管理事件块
4. 其他一些库:比如nginx中用红黑树管理timer等


8、红黑树 VS AVL树

  • 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN)
  • 红黑树不追求绝对平衡,只保证最长路径不超过最短路径的2倍,降低了插入时旋转的次数
  • 在经常进行增删的结构中,红黑树性能比AVL树更优,并且红黑树的实现相对简单,所以实际中红黑树使用更多

好啦,下期见咯~

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

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

相关文章

MySQL详解(四)——高级 2.0

性能分析 Explain 使用EXPLAIN关键字可以模拟优化器&#xff08;不改变查询结果前提下&#xff0c;调整查询顺序&#xff0c;生成执行计划&#xff09;执行SQL查询语句&#xff0c;从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈 功能&#x…

ECharts线性渐变色示例演示(2种渐变方式)

第003个点击查看专栏目录Echarts的渐变色采用了echarts.graphic.LinearGradient的方法&#xff0c;可以根据代码中的内容来看如何使用。线性渐变&#xff0c;多用于折线柱形图&#xff0c;前四个参数分别是 x0, y0, x2, y2, 范围从 0 - 1&#xff0c;相当于在图形包围盒中的百分…

PTA L1-025 正整数A+B(详解)

前言&#xff1a;本期是关于正整数AB的详解&#xff0c;内容包括四大模块&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读&#xff0c;今天你c了吗&#xff1f; 题目&#xff1a; 题的目标很简单&#xff0c;就是求两个正整数A和B的和&#xf…

用户使用苹果AirTag来追踪宠物存在风险,苹果Find My功能用处广

苹果的 AirTag 不失为追踪宠物的一种便捷方式&#xff0c;这样宠物即便挣脱宠物圈或者其它方式丢失&#xff0c;都可以通过“Find My”方式追踪定位。正如《华尔街日报》所指出的&#xff0c;这种方式也存在 AirTag 被宠物吞食的风险。 AirTag 的直径为 1.26 英寸&#xff0c…

【Faster R-CNN】之 Resize_and_Padding 代码精读

【Faster R-CNN】之 Resize_and_Padding1、前言&#xff1a;2、resize_image_and_bbox1&#xff09;先对图像做resize处理2&#xff09;再对 bounding box 做resize处理3、padding_images代码1、前言&#xff1a; 在上一篇文章 【Faster R-CNN】之 Dataset and Dataloader 代码…

Linux网络:传输层之UDPTCP协议

文章目录一、端口号1.端口号范围划分2.常用命令二、UDP 协议1.格式2.特点3. UDP 的缓冲区4. UDP 使用注意事项5.基于 UDP 的应用层协议三、TCP 协议1.格式2.确认应答机制3.超时重传机制4.连接管理机制三次握手四次挥手5.滑动窗口6.流量控制7.拥塞控制8.延迟应答9.捎带应答10.面…

PyQt5利用Qt Designer制作一个可以拖动获取文件信息的页面

前言 本篇在讲什么 用pyqt5制作一个简单的程序&#xff0c;拖动文件或脚本可以读取文件信息 本篇适合什么 适合初学PyQt5的小白 本篇需要什么 对Python语法有简单认知 对Qt有简单认知 依赖Pycharm编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&…

[Golang实战]整理Golang忽略的问题

整理Golang忽略的问题参考资料1.WaitGroup与GoRoutine的竞速2.Mutex互斥锁和RWMutex互斥读写锁3.poll,select,epoll4.何时栈和堆?5.GoRoutine合理使用6.GoRoutine优雅退出6.1data channel关闭通知退出6.2exit channel关闭通知退出6.3context超时或取消通知退出6.4WaitGroup/Er…

IPWorks EDI 2022.0.8381 for NET Crack

IPWorks EDI基于用于安全 EDI 通信&#xff08;AS2、SFTP、OFTP、RosettaNet、MLLP 等&#xff09;的领先 EDI-INT 协议&#xff0c;IPWorks EDI 库包含促进安全 EDI 消息传递以及 EDI 映射、翻译和验证&#xff08;X12、 EDIFACT、HL7、TRADACOMS、VDA、XML 和 JSON&#xff0…

golang/安装

golang中文官网 https://golang.google.cn/ golang下载 安装 一路next 配置 配置值说明GOROOTD:\ProgramFiles\golanggolang安装目录PATHD:\ProgramFiles\golang\bingolang命令路径GO111MODULEon开启go.mod功能&#xff0c;go.mod是go官方依赖包管理工具GOPROXYhttps://go…

【FPGA笔记系列7】时序逻辑电路基础D触发器

时序逻辑电路 组合逻辑与时序逻辑电路的本质区别:时序逻辑电路的输出和前一时刻的状态有关,组合逻辑电路的输出只和当前的输入有关 与非门RS锁存器的缺陷:当SR从00变到11时,状态不稳定! 电路中小圈圈表示低电平有效! 透明锁存器 R=1当En=1时,Q=S当En=0时,后面为RS触发器…

使用git合并两个不同项目代码

使用git合并两个不同项目代码 前言, 这里解决的是两个不同的项目, 因为不同项目那必然是两个不同的git仓库 都是不同的git仓库了那就更不可能是相同的分支了(即使分支名相同) 至于为什么会有这种业务情况出现, 我也不知道, 反正先学干就完了 这里图形化界面演示用的是idea自带的…

人工智能时代八大类算法你了解吗?(包邮送书6本)

文章目录本文导读1. 关联规则分析2. 回归分析3. 分类分析4. 聚类分析5. 集成学习6. 自然语言处理7. 图像处理8. 深度学习9. 书籍推荐&#xff08;包邮送书6本&#xff09;本文导读 从零带你了解人工智能时代需要掌握的8大类算法&#xff0c;包括基础理论、关联规则分析、回归分…

Java-基础-4.IO流

一&#xff1a;为什么有IO流&#xff1f; 在显示生产中&#xff0c;我们的数据&#xff0c;都是不停的往过输入和输出&#xff0c;我们将这种模式称作为流。并且在输入和输出的过程中&#xff0c;我们包装了一些其他类。 二&#xff1a;什么是IO流&#xff1f; 1. 按照数据处理…

Linux学习之常用基本命令【1】

文章目录前言一 Linux系统简介二 补充知识Unix和Minix三 开关机命令四 系统目录结构五 树形显示文件目录结构六 目录管理6.0 目录操作常用命令6.1 ls(列出目录)【常用命令】6.2 cd(切换目录)6.3 pwd( 显示当前所在的目录 )6.4 mkdir&#xff08;make directory创建目录&#xf…

Power BI折线图

如果要展现数据的趋势变化&#xff0c;折线图应该是不二之选&#xff0c;并且它更擅长于展现时间序列下的数据&#xff0c;根据折线斜率的不同展现变化的速率。 本文使用PowerBI Desktop来轻松生成一个折线图。 案例数据&#xff1a;2006-2015年各省市的三个产业的产值&#…

【SAP Abap】X档案:SAP 快速提供基础数据给第三方系统访问的几种方法

SAP 快速提供基础数据给第三方系统访问的几种方法1、数据封装2、开放RFC访问3、开放接口服务4、开放DB访问1、数据封装 在企业信息系统建设过程中&#xff0c;少不了的就是系统集成数据对接。 尤其是SAP系统中大量的基础数据集成&#xff0c;如各种字段值域&#xff0c;需要提…

Vue2笔记02 表单数据,过滤器,常见指令,生命周期,组件

表单数据 过滤器 过滤器&#xff1a;将数据进行简单处理后再使用 好用的第三方库的网站&#xff1a;BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务 显示当前时间计算属性的写法 过滤器的写法 多个过滤器可以串联 &#x1f446;这里的过滤器是局部过滤器&#xff0c…

day03_java基本语法

今日内容 零、复习昨日 一、开发工具 二、Eclipse使用 三、程序解读 四、输出语句 五、常量 六、变量 七、数据类型 零、 复习昨日 见晨考.txt 一、开发工具 开发工具: Eclipse(免费),IDEA(收费) 1.1 Eclipse安装 Eclipse是绿色安全的,直接解压即可使用 1.2 启动Eclipse ps:…

原型和原型链

什么是原型&#xff1f; 因为每一个函数都有一个属性&#xff0c;这个属性名就是prototype,&#xff08;即为显式原型&#xff09; 这个属性的值是一个对象 每一个实例对象都有一个__proto__&#xff08;即为隐式原型&#xff09; 原型就是函数的prototype属性&#xff0c;…