红黑树——插入底层实现【C++】面试重灾区!!

目录

前言

一,概念

定义 

二,insert

情况一:

情况二:

情况三:

insert代码

三, 红黑树验证(面试题)

产生随机数验证


每日一图区:

前言

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

 红黑树相对于AVL树的优势包括:

  1. 插入和删除操作更快:红黑树相对于AVL树的平衡条件更加宽松,因此在插入和删除节点时需要进行的旋转操作更少。这使得红黑树的插入和删除操作更快。

  2. 更好的平衡性能:红黑树的平衡性能比AVL树稍差,但是在实际应用中,红黑树的平衡性能已经足够好了。红黑树的插入和删除操作相对较快,这在某些场景下更重要。

  3. 更少的旋转操作:红黑树的旋转操作比AVL树少。旋转操作是一种比较耗时的操作,因此红黑树的插入和删除操作相对更快。

  4. 更好的空间效率:红黑树相对于AVL树需要更少的额外空间来存储平衡因子或颜色信息。这使得红黑树的空间效率更高。

  5. 更广泛的应用:红黑树相对于AVL树应用更广泛。红黑树在很多语言的标准库中都有实现,而AVL树的应用相对较少。

需要注意的是,红黑树和AVL树都是平衡二叉搜索树,选择使用哪种树结构取决于具体的应用场景和需求。

一,概念

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

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

其中,NIL表示一个路径的出口。

定义 

enum color{RED, BLACK};template <class data_type>
struct RBT_Data
{data_type _kv;RBT_Data<data_type>* left = nullptr;RBT_Data<data_type>* right = nullptr;RBT_Data<data_type>* parent = nullptr;color _col;  // 颜色RBT_Data(const data_type& p):_kv(p),_col(RED) // 颜色默认红{}
};template <class data_type>
class RB_Tree
{typedef RBT_Data<data_type> RBT_Data;RBT_Data* root;
};

关于,创建新节点该怎么选颜色。

  • 如果我们选择黑色,那么我们一定会违反性质四(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 ),我们为了保持红黑树的结构,就需要调整其他所有的路径。
  • 如果我们选择红色,我们可能会违法性质三(如果一个节点是红色的,则它的两个孩子结点是黑色的 ),而且影响的只是局部,不会影响其他的兄弟树。

因此我们会选择红色作为默认颜色

在AVL树中我们关注树之间的高度,而到了红黑树我们需要关注节点之间的颜色

二,insert

 插入新节点,我们需要检测新节点是否破坏红黑树的性质。

因为 新节点的默认颜色是红色,因此:如果 其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;
当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

情况一:

特点:uncle存在且为红,parent为红色,cur也为红

 用一个模板图总结该情况:

情况二:

特征:没有uncle, parent为红, cur为红

所以在情况二下,比较重要的就是旋转方法+变色,旋转如果有忘记了,可以参考本篇文章:

保姆级认识AVL树【C++】(精讲:AVL Insert)-CSDN博客

情况三:

特征:没有uncle或者uncle是黑色,parent为红,cur为红。相比较于情况二,情况三的旋转方法是双旋。

这里有一个区分情况二与情况三的小技巧,那就是看grandfather , parent, cur 三节点的线路。如果是直线,则情况二; 折线则情况三

insert代码

bool insert(const pair<K, V>& p){RBT_Data* new_a_d = new RBT_Data(p);if (!root){root = new_a_d;root->_col = BLACK;	}else{RBT_Data* cur = root;RBT_Data* parent = nullptr;while (cur){if (p.first < cur->_kv.first){parent = cur;cur = cur->left;}else if (p.first > cur->_kv.first){parent = cur;cur = cur->right;}else{delete(new_a_d); // 插入失败,删除新建结点return false;}}if (p.first < parent->_kv.first){parent->left = new_a_d;}else{parent->right = new_a_d;}new_a_d->parent = parent;// 调整颜色cur = new_a_d;RBT_Data* par = cur->parent;if (cur == root){cur->_col = BLACK;}while (par && par->_col == RED){RBT_Data* gf = par->parent;RBT_Data* uncle = nullptr;if (gf && par == gf->right){uncle = gf->left;}else if (gf && par == gf->left){uncle = gf->right;}else{assert(false);}if ( uncle && uncle->_col == RED)// 有u且为红{gf->_col = RED;uncle->_col = BLACK;par->_col = BLACK;cur = gf;  // 切换为祖先,进入循环向上par = cur->parent;}else if (!uncle ||(uncle && uncle->_col == BLACK)){   // 情况2 + 3,判断,是否是折线还是直线if (gf->left == par && par->left == cur){  // 右单选RotateR(gf);}else if (gf->right == par && par->right == cur){  // 左单旋RotateL(gf);}else if (gf->left == par && par->right == cur){  // 需要左双旋RotateLR(gf);}else if (gf->right == par && par->left == cur){  // 需要右双旋RotateRL(gf);}else{assert(false);}break;}else{assert(false);}}if ( root->_col == RED){root->_col = BLACK;}return true;}
}

左,右和双旋实现代码跟AVL章节中大差不差,这里给出左单旋的实现,大家照葫芦画瓢一下:

void RotateL(RBT_Data* parent){assert(parent->right);RBT_Data* par = parent;RBT_Data* par_R = par->right;RBT_Data* par_RL = par->right->left;RBT_Data* ppnode = par->parent;par->right = par_RL;if (par_RL)par_RL->parent = par;par_R->left = par;par->parent = par_R;par_R->parent = ppnode;if (!ppnode){root = par_R;}else if (ppnode->left == par){ppnode->left = par_R;}else{ppnode->right = par_R;}par->_col = RED;par_R->_col = BLACK;}

三, 红黑树验证(面试题)

验证红黑树性质

目标:

1. 根是否是黑

2. 没有连续红节点

3. 每条路径所经历的黑节点相同。 

代码:

public:
bool IsBalance(){if (root && root->_col == RED){return false;}int BlackNum = 0; // 所经黑节点的次数int standard = 0;  //设置一个最长路径RBT_Data* cur = root;while (cur){if (cur->_col == BLACK)standard++;cur = cur->left;}return _IsBalance(root->left, BlackNum, standard) && _IsBalance(root->right, BlackNum, standard);}private:
bool _IsBalance(const RBT_Data* cur, int BlackNum, int standard){if (cur == nullptr){return true;}if (cur->_col == BLACK)BlackNum++;if (cur->_col == RED && cur->_col == cur->parent->_col){return false;}return  _IsBalance(cur->left, BlackNum, standard) && _IsBalance(cur->right, BlackNum, standard);}

产生随机数验证

void Random_Test()
{srand(time(0));const size_t N = 10000000;RB_Tree<int, int> t;for (size_t i = 0; i < N; i++){size_t x = rand();t.insert(make_pair(x, x));}cout << t.IsBalance() << endl;
}

四,删除

红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》 http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

下期预告:用红黑树封装map与 set

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

shell script中的数值运算declare和$((运算式 ))

linux中变量定义默认是字符串类型&#xff0c;如要进行数值运算&#xff0c;需要先声明变量类型&#xff0c;或者通过固定格式来计算 看案例 如果不通过固定格式&#xff0c;直接 echo 55 如图&#xff0c;结果显示的55本身 可以写成 declare -i var#声明变量integrate类型&…

计算机视觉 计算机视觉识别是什么?

计算机视觉识别&#xff08;Computer Vision Recognition&#xff09;是计算机科学和人工智能领域中的一个重要分支&#xff0c;它致力于使计算机系统能够模拟和理解人类视觉的过程&#xff0c;从而能够自动识别、分析和理解图像或视频中的内容。这一领域的发展旨在让计算机具备…

SQL server数据库定时搜索

问题现象 出现下图问题&#xff0c;导致连接该数据库的程序不能正常启动 解决办法 定时搜索数据库 脚本 # 设置数据库名称 # 设置定时任务时间# 生成定时任务执行sql文件 sql语句 USE [master] GO ALTER DATABASE volador SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DA…

软件测试:压力测试详解

压力测试 压力测试是一种软件测试&#xff0c;用于验证软件应用程序的稳定性和可靠性。压力测试的目标是在极其沉重的负载条件下测量软件的健壮性和错误处理能力&#xff0c;并确保软件在危急情况下不会崩溃。它甚至可以测试超出正常工作点的测试&#xff0c;并评估软件在极端…

界面控件DevExpress WPF Gauge组件 - 轻松实现个性化商业仪表盘

DevExpress WPF Gauge&#xff08;仪表&#xff09;控件包含了多种圆形仪表类型、水平和垂直线性仪表、分段和矩阵数字仪表以及状态指示器&#xff0c;同时还具有最终用户交互性的集成支持。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至…

自适应H5樱花个人网站引导页导航源码SEO增强版

懂前端和 PHP 技术想更改前端页面的可以看&#xff1a;网站的前端页面不好看&#xff0c;你可以查看 index 目录&#xff0c;内有参数 txt 文本里面记录了前端页面所使的关键 JS 代码和 php 代码&#xff0c;只需要稍加复制粘贴修改到新的前端页面就可以了&#xff01;主目录的…

驱动开发11-1 编写IIC驱动-读取温湿度数据

头文件 head.h #ifndef __HEAD_H__ #define __HEAD_H__ #define GET_HUM _IOR(m, 1, int) #define GET_TEM _IOR(m, 0, int) #endif 应用程序 si7006.c #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #inc…

什么是Webpack的loader和plugin?它们的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别 计算机竞赛

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

LeetCode字符串题库 之 罗马数字转整数

题目链接&#x1f517;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 1. 题目分析 我们在做题的时候&#xff0c;一定要知道题目的目的是什么&#xff0c;我们可以结合测试用例和提示来看。 我们可以分析以下几点&#xff1a; 1. 每一个罗马数字都…

物联网系统的基本构件

1.基本组件 云服务器 数据库消息服务器应用服务器管理平台 云APP 云服务器的维护终端微信客户端网页管理平台 页面式的更全面的管理。组态软件和PLC软件 编程软件终端设备 PLC 主要指标&#xff0c;模拟数字接口数量 DO有 继电器和1.5,2.5.5V数字输出一般支持扩展IO模块模拟量…

目标检测:Proposal-Contrastive Pretraining for Object Detection from Fewer Data

论文作者&#xff1a;Quentin Bouniot,Romaric Audigier,Anglique Loesch,Amaury Habrard 作者单位&#xff1a;Universit Paris-Saclay; Universit Jean Monnet Saint-Etienne; Universitaire de France (IUF) 论文链接&#xff1a;http://arxiv.org/abs/2310.16835v1 内容…

视频列表:点击某个视频进行播放,其余视频全部暂停(同时只播放一个视频)

目录 需求实现原理实现代码页面展示 需求 视频列表&#xff1a;点击某个视频进行播放&#xff0c;其余视频全部暂停&#xff08;同时只播放一个视频&#xff09; 实现原理 在 video 标签添加 自定义属性 id (必须唯一)给每个 video 标签 添加 play 视频播放事件播放视频时&…

Ha-NeRF源码解读 train_mask_grid_sample

目录 背景&#xff1a; &#xff08;1&#xff09;Ha_NeRF论文解读 &#xff08;2&#xff09;Ha_NeRF源码复现 &#xff08;3&#xff09;train_mask_grid_sample.py 运行 train_mask_grid_sample.py解读 1 NeRFSystem 模块 2 forward()详解 3 模型训练tranining_st…

JavaScript设计模式之发布-订阅模式

发布者和订阅者完全解耦&#xff08;通过消息队列进行通信&#xff09; 适用场景&#xff1a;功能模块间进行通信&#xff0c;如Vue的事件总线。 ES6实现方式&#xff1a; class eventManager {constructor() {this.eventList {};}on(eventName, callback) {if (this.eventL…

[common c/c++] ring buffer/circular buffer 环形队列/环形缓冲区

前言&#xff1a; ring buffer / circular buffer 又名环形队列 / 环形缓冲区&#xff0c;其通过开辟固定尺寸的内存来实现反复复用同一块内存的目的。由于预先开辟了固定尺寸的内容&#xff0c;所以当数据满的时候&#xff0c;可以有两种处理方式&#xff0c;具体使用哪一种按…

HCL模拟器综合实验案例(2)

本案例提供给计算机网络专业学生以及参加新华三杯的同学进行学习 题目 由于公司并购前的历史原因导致双方使用不同的 OSPF 进程&#xff0c;经由总部技术部统一规划后再做调整&#xff0c;现阶段使用暂时过渡方案&#xff0c;即使用静态路由、OSPF、RIP、BGP多协议组网&#x…

协同办公系统:企业提质增效的利器

随着科技的不断发展&#xff0c;企业对于提高工作效率、优化管理流程、降低成本的需求日益迫切。协同办公系统应运而生&#xff0c;成为了许多企业提质增效的利器。那么&#xff0c;协同办公系统究竟是如何帮助企业实现这些目标的呢&#xff1f;本文将从以下几个方面进行详细阐…

微信小程序快速备案的通道揭秘方法

随着国家政策的调整&#xff0c;微信小程序备案已变得刻不容缓。传统备案路径较为繁琐&#xff0c;耗时较长&#xff0c;为解决此痛点&#xff0c;今天我们将揭示一个快速备案的新通道。 步骤1&#xff1a;探索智慧助手 打开微信&#xff0c;探索“智慧商家服务助手”公众号。…

NProgress顶部进度条的用法

大家打开一个网页的时候&#xff0c;会看到一个进度条&#xff0c;然后加载完成后进度条就消失了。这个呢&#xff0c;就是一个第三方的进度条库&#xff0c;叫做nprogress. 1.首先安装nprogress(咱直接用npm安装了) : npm install --save nprogress 2.然后在 router/index.j…