红黑树详解

news2025/1/12 17:46:07

1.概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

2.性质
最长路径最多是最短路径的2倍

每个结点不是红色就是黑色

根节点是黑色的

如果一个结点是红色的,则它的俩个孩子结点是黑色的【没有俩个连续的红色结点】

对于每个结点,从该节点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

3.实现红黑树

新增的结点不能是黑色的,因为如果是黑色的,那么就需要保证每条路径上的黑色结点必须是相同的。弄不好还得新增加一堆黑色结点。

所以我们新增的结点默认是红色的。

package test;


enum COLOR {
    RED, BLACK
}

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode parent;
    COLOR color;

    public TreeNode(int val) {
        this.val = val;
        this.color = COLOR.RED;
    }
}

public class RBTree {
    public TreeNode root;

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if (root == null) {
            root = node;
            root.color = COLOR.BLACK;
            return true;
        }

        TreeNode cur = root;
        TreeNode pre = null;

        while (cur != null) {
            if (cur.val < val) {
                pre = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                pre = cur;
                cur = cur.left;
            } else {
                return false;
            }
        }

        if (pre.val < val) {
            pre.right = node;
        } else {
            pre.left = node;
        }
        node.parent = pre;
        cur = node;

        while (pre != null && pre.color == COLOR.RED) {
            TreeNode grandFather = pre.parent;
            if (pre == grandFather.left) {
                TreeNode uncle = grandFather.right;
                if (uncle != null && uncle.color == COLOR.RED) {
                    pre.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;
                    cur = grandFather;
                    pre = cur.parent;
                } else {
                    //uncle不存在 或者uncle是黑色的
                    //情况三
                    if (cur == pre.right) {
                        rotateLeft(pre);
                        TreeNode tmp = pre;
                        pre = cur;
                        cur = tmp;
                    }//


                    //情况二
                    rotateRight(grandFather);
                    grandFather.color = COLOR.RED;
                    pre.color = COLOR.BLACK;
                }
            } else {
                //pre == grandFather.left
                TreeNode uncle = grandFather.left;
                if (uncle != null && uncle.color == COLOR.RED) {
                    pre.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;
                    cur = grandFather;
                    pre = cur.parent;
                } else {
                    if (cur == pre.left) {
                        rotateRight(pre);
                        TreeNode tmp = pre;
                        pre = cur;
                        cur = tmp;
                    }
                    rotateLeft(grandFather);
                    grandFather.color = COLOR.RED;
                    pre.color = COLOR.BLACK;
                }
            }
        }

        root.color = COLOR.BLACK;
        return true;
    }

    //左单旋
    public void rotateLeft(TreeNode pre) {
        TreeNode subR = pre.right;
        TreeNode subRL = subR.left;

        pre.right = subRL;
        subR.left = pre;

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

        TreeNode pPre = pre.parent;
        pre.parent = subR;

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

    //右单旋
    public void rotateRight(TreeNode pre) {
        TreeNode subL = pre.left;
        TreeNode subLR = subL.right;
        pre.left = subLR;
        subL.right = pre;
        //没有subLR的时候
        if (subLR != null) {
            subLR.parent = pre;
        }
        TreeNode pPre = pre.parent;
        pre.parent = subL;
        //检查当前是不是根节点
        if (pre == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点,判断这棵树是左子树还是右子树
            if (pPre.left == pre) {
                pPre.left = subL;
            } else {
                pPre.right = subL;
            }
            subL.parent = pPre;
        }
    }

    //判断当前树是不是红黑树
    public boolean isRBTree() {
        if (root == null) {
            //如果一棵树是空树,那么这棵树就是红黑树
            return true;
        }
        if (root.color != COLOR.BLACK) {
            System.out.println("违反了性质,根节点必须是黑色的!");
        }

        //最左边路径黑色结点的个数
        int blackNum = 0;
        TreeNode 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 每次递归的时候计算黑色结点的个数
     * @param blackNum     实现计算好的某条路径上的黑色结点个数
     * @return
     */
    public boolean checkBlackNum(TreeNode 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);

    }

    public boolean checkRedColor(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (root.color == COLOR.RED) {
            TreeNode pre = root.parent;
            if (pre.color == COLOR.RED) {
                System.out.println("违反了性质:连续俩个红色的结点连在一起");
                return false;
            }
        }
        return checkRedColor(root.left) && checkRedColor(root.right);
    }

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

    public static void main(String[] args) {
        int[] array = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
        RBTree rbTree = new RBTree();
        for (int i = 0; i < array.length; i++) {
            rbTree.insert(array[i]);
        }
        rbTree.inorder(rbTree.root);
        System.out.println();
        System.out.println(rbTree.isRBTree());
    }
}

4.AVL树和红黑树比较 

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

5.红黑树应用

java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树
 

未完待续~

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

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

相关文章

漏洞丨cve2010-3333

作者&#xff1a;黑蛋 一、漏洞简介 cve-2010-3333是一个Office 2003 的栈溢出漏洞&#xff0c;其原因是在文档中读取一个属性值的时候&#xff0c;没有对其长度验证&#xff0c;导致了一个溢出&#xff0c;看着很简单的一个漏洞&#xff0c;却又有点恶心人。 二、漏洞环境 …

Python学习基础笔记五十五——接口类和抽象类

# 设计模式&#xff1a; 《设计模式》 接口类&#xff1a;Python原生不支持&#xff1b; 抽象类&#xff1a;Python原生支持的。 例&#xff1a; from abc import abstractmethod, ABCMetaclass Payment(metaclassABCMeta): # 元类 默认的元类是 type 规范类abstract…

MyBatis学习 | 全局配置文件

文章目录一、简介二、各个标签2.1 properties&#xff08;属性&#xff09;2.2 settings&#xff08;设置&#xff09;2.3 typeAliases&#xff08;类型命名&#xff09;2.4 typeHandlers&#xff08;类型处理器&#xff09;2.5 plugins&#xff08;插件&#xff09;2.6 enviro…

电容这段走线影响这么大?

公众号&#xff1a;高速先生 作者&#xff1a;李远恒 一日&#xff0c;来了一个电源仿真项目&#xff0c;雷工像往常一样熟练的打开了PCB文件&#xff0c;先是例行查板。不查不要紧&#xff0c;一查还真有问题&#xff0c;话不多说直接上图&#xff1a; 定睛一看&#xff0c;这…

星火计划学习笔记——参考线平滑算法解析及实现(以U型弯道场景仿真调试为例)

文章目录1. Apollo参考线介绍1.1 参考线的作用1.2 导航规划的路线1.3 为什么需要重新生成参考线1.4 ReferenceLine数据结构1.5 ReferencePoint数据结构1.6 参考线处理流程1.7 参考线生成2. 参考线平滑算法2.1 参考线平滑算法总览2.2 参考线平滑算法流程2.2.1 设置中间点anchor …

康希通信冲刺科创板上市:上半年收入2亿元,计划募资约8亿元

12月21日&#xff0c;格兰康希通信科技&#xff08;上海&#xff09;股份有限公司&#xff08;下称“康希通信”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺科创板上市&#xff0c;康希通信计划募资7.82亿元。 据贝多财经了解&#xff0c;康…

Word处理控件Aspose.Words功能演示:在 Java 中将 DOC 或 DOCX 转换为 PNG

aspose.words是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

【C++入门基础(上)】

Cross the stars over the moon to meet your better-self. 目录 1 命名空间 1.1 命名空间定义 1.2 命名空间使用 1.2.1 加命名空间名称及作用域限定符 1.2.2 使用using将命名空间中成员引入 1.2.3 使用using namespace 命名空间名称引入 2 C输入&&输出 3 缺省参数…

Mentor-dft 学习笔记 day47-On-Chip Clock Controller Design Description

On-Chip Clock Controller Design Description有三种类型的片上控制器设计&#xff1a;standard, parent, and child。可以根据设计要求选择它们。使用OCC时&#xff0c;必须考虑本节中讨论的设计元素。The Standard OCC 标准OCC为快速捕获提供快速时钟&#xff0c;为换档和慢速…

Java字节流的使用:字节输入/输出流、文件输入/输出流、字节数组输入/输出流

InputStream 是 Java 所有字节输入流类的父类&#xff0c;OutputStream 是 Java 所有字节输出流类的父类&#xff0c;它们都是一个抽象类&#xff0c;因此继承它们的子类要重新定义父类中的抽象方法。 这里首先介绍上述两个父类提供的常用方法&#xff0c;然后介绍如何使用它们…

springboot整合shiro + jwt + redis实现权限认证(上手即用)

目录前言项目结构依赖导入建数据库表建表语句使用插件生成增删改查添加MyRealm添加ShiroConfig添加JwtFilterJWT相关得类JwtTokenJwtAudienceJwtHelper添加BeanFactory只贴出主要得类&#xff0c;具体得可以看我的gitee&#xff0c;接口都自测过的。前言 最近项目中涉及到使用…

NEST.JS使用心得

最近部门分享了nest.js技术&#xff0c;旨在前端人员通过项目积累将可重复使用的数据或者自己需要的数据通过nest设计出接口方便快速开发&#xff0c;不需要等待后端开发人员的数据。学习了两天发现nest很有意思&#xff0c;所以来分享下最近两天的学习心得。 nest中文文档&am…

linux下使用命令TC进行网络限流 —— 筑梦之路

Linux 下的流量控制原理 通过对包的排队&#xff0c;我们可以控制数据包的发送方式。这种控制&#xff0c;称之为数据整形&#xff0c;shape the data&#xff0c;包括对数据的以下操作: 增加延时 丢包 重新排列 重复、损坏 速率控制 在 qdisc-class-filter 结构下&#x…

ADI Blackfin DSP处理器-BF533的开发详解64:电子相册的设计(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 代码实现功能 代码实现了将 SD 卡根目录下的所有文件进行文件列表&#xff0c;然后将 480*272 尺寸的 JPEG 文件进行 JPEG 解码&#xff0c;将解…

校园跳蚤市场平台/校园二手交易系统

摘 要 本文论述了校园跳蚤市场平台的设计和实现&#xff0c;该网站从实际运用的角度出发&#xff0c;运用了计算机网站设计、数据库等相关知识&#xff0c;网络和Mysql数据库设计来实现的&#xff0c;网站主要包括学生注册、学生登录、浏览商品、搜索商品、查看商品并进行购买…

Blender——“苹果”建模

效果图 1.调出点线面面板&#xff0c;衰减编辑 1.1打开blender&#xff0c;点击常规&#xff0c;按A全选物体&#xff08;摄像头、光源、正方体&#xff09;&#xff0c;按delete删除。 1.2 在3D视图中添加一个经纬球。点击添加&#xff0c;选择网格—>经纬球。 1.3 点击下…

前端JS也可以连点成线(Vue中运用 AntVG6)

前言 什么是 G6&#xff1f;G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明&#xff0c;简单。让用户获得关系数据的 Insight。其实说白了就是我们前端中的绘图工具&#xff1b;基于 G6&#xff0c;用户可以快速搭…

Linux基础知识-文件目录结构及基本属性

1、前言 上一篇我们讲到了Linux 文件类型7种类型&#xff0c;本篇我们说说Linux文件目录结构。 2、Linux 目录树 所有可操作的计算机资源都存在于目录树这个结构中&#xff0c;对计算资源的访问&#xff0c;可以看做是对这棵目录树的访问。Linux 的目录结构如下&#xff1a;…

常见日志框架使用及日志打印规范设计

文章目录一、slf4j 简介二、常用日志框架1&#xff09;log4jpom 依赖log4j.properties 文件配置测试参考2&#xff09;logbackpom 依赖logback.xml 配置测试参考3&#xff09; java.util.logging4&#xff09;commons loggingpom 依赖配置测试参考5&#xff09;slf4j-simplepom…

MFC UI控件相关

文章目录UI控件相关CDialog::OnInitDialog() 对话框初始化手动添加UpdateData() 刷新窗口数据DoDataExchange()数据与控件动态绑定afx_msg: 声明一个消息响应函数void AFXAPI DDX_Control( CDataExchange* pDX, int nIDC, CWnd& rControl );DDV_MaxChars()UI控件相关 CDia…