红黑树:强大的数据结构之插入详解,附图

news2024/9/23 22:29:50

一、红黑树概述

红黑树是一种自平衡二叉查找树,具有以下性质:节点要么是红色要么是黑色;根节点是黑色;每个叶子节点(NIL 节点)是黑色;每个红色节点的两个子节点都是黑色;从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

在插入新节点时,新节点初始颜色为红色,这样可以尽量减少对树结构的影响。但如果新节点的父节点也是红色,就会违反红黑树的性质,此时需要进行调整。红黑树插入新节点主要有以下四种情况:

  1. 新节点位于根节点:若新节点位于根节点,其没有父节点时,将该节点直接设为黑色即可。
  1. 新节点的父节点已然是黑色:这种情况下不用进行调整,因为这已经是一棵符合红黑树性质的树。
  1. 父节点和叔节点都是红色:将父节点和叔节点设为黑色,将祖父节点设为红色,并将祖父节点设为当前节点,继续对新当前节点进行操作。
  1. 父节点是红色,叔节点是黑色:又分为四种情况。
    • 当前节点是父亲的左孩子,父亲是祖父的左孩子(Left-Left):将祖父节点右旋,交换父节点和祖父节点的颜色。
    • 当前节点是父亲的右孩子,父亲是祖父的左孩子(Right-Left):将父节点左旋,并将父节点作为当前节点,然后再使用 Left-Left 情形。
    • 当前节点是父亲的右孩子,父亲是祖父的右孩子(Right-Right):将祖父节点左旋,交换父节点和祖父节点的颜色。
    • 当前节点是父亲的左孩子,父亲是祖父的右孩子(Left-Right):将父节点右旋,并将父节点作为当前节点,然后再使用 Right-Right 情形。

通过这些规则和调整方法,可以确保在插入新节点后,红黑树依然保持其自平衡的性质。红黑树在计算机科学中有广泛的应用,如在 Java 的集合框架、数据库索引、操作系统内核等领域都发挥着重要作用。

红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的----就是说:  一条路径中没有连续的红色节点
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点----每条路径黑色节点数量是相等的
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

二、主要两种情况加图分析

其实主要是俩种情况,其它都是变形出来的

P:parent

G:grandfather

U:uncle

三角形可以理解为完整的红黑树,或者空节点,不予考虑

情况1:cur为红 p、u为红,g为黑

解决:p和u变黑、g变红(g不为根)

情况2:cur为红,p为红g为黑,u不存在/u存在且为黑 

1、u不存在、abcde都是空,cur是新增   :右旋,p变黑,g变红

2、u存在且为黑     de是空或红,c是有一个黑色节点的红黑树,cur是黑的是由情况一变红的

情况一:

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

情况二

说明:u的情况有两种
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
2.如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转p、g变色--p变黑,g变红

插入规则与四种情况--上面的才是最重要的分析,此处是详细的四种情况

(一)新节点位于根节点

若新节点为根节点,无父节点,直接设为黑色。此情况简单直接,确保根节点满足红黑树性质。红黑树要求根节点为黑色,这样可以保证树的整体稳定性。当新插入的节点成为根节点时,为了满足红黑树的性质,必须将其颜色设为黑色。例如,在某些实际应用中,如果一开始红黑树为空,插入第一个节点时,就会将这个节点设为根节点并将其颜色设为黑色。

(二)父节点为黑色

当新节点的父节点已然是黑色时,无需调整,因这已符合红黑树的部分性质,新节点的插入不会破坏树的平衡。因为红黑树的性质规定,红色节点的子节点必须是黑色,而当父节点为黑色时,新插入的红色节点不会导致连续两个红色节点出现,也不会影响从根节点到叶子节点的路径上黑色节点的数量。所以在这种情况下,无需进行任何调整,红黑树依然保持平衡。

(三)父节点和叔节点都是红色

将父节点和叔节点设为黑色,祖父节点设为红色,然后将祖父节点作为当前节点继续进行操作。此操作通过调整节点颜色来维持红黑树的性质,避免连续红色节点和保持黑色节点数量平衡。例如,假设当前红黑树中有一个节点 A,其颜色为红色,父节点为 B,颜色也为红色,叔节点为 C,颜色同样为红色,祖父节点为 D。按照规则,将 B 和 C 的颜色设为黑色,D 的颜色设为红色,然后以 D 为当前节点继续判断是否满足红黑树的性质。如果 D 还有父节点,就继续按照红黑树的插入规则进行调整。

(四)父节点是红色,叔节点是黑色

此时又分四种情况。若当前节点是父亲的左孩子,父亲是祖父的左孩子(Left-Left),将祖父节点右旋并交换父节点和祖父节点的颜色。例如,有节点 A(当前节点)、B(父节点)、C(祖父节点),A 是 B 的左孩子,B 是 C 的左孩子,此时进行右旋操作,将 C 右旋后,交换 B 和 C 的颜色,这样可以使树重新满足红黑树的性质。

若当前节点是父亲的右孩子,父亲是祖父的左孩子(Right-Left),先将父节点左旋并将父节点作为当前节点,再按 Left-Left 情形处理。比如节点 D(当前节点)、E(父节点)、F(祖父节点),D 是 E 的右孩子,E 是 F 的左孩子,先将 E 左旋,然后以 E 为当前节点,按照 Left-Left 的情况进行处理,即对新的祖父节点进行右旋并交换颜色。

若当前节点是父亲的右孩子,父亲是祖父的右孩子(Right-Right),将祖父节点左旋并交换父节点和祖父节点的颜色。假设节点 G(当前节点)、H(父节点)、I(祖父节点),G 是 H 的右孩子,H 是 I 的右孩子,此时进行左旋操作并交换颜色,以维持红黑树的平衡。

若当前节点是父亲的左孩子,父亲是祖父的右孩子(Left-Right),先将父节点右旋并将父节点作为当前节点,再按 Right-Right 情形处理。例如节点 J(当前节点)、K(父节点)、L(祖父节点),J 是 K 的左孩子,K 是 L 的右孩子,先将 K 右旋,然后以 K 为当前节点,按照 Right-Right 的情况进行调整,即对新的祖父节点进行左旋并交换颜色。这些情况通过旋转和颜色交换来调整树的结构,确保红黑树的性质得以维持。

三、红黑树插入操作详解

红黑树是一种自平衡的二叉搜索树,通过特定的颜色规则和旋转操作来保持树的平衡。以下是对红黑树插入操作函数 Insert 的详细解释。

一、创建新节点并初始化颜色

Node* tmp = new Node(data);
tmp->_col = RED;

这段代码创建了一个新的节点 tmp,并将其颜色初始化为红色。在红黑树中,新插入的节点通常先设为红色,这样在后续的调整过程中可以更容易地满足红黑树的性质。

二、处理新节点为根节点的情况

if (_pHead->_pLeft == _pHead)
{
    _pHead = tmp;
    _pHead->_col = BLACK;
    return true;
}

如果当前树为空(头节点的左子节点指向头节点本身),那么新节点将成为根节点。此时,将新节点设为黑色,以满足红黑树的性质(根节点为黑色),并返回插入成功。

三、寻找插入位置并插入新节点

Node* cur = _pHead;
Node* parent = cur;
while (cur)
{
    if (cur->_data < data)
    {
        parent = cur;
        cur = cur->_pRight;
    }
    else if(cur->_data > data)
    {
        parent = cur;
        cur = cur->_pLeft;
    }
    else if (cur->_data == data)
    {
        return false;
    }
    else
    {
        assert(false);
    }
}
if (data > parent->_data)
{
    parent->_pRight = tmp;
}
else
{
    parent->_pLeft = tmp;
}
tmp->_pParent = parent;
cur = tmp;

这里使用一个循环来找到新节点的插入位置。如果新节点的值大于当前节点的值,就继续在右子树中查找;如果小于当前节点的值,就继续在左子树中查找。当找到合适的插入位置后,将新节点插入到父节点的相应子节点位置,并设置新节点的父指针。最后,将当前指针 cur 指向新插入的节点。

四、处理插入后可能违反红黑树性质的情况

if (parent->_col == BLACK)
{
    return true;
}
else
{
    while (parent && parent->_col!= BLACK)
    {
        Node* grandfather = parent->_pParent;
        if (parent == grandfather->_pLeft)
        {
            Node* uncle = grandfather->_pRight;
            if (cur == parent->_pLeft)
            {
                if (uncle && uncle->_col == RED)
                {
                    // 情况 1:父节点和叔节点都是红色
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = grandfather->_pParent;
                }
                else if(!uncle || uncle->_col == BLACK)
                {
                    // 情况 2:父节点为红色,叔节点为黑色,且当前节点是父节点的左子节点
                    RotateR(grandfather);
                    grandfather->_col = RED;
                    parent->_col = BLACK;
                }
                else
                {
                    assert(false);
                }
            }
            else
            {
                if (uncle && uncle->_col == RED)
                {
                    // 情况 3:父节点和叔节点都是红色
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = grandfather->_pParent;
                }
                else if (!uncle || uncle->_col == BLACK)
                {
                    // 情况 4:父节点为红色,叔节点为黑色,且当前节点是父节点的右子节点
                    RotateL(parent);
                    RotateR(grandfather);
                    grandfather->_col = RED;
                    cur->_col = BLACK;
                }
                else
                {
                    assert(false);
                }
            }
        }
        else
        {
            Node* uncle = grandfather->_pLeft;
            if (cur == parent->_pRight)
            {
                if (uncle && uncle->_col == RED)
                {
                    // 情况 5:父节点和叔节点都是红色
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = grandfather->_pParent;
                }
                else if(!uncle || uncle->_col == BLACK)
                {
                    // 情况 6:父节点为红色,叔节点为黑色,且当前节点是父节点的右子节点
                    RotateL(grandfather);
                    grandfather->_col = RED;
                    parent->_col = BLACK;
                }
                else
                {
                    assert(false);
                }
            }
            else
            {
                if (uncle && uncle->_col == RED)
                {
                    // 情况 7:父节点和叔节点都是红色
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = grandfather->_pParent;
                }
                else if (!uncle || uncle->_col == BLACK)
                {
                    // 情况 8:父节点为红色,叔节点为黑色,且当前节点是父节点的左子节点
                    RotateR(parent);
                    RotateL(grandfather);
                    grandfather->_col = RED;
                    cur->_col = BLACK;
                }
                else
                {
                    assert(false);
                }
            }
        }
    }
    _pHead->_col = BLACK;
    return true;
}

如果新节点的父节点为黑色,那么插入操作完成,无需进一步调整。如果父节点为红色,那么可能违反红黑树的性质,需要进行调整。

  1. 首先,判断父节点是祖父节点的左子节点还是右子节点,并确定叔节点。
  2. 如果当前节点是父节点的左子节点且叔节点为红色,那么将父节点和叔节点设为黑色,祖父节点设为红色,并将当前节点指向祖父节点,继续向上调整。
  3. 如果当前节点是父节点的左子节点且叔节点为黑色,那么进行右旋操作,并将祖父节点设为红色,父节点设为黑色。
  4. 如果当前节点是父节点的右子节点且叔节点为红色,同样将父节点和叔节点设为黑色,祖父节点设为红色,并将当前节点指向祖父节点,继续向上调整。
  5. 如果当前节点是父节点的右子节点且叔节点为黑色,先进行左旋操作,再进行右旋操作,并将祖父节点设为红色,当前节点设为黑色。

最后,将头节点设为黑色,以确保根节点为黑色。

通过以上步骤,红黑树的插入操作能够在保持树的平衡的同时,满足红黑树的性质。

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

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

相关文章

【已解决】ElementPlus 的 el-menu 组件如何用 js 控制展开某个子菜单,并在其他组件中控制使用呢?

文章目录 需求几次探索官网寻找线索&#xff08;解决办法&#xff09; 需求 我如何用代码来实现 ElementPlus 的菜单的展开和收缩呢&#xff1f; 几次探索 尝试通过找到节点之后&#xff0c;使用 click 事件&#xff0c;失败了 // 伪代码如下 const handleFindNodeAndClick …

【数据结构】散列(哈希)表简单介绍

散列表也叫做哈希表&#xff08;Hash table&#xff09;&#xff0c;散列表通过关键码和存储地址建立唯一确定的映射关系&#xff0c;能够快速查找到对应的元素&#xff0c;排序算法中的计数排序就是一种利用哈希进行排序的算法。 一、散列表的概念 散列表&#xff08;Hash ta…

【优选算法之前缀和】No.6--- 经典前缀和算法

文章目录 前言一、前缀和例题模板&#xff1a;1.1 【模板】前缀和1.2 【模板】⼆维前缀和1.3 寻找数组的中⼼下标1.4 除⾃⾝以外数组的乘积1.5 和为 K 的⼦数组1.6 和可被 K 整除的⼦数组1.7 连续数组1.8 矩阵区域和 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f6…

提升晶振电路抗扰性:优化方案解析

在现代电子设备中&#xff0c;晶振作为提供稳定时钟信号的核心组件&#xff0c;其稳定性对整个系统的运行至关重要。然而&#xff0c;电路抗扰性不良往往会导致晶振失效&#xff0c;进而影响设备的整体性能。晶发电子针对这一问题&#xff0c;提出了以下关于晶振电路抗扰性及优…

【C++】拆分详解 - string类

文章目录 一、为什么学习string类&#xff1f;二、标准库中的string类  1. 定义  2. 常用接口说明     2.1 构造     2.2 容量操作     2.3 访问及遍历操作     2.4 修改操作     2.5 非成员函数 三、OJ练习自测  [1. 仅仅反转字母](https://leetcod…

9.23 My_string.cpp

my_string.h #ifndef MY_STRING_H #define MY_STRING_H#include <iostream> #include <cstring>using namespace std;class My_string { private:char *ptr; //指向字符数组的指针int size; //字符串的最大容量int len; //字符串当前…

华为三折叠一拆,苹果脸被打肿了!

文&#xff5c;琥珀食酒社 作者 | 随风 哎呀 苹果这次脸真是被华为狠狠打肿了 那些高高兴兴买iPhone 16的 东西一收到&#xff0c;脸马上就绿了啊 各种意想不到的问题啊 拆开手机后发现有两处掉漆咱就不说了 第一次滑动iPhone 16 Pro屏幕有响应 再滑动就没有响应了咱也…

【27】C++项目练习

练习1 题目如下 代码如下 .h #pragma once #include <string> using namespace std;class Toy { public:Toy();Toy(string name,int price,string place);~Toy();string getName() const;int getPrice() const;string getPlace() const;void changePrice(float count)…

自己开发的windows服务在虚拟机上不能正常启用

最近开发了个数据采集系统&#xff0c;在我本机上发布、安装是没有问题的&#xff1b;但是在虚拟机上进行安装部署的时候&#xff0c;反馈的错误码是1053&#xff0c;服务不能正常启动。 网上搜索可能的原因&#xff0c;如图&#xff1a; 能引起1053的问题比较多&#xff0c;首…

springboot实战学习笔记(4)(Spring Validation参数校验框架、全局异常处理器)

接着上篇博客学习。上篇博客是已经基本完成用户模块的注册接口的开发。springboot实战学习笔记&#xff08;3&#xff09;(Lombok插件、postman测试工具、MD5加密算法、post请求、接口文档、注解、如何在IDEA中设置层级显示包结构、显示接口中的方法)-CSDN博客本篇博客主要是关…

最新版Visual Studio安装教程(超详细,新手必看)

一、官网下载 这里奉上Visual Studio官方下载地址&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/https://visualstudio.microsoft.com/zh-hans/downloads/ 对于我们学习来说&#xff0c;下载第一个社区免费版即可&#xff0c;点击下载。 下载完成以后是…

Kubernetes Pod调度基础(kubernetes)

实验环境依旧是k8s快照&#xff0c;拉取本次实验所需的镜像文件&#xff1b; 然后在master节点上传已经编写好的yaml文件&#xff1b; 然后同步会话&#xff0c;导入镜像&#xff1b; pod控制器&#xff1a; 标签选择器--》标签&#xff1a; 标签&#xff1a; 在Kubernetes&…

还在用windows自带录屏?试试这三款录屏工具

作为一名办公室文员&#xff0c;我经常需要录制电脑屏幕来制作教程或者记录工作流程。在众多的录屏工具中&#xff0c;我尝试了四款不同的录屏工具&#xff0c;包括Windows自带录屏工具。今天&#xff0c;我就来跟大家分享一下我的使用体验&#xff0c;希望能帮助到和我有同样需…

利用代码,玩转腾讯云脱敏服务:Java、Python、PHP案例集

腾讯云数据脱敏服务-数据管理的优势是什么&#xff1f; 腾讯云数据脱敏服务-数据管理 提供了一种高效且灵活的方式来保护敏感数据。其核心优势在于可以在数据处理和传输过程中自动化地执行数据脱敏操作。无论是脱敏信用卡号、身份证号还是其他个人信息&#xff0c;该服务都能精…

Games101笔记-二维Transform变换(二)

1、什么是Transform Transform就是通过一个矩阵&#xff0c;进行缩放、旋转、平移等变换 2、缩放、旋转、切变、平移等基础变换 缩放变换&#xff1a; 反射变换&#xff1a; 切变&#xff1a; 绕原点旋转&#xff1a; 以上都是线性变换&#xff1a; 平移变换&#xf…

线程同步:消费者模型(非常重要的模型)

一.线程同步的概念 线程同步&#xff1a;是指在互斥的基础上&#xff0c;通过其它机制实现访问者对 资源的有序访问。条件变量&#xff1a;线程库提供的专门针对线程同步的机制线程同步比较典型的应用场合就是 生产者与消费者 二、生产者与消费者模型原理 在这个模型中&…

中文文本分词-技术实现

当做语音&文本相关的技术时&#xff0c;经常会涉及到文本的分词实现。以下是对中文的文本简单实现。 一、单个中文句子的分词 import jiebatext_ "我爱我的祖国&#xff01;" # 精确模式 seg_list jieba.cut(text_, cut_allFalse) print("精确模式: &qu…

【51实物与仿真】基于51单片机设计的波形/函数发生器(正弦波、锯齿波、三角波、矩形波,设定频率步进值,改变振幅,LCD显示)——文末完整资料链接

基于51单片机设计的波形函数发生器 演示视频: 功能简介: 1.本设计基于STC89C51/52(与AT89S51/52、AT89C51/52通用,可任选)单片机。 2.LCD1602液晶显示波形种类和频率值(10-100HZ)。 3.按键设置波形种类和设定频率步进值。 4.电位器器改变振幅(0V-3.5V稳定)。 5…

医院预约|基于springBoot的医院预约挂号系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日…

集合根据上下级关系转树结构

1、创建实体对象 public class TreeNode {private String id;private String pid;private String name;private List<TreeNode> children;public TreeNode(String id,String pid,String name){this.id id;this.pid pid;this.name name;}public String getId() {retur…