【C++杂货铺】一文带你走进RBTree

2023-10-10 17:01

本文主要是介绍【C++杂货铺】一文带你走进RBTree,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

文章目录

  • 一、红黑树的概念
  • 二、红黑树的性质
  • 三、红黑树结点的定义
  • 四、红黑树的插入操作
    • 4.1 情况一:uncle 存在且为红
    • 4.2 情况二:uncle 不存在
    • 4.3 情况三:uncle 存在且为黑
    • 4.4 插入完整源码
  • 五、红黑树的验证
  • 六、红黑树与 AVL 树的比较
  • 七、结语

一、红黑树的概念

红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,这句话换个意思就是:红黑树中最长路径不超过最短路径的 2 倍。因而是接近平衡的,而 AVL 树是严格平衡的,这就导致,红黑树的高度会比 AVL 树高一些,但是效率并不会比 AVL 树差。
在这里插入图片描述

二、红黑树的性质

  • 每个结点不是红色就是黑色。

  • 根节点是黑色。

  • 如果一个结点是红色,则它的两个孩子结点必须是黑色的。

  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

  • 每个 NIL 叶子结点都是黑色的(此处的叶子结点指的是空节点)。

小Tips:第三点决定了一颗红黑树的任何路径没有连续的红色结点。在红黑树中计算路径一定是计算到 NIL 结点。一颗红黑树中的最短路径是全为黑结点的路径,最长路径是一黑一红相间的路径。任意一条路径上黑色结点的占比一定是大于等于 1 / 2 1/2 1/2 的。这就决定了,红黑树中其最长路径中结点个数不会超过最短路径结点个数的两倍。

三、红黑树结点的定义

//红黑树的结点
template<class K, class V>
struct RBTreeNode
{RBTreeNode(const& pair<K, V> kv = pair<K, V>(), Color color = RED):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_col(color){}pair<K, V> _kv;//结点中存的值RBTreeNode<K, V>* _left;//结点的左孩子RBTreeNode<K, V>* _right;//结点的右孩子RBTreeNode<K, V>* _parent;//结点的父亲Color _col;//结点的颜色
};

小Tips:新节点默认颜色是 RED。这是因为,如果新插入结点的颜色是 BLACK,那意味着当前路径上新增了一个黑色结点,为了保证二叉树的第四条性质,我们要对这颗红黑树其他的所有路径进行检查,可见新插入结点如果默认是 BLACK,会存在着牵一发而动全身的影响。而让新插入结点默认是 RED 则不会出现这样的结果。假如新插入结点的 parent 恰好是 BLACK,那这次插入就没有什么问题。如果新插入结点是 parentRED,此时需要对这颗红黑树稍作调整。

四、红黑树的插入操作

红黑树是在二叉搜索树的基础上加上平衡限制条件,因此红黑树的插入可以分为两步:

  • 按照二叉搜索树的规则插入结点。

  • 检测新节点插入后,红黑树的性质是否遭到破坏。

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

约定cur为当前结点,parent 为父结点,grandp 为祖父结点,uncle 为叔叔结点。如果 parent 为红那 grandp 一定为黑。所以当前唯一不确定的就是 uncle,主要分以下三种情况

4.1 情况一:uncle 存在且为红

在这里插入图片描述
小Tips:此处看到的树,可能是一颗完整的树,也可能是一颗子树。

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

  • 如果 grandp 是根结点,将 grandp 再改成黑色,本次插入就算结束。

  • 如果 grandp 是子树,则其一定也有双亲,且 grandp 的双亲如果是红色,需要继续向上调整。

在这里插入图片描述

4.2 情况二:uncle 不存在

如果 uncle 结点不存在,则 cur 一定是新插入结点,因为如果 cur 不是新插入结点,则 curparent 一定有一个结点的颜色是黑色,就不满足性质四:每条路径黑色结点个数相同。
在这里插入图片描述
解决方法:直接进行旋转即可。

4.3 情况三:uncle 存在且为黑

在这里插入图片描述
叔叔存在且为黑,那么 cur 一定不是新插入的结点,并且 cur 结点原来的颜色一定是黑色,现在看到是红色的原因是因为 cur 的子树在调整的过程中将 cur 结点的颜色由黑色改成了红色。

4.4 插入完整源码

public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;//插入成功}//找插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first)//小于往左走{parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first)//大于往右走{parent = cur;cur = cur->_right;}else//相等插入不了{return false;}}//找到待插入位置了,进行插入cur = new Node(kv);cur->_col = RED;if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//检测新结点插入后,红黑树的性质是否遭到破坏while (parent && parent->_col == RED){Node* grandp = parent->_parent;if (parent == grandp->_left){Node* uncle = grandp->_right;if (uncle && uncle->_col == RED)//叔叔存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandp->_col = RED;//继续向上处理cur = grandp;parent = cur->_parent;}else //uncle不存在或者存在为黑{if (cur == parent->_left){RotateR(grandp);parent->_col = BLACK;//parent当了根grandp->_col = RED;}else if (cur == parent->_right){RotateLR(grandp);cur->_col = BLACK;//cur当了根节点grandp->_col = RED;}break;}}else if (parent == grandp->_right){Node* uncle = grandp->_left;if (uncle && uncle->_col == RED)//叔叔存在且为红{parent->_col = BLACK;uncle->_col = BLACK;grandp->_col = RED;//继续向上处理cur = grandp;parent = cur->_parent;}else //uncle不存在或者存在为黑{if (cur == parent->_right){RotateL(grandp);parent->_col = BLACK;//parent当了根grandp->_col = RED;}else if (cur == parent->_left){RotateRL(grandp);cur->_col = BLACK;//cur当了根节点grandp->_col = RED;}break;}}}_root->_col = BLACK;//根结点始终变黑return true;}
private://左单旋void RotateL(Node* parent){++_rotatecount;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;cur->_left = parent;if (curleft){curleft->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}//右单旋void RotateR(Node* parent){++_rotatecount;Node* cur = parent->_left;Node* curright = cur->_right;//此时的情况是curright比cur大,比parent小parent->_left = curright;cur->_right = parent;if (curright){curright->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = cur;if (ppnode){cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}else{_root = cur;cur->_parent = nullptr;}}//右左双旋void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;RotateR(parent->_right);RotateL(parent);}//左右双旋void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;RotateL(cur);RotateR(parent);}

五、红黑树的验证

红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。

  • 检测其是否满足红黑树的性质(主要是性质三和性质四)。

public:bool Isblance(){if (_root == nullptr)return true;//根节点如果不是黑色说明就不是红黑树if (_root->_col != BLACK){return false;}//计算红黑树中任意一条路径上黑色结点的个数作为一个基准值Node* cur = _root;int count = 0;while (cur){if (cur->_col == BLACK){++count;}cur = cur->_left;}return CheckColour(_root, 0, count);}
private://检查颜色bool CheckColour(Node* root, int blacknum, int stand){if (root == nullptr){//到这里说明一条路径结束,那么这条路径上的黑色结点数也一定统计出来了if (blacknum != stand){cout << "当前路径上黑色结点的个数有问题" << endl;return false;}return true;}//检查是否出现连续的红色结点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << ":为红色节点,并且孩子结点也是红色" << endl;}//统计一条路径上黑色结点个数if (root->_col == BLACK){++blacknum;}return CheckColour(root->_left, blacknum, stand) && CheckColour(root->_right, blacknum, stand);}	

六、红黑树与 AVL 树的比较

红黑树和 AVL 树都是高效的平衡二叉树,增删查改的时间复杂度都是 O ( l o g 2 N ) O(log2^N) O(log2N),红黑树不追求绝对平衡,其只需要保证最长路径不超过最短路径的 2 倍,相对而言,降低了插入过程中旋转的次数,所以在经常进行增删查改的结构中性能比 AVL 树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。红黑树主要会应用在以下几个地方:

  • C++ STL 库----map、set、mutilmap、mutilset。

  • Java 库。

  • Linux 内核。

  • 其它一些库。

七、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

这篇关于【C++杂货铺】一文带你走进RBTree的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/181852

相关文章

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

一文全面详解Python变量作用域

《一文全面详解Python变量作用域》变量作用域是Python中非常重要的概念,它决定了在哪里可以访问变量,下面我将用通俗易懂的方式,结合代码示例和图表,带你全面了解Python变量作用域,需要的朋友... 目录一、什么是变量作用域?二、python的四种作用域作用域查找顺序图示三、各作用域详解1. 局部作

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

一文彻底搞懂Java 中的 SPI 是什么

《一文彻底搞懂Java中的SPI是什么》:本文主要介绍Java中的SPI是什么,本篇文章将通过经典题目、实战解析和面试官视角,帮助你从容应对“SPI”相关问题,赢得技术面试的加分项,需要的朋... 目录一、面试主题概述二、高频面试题汇总三、重点题目详解✅ 面试题1:Java 的 SPI 是什么?如何实现一个

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元

C/C++的OpenCV 进行图像梯度提取的几种实现

《C/C++的OpenCV进行图像梯度提取的几种实现》本文主要介绍了C/C++的OpenCV进行图像梯度提取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录预www.chinasem.cn备知识1. 图像加载与预处理2. Sobel 算子计算 X 和 Y

C/C++和OpenCV实现调用摄像头

《C/C++和OpenCV实现调用摄像头》本文主要介绍了C/C++和OpenCV实现调用摄像头,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录准备工作1. 打开摄像头2. 读取视频帧3. 显示视频帧4. 释放资源5. 获取和设置摄像头属性