[数据结构]红黑树的原理及其实现

文章目录

  • 红黑树的特性
  • 红黑树的时间复杂度
    • 推导:
    • 结论
    • 红黑树与AVL树比较
  • 红黑树的插入
    • 红黑树的节点定义
    • 调整策略
      • 思考情况2:
      • 思考情况3:
  • 代码实现
    • myBTRee.h
    • test.cpp

红黑树的特性

红黑树最常用的平衡二叉搜索树。跟AVL树不同的是,红黑树是依靠节点的颜色来维护平衡的。虽然任意节点不具备严格平衡,但是数据的查找、插入、删除等操作效率依旧出色。下面是红黑树的一些特性:

  1. 每个节点的颜色要么是红色要么是黑色
  2. 根节点的颜色是黑色
  3. 如果一个节点的颜色是红色的,那么它的两个孩子节点的颜色一定是黑色的
  4. 任意节点到其所能到达的叶子节点之间的黑色节点的数量相同
  5. 叶节点是黑色的空节点

根据以上红黑树的特性,我们可以总结出以下结论:

结论:红黑树中最长路径的节点数量不超过最短路径节点数量的2倍
证明:假设每条路径黑色节点的数量为n(假设不包括空叶子节点),则红色节点的数量最多是n。任意路径节点数量最少为n(只有黑节点),最多为2*n

这样一来,任意一条路径的长度之差都保证在了一个有限的范围内,这也是红黑树具有平衡性的原因。

红黑树的时间复杂度

由于红黑树底层还是一颗二叉搜索树,根据二叉搜索树的特性:查找的效率取决于树的高度

推导:

现假设红黑树的某一路径黑色节点数量为bh。由于任意路径黑色节点数目相同,我们可以把路径上所有的红色节点删除,将删除节点的父节点和其子节点相连,于是我们得到了一颗纯黑色节点的树(四叉树),如下图:
在这里插入图片描述
(该图截自b站动画讲编程)
这颗黑树的高度显然就是bh,且我们可以得到之前红黑树的节点数最少为2^bh-1(最少就是全是黑色)。假设这棵红黑树的节点总数为N,则N>=2^bh-1,两边取对数得bh<=log(N+1)(以2为底)。

设h为红黑树得最大高度,则有h=2*hb=2log(N+1)(最长路径节点数是2*bh)

结论

红黑树的高度最大为2log(N+1),N表示树的节点总数。这个证明基于红黑树的性质。
得到了节点数与树的高度的关系,我们也就能得到红黑树查找数据的效率为O(logN)。此外,红黑树的插入效率和删除效率取决于查找效率,也是O(logN)。

红黑树与AVL树比较

相对来说,红黑树的内部实现细节没有AVL树这么复杂,虽然AVL树保证具备严格平衡性,但是其插入元素时调整平衡的操作相对也就繁琐。插入和删除的时候,AVL树可能会进行更多的旋转操作来维持平衡,导致实际的执行时间可能高于红黑树

总的来说,红黑树适用于读写均衡的场景,插入和删除操作较为高效。而AVL树适用于查询频繁的场景,查询效率更高,但是插入和删除的维护成本较高。即使是这样,两者的差别依旧不大,查找、删除、插入的时间复杂度都是OlogN。

红黑树的插入

上面我们已经了解了红黑树的特性,接下来谈谈红黑树插入数据时是如何维护上述特性的。

红黑树的节点定义

与AVL实现类似,红黑树的节点依旧有三个指针分别指向父节点、左孩子节点、右孩子节点。且有一个变量表示当前节点的颜色。初始默认是红色。为什么默认设置为红色呢?这是因为插入节点时,新节点一定与空叶子节点相邻。而根据红黑树的性质,空叶子节点的颜色是黑色,这就使得,如果新插入节点是黑色,那么每插入一次这条路径就一定会多出来一个黑色节点,即一定要调整。虽然新节点是红色也可能会需要调整,但影响会少很多。

template<class K,class V>
struct RBTreeNode {RBTreeNode<T, V>* _parent;RBTreeNode<T, V>* _left;RBTreeNode<T, V>* _right;pair<K, V> _kv;//键值对Colour _col;//颜色RBTreeNode(const pair<K, V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_col(RED){}
};

调整策略

如果新插入一个节点,破坏了红黑树的性质,那么我们需要进行调整。具体需要调整的情况如下:

  1. 如果插入节点是根节点:那么只需要将根节点变黑
  2. 插入节点的叔叔节点是红色:父亲节点和叔叔节点变成黑色,爷爷变成红色,且爷爷节点变成新插入节点
  3. 插入节点的叔叔节点是黑色(为空也是黑色):需要先旋转,然后变色

上述2,3情况一定是出现两个相邻节点是红色。

思考情况2:

为什么我们要把父亲节点和叔叔节点变成黑色,爷爷变成红色,且爷爷节点变成新插入节点?为什么这样做一定能维护红黑树的性质呢?
在这里插入图片描述

  • 首先,把父亲节点变黑是因为相邻两个节点是红色,那为什么要把叔叔节点也变色呢
    这是因为新节点插入的这条路径可能会多一个黑色节点。为了让所有路径黑色节点数都能加一,每次调节父节点颜色的同时,也要调节叔叔节点颜色。这样一来,即使最后新节点插入的这条路径黑色节点数加一,其它所有路径的黑色节点也能同步加一。

  • 其实将爷爷节点变色也是为了维护所有路径黑色节点数一致这一特性
    设想我们不将爷爷节点变成红色,此时整个红黑树确实没有出现“相邻的红色节点”,但是经过爷爷节点的路径的黑色节点数目就会比其它路径黑色节点数目多(parent和uncle都是变黑了)。除非此时爷爷节点是根节点(所有路径都经过根节点)。所以我们选择继续向上调整,把爷爷节点变成红色,并更新cur指针指向grandparent节点,看是否还会出现“红红”的俩节点。把爷爷节点变红是基于贪心思路:先不让路径的黑色节点数加一试试能不能平衡

在这里插入图片描述

什么时候停止调整呢?父节点的颜色是黑色为止。此时红黑树平衡

思考情况3:

如果叔叔节点本来就是黑色的呢?(空节点也是黑色)此时无论如何调节叔叔节点颜色,都有可能使得其它路径(比如叔叔这条路径)黑色节点数少于当前新节点插入路径的黑色节点数。此时考虑旋转。旋转的方式和AVL树是一致的。
旋转一共有四种方式:左旋、右旋、左右双旋、右左双旋。
细分情况3,一共有以下四种情况:
1.curparent的左孩子,parentgrandparent的左孩子。
在这里插入图片描述
将grandparent节点右旋转,旋转之后parent顶替了原来的grandparent,并变成黑色。grandparent成为parent的右孩子,变成红色
在这里插入图片描述
旋转是不会改变二叉搜索树的特性的。那么思考这样一个问题,旋转之后还是平衡的红黑树吗?答案是–是的。因为我们观察旋转之后经过parent的所有路径的黑色节点数压根就没有变。

2.curparent的右孩子,parentgrandparent的右孩子
这种情况和情况1一样,只不过方向相反,是左旋。不做过多徐述。
在这里插入图片描述
3.curparent孩子,parentgrandparent孩子

此时这种情况对grandparent节点进行左单旋还是右单旋都不能保证红黑树平衡,于是考虑双旋,即旋转两次。
在这里插入图片描述
先对parent节点左旋,发现左旋之后就变成了情况1
在这里插入图片描述
此时我们就可以像情况1一样,对grandparent进行右旋,对cur节点和grandparent节点进行变色
在这里插入图片描述
这样红黑树就平衡了,整个过程并没有增加路径的黑色节点的数量,因此也就不需要继续向上调整。

  1. curparent孩子,parentgrandparent孩子
    思路跟情况3一致,只不过旋转方向相反。向对parent节点进行右旋,再对grandparent节点进行左旋,再对cur和grandparent节点进行变色。

在这里插入图片描述

代码实现

myBTRee.h

封装了红黑树的类模板,包括各种功能的声明于定义

#pragma once#include<vector>
#include<iostream>
using namespace std;enum Colour{RED,BLACK
};template<class K,class V>
struct RBTreeNode {RBTreeNode<K, V>* _parent;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;pair<K, V> _kv;//键值对Colour _col;//颜色RBTreeNode(const pair<K, V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_col(RED){}
};template<class K,class V>
class RBTree {typedef RBTreeNode<K, V> Node;typedef Node* pNode;
public:bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_col = BLACK;return true;}//找到一个合适的插入位置pNode cur = _root;pNode parent = nullptr;while (cur) {if (cur->_kv.first > kv.first) {parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first) {parent = cur;cur = cur->_right;}else {return false;}}//此时cur为nullptrcur = new Node(kv);if (cur->_kv.first < parent->_kv.first) {//插入节点parent->_left = cur;cur->_parent = parent;}else {parent->_right = cur;cur->_parent = parent;}//插入之后需要调节颜色平衡while (parent && parent->_col == RED) {//找叔叔节点pNode grandparent = parent->_parent;//祖父节点一定不为空,因为父节点为红色if (parent == grandparent->_left) {//如果叔叔在右边pNode uncle = grandparent->_right;//叔叔存在且为红,直接都变成黑色if (uncle && uncle->_col == RED) {parent->_col = BLACK;uncle->_col = BLACK;grandparent->_col = RED;//继续往上处理cur = grandparent;//cur往上跳两个节点parent = cur->_parent;}//叔叔节点不存在或者为黑色else {if (cur == parent->_left) {RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else if (cur == parent->_right) {RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}//如果叔叔在左边if (parent == grandparent->_right) {pNode uncle = grandparent->_left;//叔叔存在且为红,直接都变成黑色if (uncle && uncle->_col == RED) {parent->_col = BLACK;uncle->_col = BLACK;//继续往上处理grandparent->_col = RED;cur = grandparent;//cur往上跳两个节点parent = cur->_parent;}//叔叔节点不存在或者为黑色else {if (cur == parent->_left) {RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col= RED;}else if (cur == parent->_right) {RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateR(Node* parent)//右旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent)//左旋{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent= nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}bool IsBalance() {if (_root->_col == RED)return false;int targetnum = 0;pNode cur = _root;while (cur) {if (cur->_col == BLACK)targetnum++;cur = cur->_left;}return _Check(_root, targetnum, 0);}void InOrder() {_InOrder(_root);}
private:bool _Check(pNode root,int targetnum,int blacknum) {if (root == nullptr) {if (blacknum != targetnum) {//路径的黑色节点数不相等return false;}else return true;}if (root->_left && root->_col == RED && root->_left->_col == RED)return false;if (root->_right && root->_col == RED && root->_right->_col == RED)return false;if (root->_col == BLACK)blacknum++;return _Check(root->_left,targetnum,blacknum) && _Check(root->_right,targetnum,blacknum);}void _InOrder(pNode root) {if (root == nullptr) {return;}_InOrder(root->_left);cout << root->_kv.first << " " << root->_kv.second << endl;_InOrder(root->_right);}pNode _root=nullptr;};

test.cpp

用于测试代码的正确性。给出一组随机数,插入到红黑树中之后进行平衡检查。平衡检查内容为,检查是否出现连个相邻的红色节点,且所有路径的黑节点·数目是否相等。
代码:

void TestRBTree2()
{const int N = 100000;vector<int> v;v.reserve(N);srand((unsigned int)time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);//cout << v.back() << endl;}size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << t.IsBalance() << endl;
}

在这里插入图片描述

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

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

相关文章

Chirpstack配合网关与lora设备通信

之前的章节讲过chirpstack的下载和安装部署&#xff0c;这节算是后续&#xff0c;利用chirpstack和lora设备做通信&#xff0c; 首先开启chirpstack&#xff0c;并登录&#xff0c;登录完成之后需要添加网关和设备&#xff0c;添加网关也就是Gatway&#xff0c;所以点开左侧的G…

「51媒体」北京科技类媒体有哪些?媒体邀约宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 北京作为中国的科技创新中心&#xff0c;拥有众多的科技类媒体&#xff0c;这些媒体在科技新闻报道、技术趋势分析、企业产品展示等方面发挥着重要作用。以下是一些北京地区的科技类媒体…

DOS学习-目录与文件应用操作经典案例-dir

欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.练习 一.前言 dir是"directory"&#xff08;目录&#xff09;的缩写&#xff0c;它主要用于展示某个磁盘上的全部或特定文件目录。在DOS操作系统中&#…

如何看待Agent的爆火

在2023年3月&#xff0c;一个名为AutoGPT的框架项目引发了一场AI Agent热潮。这个项目利用大型语言模型&#xff0c;将大任务拆分成小任务&#xff0c;并使用工具完成它们。这种技术将大语言模型处理语言、创造内容和逻辑推理的能力扩展到了应用场景中&#xff0c;还加入了感知…

家政系统源码部署/售后更新/搭建/上线维护

基于FastAdmin和原生微信小程序开发的一款同城预约、上门服务、到店核销家政系统&#xff0c;用户端、服务端(高级授权)、门店端(高级授权)各端相互依赖又相互独立&#xff0c;支持选择项目、选择服务人员、选择门店多种下单方式&#xff0c;支持上门服务和到店核销两种服务方式…

树莓派3B+入门(无外设)

昨日刚到一块树莓派3B&#xff0c;甚是喜爱&#xff0c;然半宿未眠 1、下载 在官网先下载烧录文件https://www.raspberrypi.com/software/ 下载完毕打开&#xff0c;选择&#xff0c;根据自己板子型号定 操作系统用最新的就行&#xff0c;64位不太稳定 储存卡&#xff0c;需…

YOLOV8环境部署(GPU版本)

一、安装&#xff43;&#xff55;&#xff44;&#xff41;和&#xff43;&#xff55;&#xff44;&#xff4e;&#xff4e; 1、安装cuda之前先打开英伟达控制面板查看自己的显卡信息 2、“帮助”—>“系统信息”—>“组件”&#xff0c;然后看第三行的信息“Nvidia …

docker镜像容器常用命令

常用基础命令1、docker info #查看docker版本等信息 2、docker search jenkins #搜索jenkins镜像 3、docker history nginx #查看镜像中各层内容及大小,每层对应的dockerfile中的一条指令。 4、docker network ls #显示当前主机上的所有网络 5、docker logs nginx …

PPT为何无法复制粘贴?附解决办法!

PPT文件里的内容无法复制&#xff0c;或者复制后无法粘贴&#xff0c;这是怎么回事呢&#xff1f; 这种情况&#xff0c;一般是因为PPT被设置了保护&#xff0c;设置了以“只读方式”打开&#xff0c;就无法进行复制粘贴了。PPT的“只读方式”不同&#xff0c;解决方法也不同&…

AI视频教程下载:用ChatGPT快速精通Python编程

这个课程是为想要通过ChatGPT提升Python编程技能的个人设计的。 **你将会学到的&#xff1a;** - Python编程语言的基础知识。 - ChatGPT的介绍和多种用例。 - 如何使用ChatGPT加快你的编码项目的开发。 - 了解新技术&#xff0c;如数据科学和机器学习&#xff0c;并进行实…

el-upload 上传多个视频

<el-form-item label="视频" prop="video_url"><el-uploadclass="upload-demo"ref="uploadRef":multiple="true":on-change="handleChange":before-remove="beforeRemove":before-upload=&quo…

Facebook广告运营黑五类怎么投?

哈喽呀&#xff0c;很多小伙伴不知道黑五具体是哪些今天就跟大家来说说&#xff0c;黑五类是指一些擦边的受到限制的产品&#xff0c;指的是药品、医疗器械、丰胸、减肥、增高这五类产品。 黑五类产品可以在哪些平台进行投放&#xff1a; 目前黑五类可以广告投放的跨境电商平台…

Visual Studio Code 扩展程序Text Edits

需求 比如把Scarzombie_Monster全部转换为大写或者小写 安装 Text Edits 直接搜索安装即可 使用 假如要把Scarzombie_Monster全部转为大写&#xff0c;选中右键选中 To Upper Case或者直接快捷键shiftAltU即可

WordPress建站公司模板免费下载

WordPress建站公司 适合提供WordPress建站服务的公司或个体(个人)工作室使用的WordPress建站公司主题模板。 演示 https://www.jianzhanpress.com/?p545 https://www.wpicu.com/jianzhan/ 下载 链接: https://pan.baidu.com/s/11trlwUJq_lW81R_acq4ilA 提取码: r19i

从零入门激光SLAM(十五)——IMU在SLAM中的用处

从这节开始&#xff0c;进入到LIO章节&#xff0c;LIO具有更高的鲁棒性、精度、实时性、环境适应性和成本效益&#xff0c;快来学习一下吧 一、IMU能干什么 惯性测量单元(Inertial measurement unit&#xff0c;IMU)&#xff0c;是测量物体三轴姿态角以及加速度的装置。IMU通…

DDNS配置详解

正文共&#xff1a;1111 字 8 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面配置了DDNS&#xff08;拨号有公网IP地址了&#xff0c;肯定要通过DDNS用起来啊&#xff01;&#xff09;&#xff0c;有不少小伙伴咨询具体的配置问题。为了方便大家深入理解DDNS的技术原理&am…

IDEA 每次启动都显示选择项目页面

IDEA版本&#xff1a;2021.3.3 打开 Settings > Appearance & Behavior > System Settings 取消勾选 Reopen projects on startup 然后下次启动 IDEA 会显示选择项目页面

记录计全支付切换到RabbitMQ时启动报错的问题

记录计全支付切换到RabbitMQ时启动报错的问题 首先在application.yml中切换到RabbitMQ配置安装RabbitMQ、Erlang、延时插件 rabbitmq_delayed_message_exchange&#xff0c;延迟插件必装 首先在application.yml中切换到RabbitMQ配置 # 第一处rabbitmq:addresses: 127.0.0.1:56…

减肥健身个人总结

个人一直没有健身运动的习惯&#xff0c;工作久了体重超标&#xff0c;体检报告各种指标也不太“美丽”&#xff0c;开始学习一些减肥健身知识&#xff0c;持续更新。目标是每周减1~2斤&#xff0c;用几个月时间持续到体重恢复正常。 文章目录 一、减脂原理---制造热量缺口控制…

停车场车位引导管理系统工作原理是什么,由哪些软硬件设备组成?

在现代城市中&#xff0c;随着汽车保有量的持续增长&#xff0c;停车难成为了许多城市面临的共同问题。有效管理停车场资源&#xff0c;提高车位利用率&#xff0c;减少寻找停车位的时间&#xff0c;对于缓解交通拥堵、提高城市运行效率具有重要意义。车位引导管理系统正是为了…