红黑树浅浅学习

news2024/11/25 14:39:45

红黑树浅浅学习

    • 红黑树概念
    • 红黑树平衡性调整


红黑树概念

  • 二叉树:二叉树是每个节点最多有两个子树的树结构。
  • 二叉查找树:又称“二叉搜索树”,左孩子比父节点小,右孩子比父节点大,还有一个特性就是”中序遍历“可以让结点有序。
  • 平衡二叉树:它是一颗空树或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
  • 红黑树是一种自平衡的二叉查找树,它属于平衡树,但是却没有平衡二叉树那么“平衡”。可以保证在最坏情况下基本动态操作的时间复杂度为O(log n)。
  • 红黑树中的每个节点都有一个颜色属性,可以是红色或黑色。

红黑树满足以下5个性质:

  • 每个节点要么是红色的,要么是黑色的,根节点是黑色的。
  • 每个叶子节点(NIL节点,空节点)是黑色的。
  • 任何相邻节点不能同时为红色。
  • 任何叶子节点,到根节点,所经过的黑节点数目相同。
  • 当前节点到其所有叶节点包含的黑色节点数量相同。

通过这些性质,红黑树可以保证在插入和删除节点时,自动调整树的结构,以保持树的平衡和性质的满足。相比于普通的二叉查找树,红黑树的平衡性更好,查找、插入和删除都具有更稳定的时间复杂度,因此在很多场景下被广泛应用。
在这里插入图片描述


红黑树平衡性调整

  1. 依次插入 100 90 120,不需要进行平衡性调整
    在这里插入图片描述
  2. 插入85,把90和120变成黑色,100变成红色。但100必须为黑色,100也变成黑色
    在这里插入图片描述

平衡性调整情况1:爷节点为黑色,父节点为红色,且有叔节点为红色,父亲节点为爷节点的左孩子

  • 将父节点和叔节点都变为黑色。
  • 爷节点变成红色
    • 若爷节点变色之后红黑树性质出现问题,需要沿着爷节点继续向上调整
    • 若爷节点为根节点,要变黑色。
  1. 插入60为红色,红黑树继续进行调整,85变成黑色,90变成红色。
    在这里插入图片描述

平衡性调整情况2:爷节点为黑色,父节点为红色,没有叔节点或叔节点为黑色,父亲节点为爷节点的左孩子,插入的节点是父节点的左孩子

平衡性调整情况3:爷节点为黑色,父节点为红色,没有叔节点或叔节点为黑色,父亲节点为爷节点的右孩子,插入的节点是父节点的左孩子

平衡性调整情况4-6分别为1-3的反情况

#include <iostream>
#include <list>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <assert.h>
#include <sstream>
#include <stack>

using namespace std;

template<typename T>
struct RBNode{
    T data;
    RBNode* leftChild;
    RBNode* rightChild;
    RBNode* parentNd;

    bool isRed; //判断是否为红色节点。
};

template<typename T>
class RBTree{
public:
    RBTree():root(nullptr){}

    ~RBTree(){
        ReleaseNode(root);
    }

    void InsertElem(const T&e){
        InsertElem(root,e);
    }

    void InsertElem(RBNode<T>*& tNode, const T& e){ //第一个参数类型:指针引用
        RBNode<T>* point = tNode; // 从指向根节点开始
        RBNode<T>* parent = nullptr; // 保存父节点,根节点的父节点先为nullptr

        // 通过一个while循环寻找要插入节点的位置,同时还要把插入路线上所经过的所有节点都保存到栈中,因为这些节点的平衡因子可能要调整。
        while(point !=nullptr){
            if(e==point->data) return; //要插入的数据和当前树中某节点的数据相同,不允许插入
            
            parent = point; // 记录父节点

            if(e > point->data){
                point = point->rightChild;
            }
            else{
                point = point->leftChild;
            }
        }// end while

        // 走到这里,point 等于nullptr,该生成新节点了
        point = new RBNode<T>;
        point->data = e;
        point->leftChild = nullptr;
        point->rightChild = nullptr;
        point->parentNd = nullptr;
        point->isRed = true; //缺省插入的节点先给红色,之后才会判断需不需要进行调整

        if(parent == nullptr){
            // 创建的是根节点
            point->isRed = false;
            tNode = point;
            return;
        } 

        // 创建的不是根节点,要把节点链接到父节点上
        if(e > parent->data){
            parent->rightChild = point;
        }else{
            parent->leftChild = point;
        }

        point->parentNd = parent;
        if(parent->isRed == false) return; //如果父节点是黑色,当前插入的又是红色节点,不需要做什么直接返回

        BalanceTune(point,parent);
        
        // 不管前面经历了什么,根节点固定黑色
        root->isRed = false;

    }

private:

    // 获取兄弟节点指针
    RBNode<T>* getBrotherNode(RBNode<T>* p){
        // 由调用者确认p->parent 一定不为nullptr
        if(p->parentNd->leftChild == p){
            return p->parentNd->rightChild;
        }
        return p->parentNd->leftChild; 
    }


    // 平衡性调整
    void BalanceTune(RBNode<T>* point, RBNode<T>* parent){
        // 能走到这里的,要插入的节点肯定至少在第三层了,因为如果是第二层,那么插入的节点都是红色的,父节点肯定是黑色的

        // 父节点为红色才能走下来(当前节点为红色,此时需要进行平衡性调整)
        RBNode<T>* parentBroNode = nullptr; //叔节点,可能不存在
        RBNode<T>* grandFatherNode = nullptr; //爷节点,因为父节点为红色,红色不能为根,那么至少都是爷节点做根
        
        while(true){
            parentBroNode = (parent->parentNd !=nullptr) ? (getBrotherNode(parent)):nullptr; //叔节点
            grandFatherNode = point->parentNd->parentNd; //爷节点

            // 不断向上调整,爷节点可能有为空的时候
            if(grandFatherNode == nullptr) break;

            // 如果叔节点为红色,那么爷节点不可能为红色
            if(parentBroNode != nullptr && parentBroNode->isRed == true){//平衡性调整情况1,没有将爷节点置为黑色的原因是统一在外部进行根节点为黑色的设置

                // 先处理变色问题
                // (1)父节点和叔节点变为黑色,爷节点变为红色
                parent->isRed = false;
                parentBroNode->isRed = false;
                grandFatherNode->isRed = true;

                // (2)如果爷节点是根,跳出循环,根节点颜色在循环外进行设置为黑色的处理
                if(grandFatherNode == root) break;


                // (3) 往上走继续循环
                point = grandFatherNode;
                parent = point->parentNd;
                if(parent->isRed = false) break;

                continue;
            } 

            // 能走到这里的平衡性调整情况2,不满足if(parentBroNode != nullptr && parentBroNode->isRed == true)
            // 叔节点为黑色或叔节点为空的情况
            // 旋转变色之前的一些信息,这是通用代码
            RBNode<T>* gff = grandFatherNode->parentNd; //太爷节点
            int sign = 0; // 标记1:grandFatherNode是父节点的左孩子,标记2:grandFatherNode是父节点的右孩子。
            if(gff!=nullptr){
                if(gff->leftChild == grandFatherNode){
                    sign = 1;
                }
                else{
                    sign = 2;
                }
            }
            if(grandFatherNode->leftChild == parent){ //第一种情形,父亲是爷节点的左孩子
                // 开始旋转和变色以调整平衡
                if(parent->leftChild == point){ //新节点是父亲节点的左孩子
                    // 右旋转
                    RotateRight(grandFatherNode);
                }else{ //新节点是父亲节点的右孩子
                    // 先左旋后右旋
                    RotateLeftRight(grandFatherNode);
                }

                // 旋转之后变色的代码,通用
                grandFatherNode->isRed = false; //新的根节点设置为黑色
                grandFatherNode->rightChild->isRed = true; //新右叶子设置为红色
            }else{ // 第二种情形,父亲是爷节点的右孩子
                if(parent->rightChild == point){ //新节点是父亲的右孩子
                    RotateLeft(grandFatherNode);
                }else{
                    RotateRightLeft(grandFatherNode);
                }
                // 旋转变色之后的一些公用代码
                grandFatherNode->isRed = false; //新根设置为黑色
                grandFatherNode->leftChild->isRed = true; //新左叶子设置为红色
            }


            //*** 一些通用代码
            // 根已经改变了,所以要设置一些节点指向信息
            if(gff == nullptr){
                root = grandFatherNode;
            }else if(sign == 1){
                gff->leftChild = grandFatherNode;
            }else if(sign == 2){
                gff->rightChild = grandFatherNode;
            }
            break;

        }// end while(true)
        return;
    }

    // 右旋转
    void RotateRight(RBNode<T>*& pointer){ //注意参数类型
        RBNode<T>* ptmproot = pointer;
        pointer = ptmproot->leftChild;
        pointer->parentNd = ptmproot->parentNd;

        ptmproot->leftChild = pointer->rightChild;
        if(pointer->rightChild){
            pointer->rightChild->parentNd =ptmproot;
        }
        pointer->rightChild = ptmproot;
        ptmproot->parentNd = pointer;

    }


    void ReleaseNode(RBNode<T>* pnode){
        if(pnode !=nullptr){
            ReleaseNode(pnode->leftChild);
            ReleaseNode(pnode->rightChild);
        }
        delete pnode;
    }

private:
    RBNode<T>* root; 
};


int main()
{
    RBTree<int> myrbtr;
    int array[] = {80,50,120,30,60,20,40};
    int acount = sizeof(array)/sizeof(int);
    for(int i=0;i<acount;i++){
        myrbtr.InsertElem(array[i]);
    }
    return 0;
}

= =

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

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

相关文章

机器学习实验报告——Bayes算法

目录 一、算法介绍 1.1算法背景 1.2算法假设 1.3 贝叶斯与朴素贝叶斯 1.4算法原理 二、算法推导 2.1朴素贝叶斯介绍 2.2朴素贝叶斯算法推导 2.2.1先验后验概率 2.2.2条件概率公式 2.3 独立性假设 2.4 朴素贝叶斯推导 三、算法实现 3.1数据集描述 3.2代码实现 四…

python04-变量命名规则

python需要使用标识符来给变量命名。 标识符&#xff0c;我来解释下&#xff0c;就是给程序中变量、类、方法命名的符号&#xff0c;简单理解就是起一个名字&#xff0c;这个名字必须是合法的名字&#xff0c; 对于Python来说&#xff0c;标识符必须是以字母、下划线(_)开头&…

性能优化-高通的Hexagon DSP和NPU

原文来自【 Qualcomm’s Hexagon DSP, and now, NPU 】 本文主要介绍Qualcomm Hexagon DSP和NPU&#xff0c;这些为处理简单大量运算而设计的硬件。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xf…

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-2 动态规划 Dynamic Programming

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-2 动态规划 Dynamic Programming 1. 基本概念2. 代码详解3. 简单一维案例 1. 基本概念 Richoard Bell man 最优化理论&#xff1a; An optimal policy has the …

Python + Selenium —— 常用控制方法!

Selenium 体系中用来操作浏览器的 API 就是 WebDriver&#xff0c;WebDriver 针对多种语言都实现了一套 API&#xff0c;支持多种编程语言。 Selenium 通常用来做自动化测试&#xff0c;或者编写网络爬虫。 通常我们说的 Selenium 自动化操作&#xff0c;指的就是 WebDriver …

LLM:RoPE - 开源代码中的实现 (下)

本文着重学习一下开源代码中关于RoPE的实现:ChatGLM-6B、ChatGLM2-6B、LLAMA 回顾一下RoPE位置编码: 1:对于 token 序列中的每个词嵌入向量,首先计算其对应的 query 和 key 向量 2:然后对每个 token 位置都计算对应的旋转位置编码 3:接着对每个 token 位置的 query 和 …

聊聊呼声较高的向量过滤搜索及其优化

向量过滤搜索是一种基于条件的向量搜索方法&#xff0c;常用于推荐系统和信息检索等领域&#xff0c;能够帮助用户快速找到在给定条件下与其查询相关的内容。 在 Milvus 社区中&#xff0c;这也是呼声比较高的功能。为满足广大用户的需求&#xff0c;Milvus 在 Knowhere 2.x 版…

通过Stable Diffusion生成虚假的遥感影像

简介 这两天玩了一下stable diffusion&#xff0c;是真的好玩&#xff01; 然后我在想遥感有没有相关的生成模型&#xff0c;找了一下&#xff0c;还真找到了&#xff08;https://github.com/xiaoyuan1996/Stable-Diffusion-for-Remote-Sensing-Image-Generation/tree/main&a…

COT元素

论文首先定义了思维链中的两种核心元素 Bridge Object: 模型解决问题所需的核心和必须元素。例如数学问题中的数字和公式&#xff0c;QA问题中的实体&#xff0c;有点类似把论文1中pattern和symbol和在了一起&#xff0c;感觉定义更清晰了 Language Template&#xff1a;除去B…

跨平台实用软件推荐

现代工作环境下&#xff0c;每个人都需要高效管理时间、任务和信息流来提高生产力和实现目标。为了帮助您更有效地管理工作流程&#xff0c;我们为您推荐了以下几款实用的跨平台软件。 1.亿可达&#xff1a;是一款连接不同应用功能的超级软件连接器。它可以将不同的应用程序进行…

《WebKit 技术内幕》学习之九(1): JavaScript引擎

1 概述 1.1 JavaScript语言 说起JavaScript语言&#xff0c;又要讲一个典型的从弱小到壮大的奋斗史。起初&#xff0c;它只是一个非常不起眼的语言&#xff0c;用来处理非常小众的问题。所以&#xff0c;从设计之初&#xff0c;它的目标就是解决一些脚本语言的问题&#xff…

常用界面设计组件 —— 按钮组件、布局组件

2.4 按钮组件2.5 布局组件 2.4 按钮组件 QPushButton、QRadioButton 、QCheckBox都从 QAbstractButton&#xff0c;拥有一些共同的属性&#xff0c;如下图所 示&#xff1a; 图标使用setIcon()来设置&#xff0c;文本可以在构造函数或通过 setText()来设置。 可以使用 isCheck…

Spring第一天

学习目标 能够说出Spring的体系结构 能够编写IOC入门案例 能够编写DI入门案例 能够配置setter方式注入属性值 能够配置构造方式注入属性值 能够理解什么是自动装配 一、Spring简介 1 Spring课程介绍 问题导入 我们为什么要学习Spring框架&#xff1f; 1.1 为什么要学 Spri…

【数据结构】从顺序表到ArrayList类

文章目录 1.线性表1.1线性表的概念2.顺序表2.1顺序表的概念2.2顺序表的实现2.3接口的实现(对数组增删查改操作)3.ArrayList简介4. ArrayList使用 4.1ArrayList的构造4.2 ArrayList的方法4.3 ArrayList的遍历 1.线性表 1.1线性表的概念 线性表&#xff08;linear list&#xf…

VsCode容器开发 - VsCode连接远程服务器上的docker

VsCode容器开发 - VsCode连接远程服务器上的docker 前言 之前在服务器上的Docker内开发&#xff0c;文件编辑起来就很不爽。不如使用VsCode直接打开远程服务器上的Docker&#xff0c;这样就能在VsCode里直接无缝编辑Docker里的文件了。 但是百度和必应得到的中文结果都很奇葩…

【数学笔记】集合及简要逻辑

集合 基础简要逻辑集合间的关系与运算 基础 集合定义&#xff1a;把一些能够确定的不同对象组成的整体叫做一个集合&#xff0c;每个对象叫做元素。集合记法&#xff1a;一般用大写字母 A , B , C . . . . . . A,B,C...... A,B,C......表示集合&#xff0c;小写字母 a , b ,…

Python __repr__()方法:显示属性

先看下面程序&#xff1a; class Item:def __init__ (self, name, price):self.name nameself.price price # 创建一个Item对象&#xff0c;将之赋给im变量 im Item(鼠标, 29.8) # 打印im所引用的Item对象 print(im) 上面程序创建了一个 Item 对象&#xff0c;然后使用 prin…

Linux中NFS服务器的搭建和安装

1.介绍&#xff1a; 网络文件系统即将本地系统放在网络上某一个位置的系统&#xff0c;基于UDP/IP使用nfs能够在不同计算机之间通过网络进行文件共享&#xff0c;能使使用者访问网络上其他计算机中的文件就像在访问自己的计算机一样&#xff0c;也就是说放在一个开发板上&#…

8.Gateway服务网关

3.Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式…

python random.randint方法底层分析及其逆向

本文主要解释了python random模块中的randint方法的底层原理&#xff0c;并做了简单的逆向&#xff0c;能还原出所使用的随机数的部分&#xff0c;这在对random模块逆向的时候会有一些帮助。 文章目录 random模块底层原理概述randint分析逆向 random模块底层原理概述 python的…