C++进阶之路---手撕“红黑树”

顾得泉:个人主页

个人专栏:《Linux操作系统》 《C++从入门到精通》  《LeedCode刷题》

键盘敲烂,年薪百万!


一、红黑树的概念与性质

1.概念

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

2.性质

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


二、红黑树结构

       为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:


三、红黑树的相关实现

1. 红黑树节点的定义

       想要实现一颗红黑树 ,首先我们得有树的节点,而树的节点中我们需要存:该节点的父节点、该节点的右孩子、该节点的左孩子、树节点的颜色以及数据类型;代码如下:

enum COLOUR
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _parent;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;COLOUR _col;pair<K, V> _kv;RBTreeNode(const pair<K, V>& kv):_parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _col(RED){}
};

       这里在节点的定义中,要将节点的默认颜色给成红色,这个需仔细品味。

2. 红黑树的定义

红黑树的定义如下:

template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;private:Node* _root = nullptr;
};

3. 红黑树的插入

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

       约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一

       cur为红,p为红,g为黑,u存在且为红

       解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

情况二

       cur为红,p为红,g为黑,u不存在/u存在且为黑

解决方式:

       p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
       p为g的右孩子,cur为p的右孩子,则进行左单旋转
       p、g变色–p变黑,g变红

情况三

       cur为红,p为红,g为黑,u不存在/u存在且为黑

解决方式:

       p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
       p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
       则转换成了情况2

4.代码实现

template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}// 找到插入位置Node* cur = _root, * 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 = new Node(kv);if (parent->_kv.first > cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//     g//   p   u// cif (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1、uncle 存在且为红// 不需要旋转if (uncle && uncle->_col == RED){// 变色	parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上更新处理cur = grandfather;parent = cur->_parent;}else{// 情况2.1// 单旋//     g//   p// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// 情况3.1// 双旋//     g//   p//     celse{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}// grandfather->_right == parent//     g//   u   p//         celse{Node* uncle = grandfather->_left;// uncle 存在且为红// 不需要旋转if (uncle && uncle->_col == RED){// 变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}// uncle 存在且为黑 或者 uncle 不存在else{// 情况2.2// 单旋//     g//   u   p //         cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// 情况3.2// 双旋//     g//   u   p //     celse{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 最后保证根节点是黑色的_root->_col = BLACK;return true;}// 判断中序遍历是否为有序序列void Inorder(){_Inorder(_root);cout << endl;}// 判断是否平衡bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;// 先统计一条路径的黑色节点,与其它路径的比较int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){refVal++;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}// 获取树的高度int Height(){return _Height(_root);}// 获取树的节点数size_t Size(){return _Size(_root);}	// 查找Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return NULL;}
private:// 获取树的节点个数size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}// 获取树的高度int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}// 检查是否符合红黑树规则bool Check(Node* root, int blacknum, int refVal){if (root == nullptr){if (blacknum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){blacknum++;}return Check(root->_left, blacknum, refVal)&& Check(root->_right, blacknum, refVal);}// 按中序遍历打印树的节点void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}// 左单旋void RotateL(Node* parent){Node* subR = parent->_right, * subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;// 如果 parent 是根节点,就直接更新 subR 为根节点,并将 subR 的_parent指向空if (_root == parent){_root = subR;subR->_parent = nullptr;}// 否则,先判断 parent 是 parentParent 的右还是左,再将parentParent的左或者右连接subRelse{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}// 右单旋void RotateR(Node* parent){Node* subL = parent->_left, * subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* parentParent = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}
private:Node* _root = nullptr;
};

四、红黑树与AVL树的比较     

       红黑树和AVL树都是自平衡二叉查找树,它们都能在插入和删除操作后通过旋转来维持树的平衡,保证查找、插入和删除操作的时间复杂度大致为( O(\log n) )。然而,它们在实现方式和具体性能上存在一些差异:

平衡性:

       红黑树通过确保从根节点到叶子的最长可能路径不会超过最短可能路径的两倍来保持平衡,即它允许一定程度的不平衡。

       AVL树则更加严格,它要求任何从根节点到叶子的路径的长度最多只相差1,因此它比红黑树更接近于完全平衡。

旋转操作:

       红黑树通常需要进行更多的旋转以维护其性质(每次插入或删除后都可能需要进行多达三次旋转),但它不需要维护额外的平衡因子信息。

       AVL树在每个节点上存储高度信息(或平衡因子),这使得它能够进行更精细的平衡操作,通常在一次插入或删除后只进行常数次旋转

空间开销:

       红黑树不需要存储额外的高度或平衡因子信息,因此它的空间效率略高。

       AVL树的每个节点都需要存储高度信息,这增加了少量的内存开销。

应用场景:

       红黑树由于其简单性和对平衡的宽松要求,在实际应用中非常流行,特别是在实现标准库中的关联容器如map和set等。

       AVL树通常在需要频繁进行查找操作,而插入和删除操作相对较少的情况下使用,因为它的高度总是最小化的,这可以最大化查找效率。


结语:C++关于如何实现红黑树的分享到这里就结束了,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言~~~ 

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

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

相关文章

matlab矩形薄板小挠度弯曲有限元编程 |【Matlab源码+理论文本】|板单元| Kirchoff薄板 | 板壳单元

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

后端工程师快速使用axios

文章目录 01.AJAX 概念和 axios 使用模板目标讲解代码解析案例前端后端结果截图 02.URL 查询参数模板目标讲解案例前端后端结果截图 03.常用请求方法和数据提交模板目标讲解案例前端后端结果截图 04.axios 错误处理模板目标讲解案例前端后端结果截图 01.AJAX 概念和 axios 使用…

C语言数据结构与算法笔记(排序算法)

排序算法 基础排序 冒泡排序 核心为交换&#xff0c;通过不断进行交换&#xff0c;将大的元素一点一点往后移&#xff0c;每一轮最大的元素排到对应的位置上&#xff0c;形成有序。 设数组长度为N&#xff0c;过程为: 共进行N轮排序每一轮排序从数组的最左边开始&#xff0…

C++中的作用域解析运算符

1. 访问命名空间成员 当我们需要访问某个命名空间中的变量、函数或类型时&#xff0c;可以使用::来指定明确的作用域。 namespace myNamespace {int value 42; }int value 24;int main() {// 使用作用域解析运算符访问命名空间中的变量std::cout << myNamespace::val…

分治法排序:原理与C语言实现

分治法排序&#xff1a;原理与C语言实现 一、分治法与归并排序概述二、归并排序的C语言实现三、归并排序的性能分析四、归并排序的优化 在计算机科学中&#xff0c;分治法是一种解决问题的策略&#xff0c;它将一个难以直接解决的大问题&#xff0c;分割成一些规模较小的相同问…

确保云原生部署中的网络安全

数字环境正在以惊人的速度发展&#xff0c;组织正在迅速采用云原生部署和现代化使用微服务和容器构建的应用程序&#xff08;通常运行在 Kubernetes 等平台上&#xff09;&#xff0c;以推动增长。 无论我们谈论可扩展性、效率还是灵活性&#xff0c;对于努力提供无与伦比的用…

算法——贪心算法

《算法图解》——贪心算法 # 首先创建一个表&#xff0c;包含所覆盖的州 states_needed set([mt,wa,or,id,nv,ut,az]) # 传入一个数组&#xff0c;转换成一个集合#可供选择的广播台清单 stations {} stations[kone] set([id,nv,ut]) #用集合表示想要覆盖的州&#xff0c;且不…

【K8S】docker和K8S(kubernetes)理解?docker是什么?K8S架构、Master节点 Node节点 K8S架构图

docker和K8S理解 一、docker的问世虚拟机是什么&#xff1f;Docker的问世&#xff1f;docker优点及理解 二、Kubernetes-K8SK8S是什么&#xff1f;简单了解K8S架构Master节点Node节点K8S架构图 一、docker的问世 在LXC(Linux container)Linux容器虚拟技术出现之前&#xff0c;业…

C语言黑魔法第三弹——动态内存管理

本文由于排版问题&#xff0c;可能稍显枯燥&#xff0c;但里面知识点非常详细&#xff0c;建议耐心阅读&#xff0c;帮助你更好的理解动态内存管理这一C语言大杀器 进阶C语言中有三个知识点尤为重要&#xff1a;指针、结构体、动态内存管理&#xff0c;这三个知识点决定了我们…

利用textarea和white-space实现最简单的文章编辑器 支持缩进和换行

当你遇到一个非常基础的文章发布和展示的需求&#xff0c;只需要保留换行和空格缩进&#xff0c;你是否会犹豫要使用富文本编辑器&#xff1f;实际上这个用原生的标签两步就能搞定&#xff01; 1.直接用textarea当编辑器 textarea本身就可以保存空格和换行符&#xff0c;示例如…

DockerFile遇到的坑

CMD 命令的坑 dockerfile 中的 CMD 命令在docker run -it 不会执行 CMD 命令。 FROM golang WORKDIR / COPY . ./All-in-one CMD ["/bin/sh","-c","touch /kkk.txt && ls -la"] RUN echo alias ll"ls -la" > ~/.bashrc(不…

一维前缀和一维差分(下篇讲解二维前缀和二维差分)(超详细,python版,其他语言也很轻松能看懂)

本篇博客讲解一维前缀和&#xff0c;一维差分&#xff0c;还会给出一维差分的模板题&#xff0c;下篇博客讲解 二维前缀和&二维差分。 一维前缀和&#xff1a; 接触过算法的小伙伴应该都了解前缀和&#xff0c;前缀和在算法中应用很广&#xff0c;不了解也没有关系&#…

Ubuntu 搭建gitlab服务器,及使用repo管理

一、GitLab安装与配置 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 1、安装Ubuntu系统&#xff08;这个教程很多&#xff0c;就不展开了&#xff09;。 2、安装gitlab社区版本&#xff0c;有需…

车载电子电器架构 - 网络拓扑

车载电子电器架构 - 网络拓扑 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎,出门靠…

学习笔记Day11:初探Linux

Linux系统初探 Linux系统简介 发行版本Ubuntu/centOS&#xff0c;逻辑一样&#xff0c;都可以用。 服务器 本质是一台远程电脑&#xff0c;大多数服务器是Linux系统&#xff0c;通常使用命令行远程访问而不是桌面操作。LInux服务器允许多用户同时访问。NGS组学测序数据上游…

使用树莓派 结合Python Adafruit驱动OLED屏幕 显示实时视频

关于OLED屏幕的驱动&#xff0c;在之前我已经写过很多篇博文&#xff1a; IIC 协议 和 OLED_oled iic-CSDN博客 香橙派配合IIC驱动OLED & 使用SourceInsight解读源码_香橙派5 驱动屏幕-CSDN博客 这两篇博文都是通过模拟或调用IIC协议来使用C语言驱动OLED屏幕&#xff0c;现…

【Linux】进程---概念---进程---优先级

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.操作系统(Operator System) 1.1 概念 1.2 设计OS的目的 1.3 定位 1.4 如何理解 "管理" 1.5 总结 1.6 系统调用和…

数据可视化-ECharts Html项目实战(3)

在之前的文章中&#xff0c;我们学习了如何创建堆积折线图&#xff0c;饼图以及较难的瀑布图并更改图标标题。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 …

主存中存储单元地址的分配

主存中存储单元地址的分配 为什么写这篇文章? 因为我看书中这部分时&#xff0c;看到下面的计算一下子没反应过来&#xff1a; 知识回顾&#xff08;第1章&#xff09; 计算机系统中&#xff0c;字节是最小的可寻址的存储单位&#xff0c;通常由8个比特&#xff08;bit&…

OpenCV 单目相机标定

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 单目相机的标定过程与双目相机的标定过程很类似,具体过程如下所述: 1、首先我们需要获取一个已知图形的图像(这里我们使用MATLAB所提供的数据)。 2、找到同名像点(匹配点),这里主要是探测黑白格子之间的角点…