当前位置: 首页 > news >正文

红黑树解析

目录

 

一、引言

二、红黑树的概念与性质

2.1 红黑树的概念

2.2 红黑树的性质

三、红黑树的节点定义与结构

3.1 节点定义

四、红黑树的插入操作

4.1 插入步骤

4.2 插入代码实现

五、红黑树的验证

5.1 验证步骤

5.2 验证代码实现

六、红黑树迭代器的实现

6.1 迭代器的定义

6.2 迭代器的递增和递减操作

七、红黑树模拟实现  map  与  set 

7.1  set  的模拟实现

7.2  map  的模拟实现 

八、总结 


 

一、引言

在数据结构的世界里,平衡二叉搜索树是高效组织和查询数据的重要工具。红黑树作为平衡二叉搜索树的一种,因其良好的平衡特性和相对简单的实现,在许多场景中得到广泛应用,尤其是在C++ 的标准模板库(STL)中, map  和  set  等关联式容器的底层实现就依赖于红黑树。本文将深入探讨红黑树的原理、实现细节,并结合代码详细展示其在模拟  map  和  set  容器中的应用。

二、红黑树的概念与性质

2.1 红黑树的概念

红黑树是一种二叉搜索树,在每个节点上增加了一个存储位来表示节点的颜色,颜色可以是红色(Red)或黑色(Black)。通过对从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,从而达到近似平衡的状态。

2.2 红黑树的性质

1. 节点颜色性质:每个节点不是红色就是黑色。

 

2. 根节点性质:根节点是黑色的。

 

3. 红色节点性质:如果一个节点是红色的,则它的两个孩子节点是黑色的。

 

4. 黑色节点高度性质:对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

 

5. 叶子节点性质:每个叶子节点(此处的叶子节点指的是空节点)都是黑色的。

 

这些性质保证了红黑树的最长路径不会超过最短路径的两倍,使得树的高度相对平衡,从而在插入、删除和查找操作上都能保持高效。

三、红黑树的节点定义与结构

3.1 节点定义

在C++ 中,我们可以如下定义红黑树的节点:

cpp// 节点的颜色enum Color {RED, BLACK};// 红黑树节点的定义template<class ValueType>struct RBTreeNode{RBTreeNode(const ValueType& data = ValueType(), Color color = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _pLeft; // 节点的左孩子RBTreeNode<ValueType>* _pRight; // 节点的右孩子RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)ValueType _data; // 节点的值域Color _color; // 节点的颜色};

 

这里将节点的默认颜色设为红色,主要是为了在插入新节点时,尽量减少对树结构调整的次数。因为插入红色节点比插入黑色节点更不容易破坏红黑树的性质,后续可以通过旋转和变色操作来恢复性质。3.2 红黑树结构

为了方便实现关联式容器,我们在红黑树的实现中增加一个头结点。头结点颜色为黑色,其  _pParent  域指向红黑树的根节点, _pLeft  域指向红黑树中最小的节点, _pRight  域指向红黑树中最大的节点。以下是红黑树类的基本框架:

cpptemplate<class K, class ValueType, class KeyOfValue>class RBTree{typedef RBTreeNode<ValueType> Node;typedef Node* PNode;public:typedef RBTreeIterator<ValueType, ValueType*, ValueType&> Iterator;RBTree();~RBTree();// 迭代器相关Iterator Begin() { return Iterator(_pHead->_pLeft); }Iterator End() { return Iterator(_pHead); }// 插入操作pair<Iterator, bool> Insert(const ValueType& data);// 清空树void Clear();// 查找操作Iterator Find(const K& key);// 容量相关size_t Size() const;bool Empty() const;private:PNode _pHead;size_t _size; // 红黑树中有效节点的个数};

 

四、红黑树的插入操作

4.1 插入步骤

红黑树的插入操作分为两步:

 

1. 按照二叉搜索树规则插入新节点:从根节点开始,根据新节点值与当前节点值的大小比较,向左或向右遍历树,直到找到合适的插入位置,插入新节点。新插入节点默认颜色为红色。

 

2. 检测并修复红黑树性质:由于新插入节点为红色,可能会违反红黑树的性质(主要是性质3:不能有连在一起的红色节点),此时需要对树进行调整,通过旋转和变色操作来恢复红黑树的性质。

4.2 插入代码实现

cpptemplate<class K, class ValueType, class KeyOfValue>pair<typename RBTree<K, ValueType, KeyOfValue>::Iterator, bool> RBTree<K, ValueType, KeyOfValue>::Insert(const ValueType& data){PNode& pRoot = _pHead->_pParent;if (nullptr == pRoot){pRoot = new Node(data, BLACK);pRoot->_pParent = _pHead;_pHead->_pLeft = _pHead->_pRight = pRoot;_size++;return make_pair(Iterator(pRoot), true);}KeyOfValue kofv;PNode pCur = pRoot;PNode pParent = nullptr;while (pCur){if (kofv(pCur->_data) > kofv(data)){pParent = pCur;pCur = pCur->_pLeft;}else if (kofv(pCur->_data) < kofv(data)){pParent = pCur;pCur = pCur->_pRight;}else{return make_pair(Iterator(pCur), false);}}pCur = new Node(data);pCur->_pParent = pParent;if (kofv(pParent->_data) > kofv(data)){pParent->_pLeft = pCur;if (_pHead->_pLeft == _pHead){_pHead->_pLeft = pCur;}}else{pParent->_pRight = pCur;if (_pHead->_pRight == _pHead){_pHead->_pRight = pCur;}}_size++;// 检测并修复红黑树性质while (pParent && pParent->_color == RED){PNode grandFather = pParent->_pParent;if (pParent == grandFather->_pLeft){PNode uncle = grandFather->_pRight;// 情况一:叔叔节点存在且为红if (uncle && uncle->_color == RED){pParent->_color = BLACK;uncle->_color = BLACK;grandFather->_color = RED;pCur = grandFather;pParent = pCur->_pParent;}else{// 情况二:叔叔节点不存在或为黑if (pCur == pParent->_pRight){_RotateLeft(pParent);swap(pParent, pCur);}pParent->_color = BLACK;grandFather->_color = RED;_RotateRight(grandFather);}}else{PNode uncle = grandFather->_pLeft;if (uncle && uncle->_color == RED){pParent->_color = BLACK;uncle->_color = BLACK;grandFather->_color = RED;pCur = grandFather;pParent = pCur->_pParent;}else{if (pCur == pParent->_pLeft){_RotateRight(pParent);swap(pParent, pCur);}pParent->_color = BLACK;grandFather->_color = RED;_RotateLeft(grandFather);}}}_pHead->_pParent->_color = BLACK;return make_pair(Iterator(pCur), true);}

上述代码中, _RotateLeft  和  _RotateRight  分别是左旋和右旋操作的函数,实现如下:

cpptemplate<class K, class ValueType, class KeyOfValue>void RBTree<K, ValueType, KeyOfValue>::_RotateLeft(PNode pParent){PNode pSubR = pParent->_pRight;PNode pSubRL = pSubR->_pLeft;pParent->_pRight = pSubRL;if (pSubRL){pSubRL->_pParent = pParent;}pSubR->_pLeft = pParent;PNode pPParent = pParent->_pParent;pParent->_pParent = pSubR;if (pPParent == nullptr){_pHead->_pParent = pSubR;}else if (pPParent->_pLeft == pParent){pPParent->_pLeft = pSubR;}else{pPParent->_pRight = pSubR;}pSubR->_pParent = pPParent;}template<class K, class ValueType, class KeyOfValue>void RBTree<K, ValueType, KeyOfValue>::_RotateRight(PNode pParent){PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;pParent->_pLeft = pSubLR;if (pSubLR){pSubLR->_pParent = pParent;}pSubL->_pRight = pParent;PNode pPParent = pParent->_pParent;pParent->_pParent = pSubL;if (pPParent == nullptr){_pHead->_pParent = pSubL;}else if (pPParent->_pLeft == pParent){pPParent->_pLeft = pSubL;}else{pPParent->_pRight = pSubL;}pSubL->_pParent = pPParent;}

五、红黑树的验证

 

5.1 验证步骤

红黑树的检测分为两步:

 

1. 检测是否满足二叉搜索树性质:通过中序遍历,检查遍历结果是否为有序序列。

 

2. 检测是否满足红黑树性质:检查根节点是否为黑色,以及每条路径上黑色节点的个数是否相同等性质。

5.2 验证代码实现

cpptemplate<class K, class ValueType, class KeyOfValue>bool RBTree<K, ValueType, KeyOfValue>::IsValidRBTree(){PNode pRoot = _pHead->_pParent;// 空树也是红黑树if (nullptr == pRoot){return true;}// 检测根节点是否为黑色if (pRoot->_color!= BLACK){std::cout << "违反红黑树性质二:根节点必须为黑色" << std::endl;return false;}// 获取任意一条路径中黑色节点的个数size_t blackCount = 0;PNode pCur = pRoot;while (pCur){if (pCur->_color == BLACK){blackCount++;}pCur = pCur->_pLeft;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return _IsValidRBTree(pRoot, k, blackCount);}template<class K, class ValueType, class KeyOfValue>bool RBTree<K, ValueType, KeyOfValue>::_IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount){// 走到null之后,判断k和black是否相等if (nullptr == pRoot){if (k!= blackCount){std::cout << "违反性质四:每条路径中黑色节点的个数必须相同" << std::endl;return false;}return true;}// 统计黑色节点的个数if (pRoot->_color == BLACK){k++;}// 检测当前节点与其双亲是否都为红色PNode pParent = pRoot->_pParent;if (pParent && pParent->_color == RED && pRoot->_color == RED){std::cout << "违反性质三:没有连在一起的红色节点" << std::endl;return false;}return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&_IsValidRBTree(pRoot->_pRight, k, blackCount);}

 

六、红黑树迭代器的实现

6.1 迭代器的定义

迭代器是红黑树与用户交互的重要接口,它可以方便地遍历红黑树。我们定义迭代器类如下:

cpptemplate<class ValueType, class Pointer, class Reference>class RBTreeIterator{typedef RBTreeNode<ValueType> Node;typedef RBTreeIterator<ValueType, Pointer, Reference> Self;public:RBTreeIterator(Node* node = nullptr) : _pNode(node) {}Reference operator*() { return _pNode->_data; }Pointer operator->() { return &(_pNode->_data); }Self& operator++(){_Increament();return *this;}Self operator++(int){Self tmp(*this);_Increament();return tmp;}Self& operator--(){_Decreament();return *this;}Self operator--(int){Self tmp(*this);_Decreament();return tmp;}bool operator==(const Self& s) const { return _pNode == s._pNode; }bool operator!=(const Self& s) const { return _pNode!= s._pNode; }private:void _Increament(){if (_pNode->_pRight){_pNode = _pNode->_pRight;while (_pNode->_pLeft){_pNode = _pNode->_pLeft;}}else{Node* pParent = _pNode->_pParent;while (pParent && _pNode == pParent->_pRight){_pNode = pParent;pParent = pParent->_pParent;}_pNode = pParent;}}void _Decreament(){if (_pNode->_color == RED && _pNode->_pParent->_pParent == _pNode){_pNode = _pNode->_pRight;}else if (_pNode->_pLeft){_pNode = _pNode->_pLeft;while (_pNode->_pRight){_pNode = _pNode->_pRight;}}else{Node* pParent = _pNode->_pParent;while (pParent && _pNode == pParent->_pLeft){_pNode = pParent;pParent = pParent->_pParent;}_pNode = pParent;}}Node* _pNode;};

 

6.2 迭代器的递增和递减操作

迭代器的递增操作  operator++  用于找到当前节点的下一个节点。如果当前节点有右子树,那么下一个节点就是右子树中最左侧的节点;如果当前节点没有右子树,则向上查找,直到找到一个是其双亲节点左孩子的节点,该双亲节点就是下一个节点。

 

迭代器的递减操作  operator--  则相反,用于找到当前节点的前一个节点。如果当前节点是头结点且颜色为红色,那么前一个节点是右子树中最大的节点;如果当前节点有左子树,那么前一个节点就是左子树中最右侧的节点;如果当前节点没有左子树,则向上查找,直到找到一个是其双亲节点右孩子的节点,该双亲节点就是前一个节点。

七、红黑树模拟实现  map  与  set 

7.1  set  的模拟实现

 set  是一种关联式容器,其底层结构为红黑树。`在  set  中,每个元素的值同时也是其键值,元素是唯一且有序的。下面是  set  类的模拟实现代码:
 

cpp   
namespace bit
{template<class K>class set{private:// 定义仿函数用于提取键值struct KeyOfValue{const K& operator()(const K& key) const{return key;}};// 底层使用红黑树存储数据typedef RBTree<K, K, KeyOfValue> RBTreeType;public:typedef typename RBTreeType::Iterator iterator;set() {}// 迭代器相关iterator begin() { return _tree.Begin(); }iterator end() { return _tree.End(); }// 容量相关size_t size() const { return _tree.Size(); }bool empty() const { return _tree.Empty(); }// 插入操作pair<iterator, bool> insert(const K& data){return _tree.Insert(data);}// 清空操作void clear() { _tree.Clear(); }// 查找操作iterator find(const K& key) { return _tree.Find(key); }private:RBTreeType _tree;};
}


 
在上述代码中,通过定义  KeyOfValue  仿函数,将元素值直接作为键值。然后利用前面实现的红黑树类  RBTree  来存储和管理元素,对外提供  set  容器常用的接口,如插入、查找、遍历等。
 

7.2  map  的模拟实现
 


 map  也是关联式容器,它存储的是键值对( pair ),其中键值是唯一且有序的,通过键值可以快速查找对应的值。以下是  map  类的模拟实现:
 

cpp   
namespace bit
{template<class K, class V>class map{private:// 定义键值对类型typedef pair<K, V> valueType;// 定义仿函数用于提取键值struct KeyOfValue{const K& operator()(const valueType& v) const{return v.first;}};// 底层使用红黑树存储数据typedef RBTree<K, valueType, KeyOfValue> RBTreeType;public:typedef typename RBTreeType::Iterator iterator;map() {}// 迭代器相关iterator begin() { return _tree.Begin(); }iterator end() { return _tree.End(); }// 容量相关size_t size() const { return _tree.Size(); }bool empty() const { return _tree.Empty(); }// 插入操作pair<iterator, bool> insert(const valueType& data){return _tree.Insert(data);}// 重载 [] 运算符,方便通过键值访问对应的值V& operator[](const K& key){pair<iterator, bool> ret = _tree.Insert(valueType(key, V()));return ret.first->second;}const V& operator[](const K& key) const{iterator it = _tree.Find(key);return it->second;}// 清空操作void clear() { _tree.Clear(); }// 查找操作iterator find(const K& key) { return _tree.Find(key); }private:RBTreeType _tree;};
}


 
 
在  map  的实现中,通过  KeyOfValue  仿函数从键值对中提取键值。同样基于红黑树实现数据存储和管理,并且重载了  []  运算符,使得可以像使用数组一样通过键值方便地访问和修改对应的值。
 


八、总结
 


红黑树通过巧妙地利用节点颜色和一些性质,在保证树近似平衡的同时,实现了高效的插入、删除和查找操作。在C++ 中, map  和  set  等关联式容器借助红黑树的特性,为我们提供了快速、有序的数据存储和访问方式。通过深入理解红黑树的原理和实现细节,我们不仅能更好地使用这些容器,还能在遇到类似数据组织和查询需求时,灵活运用红黑树来设计高效的数据结构。
 
希望本文能帮助大家对红黑树及其在C++ 关联式容器中的应用有更清晰、深入的认识。如果有任何疑问或建议,欢迎交流探讨。

 

相关文章:

  • uniapp x
  • 网络安全EN18031-1,EN18031-2,EN18031-3三个标准对应的测试项目
  • jedis+redis pipeline诡异的链接损坏、数据读取异常问题解决
  • vue使用vite, 渲染glb模型时报错
  • Nginx与Tomcat负载均衡集群配置指南
  • 牛客网NC21994:分钟计算
  • 计量经济学——预测与chow检验
  • [6-8] 编码器接口测速 江协科技学习笔记(7个知识点)
  • 虚拟网络编辑器
  • 【数据结构入门训练DAY-35】棋盘问题
  • Python-Django系列—日志
  • 张 提示词优化(相似计算模式)深度学习中的损失函数优化技巧
  • ES常识9:如何实现同义词映射(搜索)
  • 平滑过滤值策略
  • 时序数据库IoTDB分布式架构解析与运维指南
  • DeepSeek 赋能物联网:从连接到智能的跨越之路
  • 【Pandas】pandas DataFrame diff
  • 语音识别——通过PyAudio录入音频
  • Linux线程控制
  • 【Pandas】pandas DataFrame eval
  • 北京警方:海淀发生小客车刮碰行人事故4人受伤,肇事司机已被查获
  • 陕西榆林:全力推进榆林学院升格榆林大学
  • 人民日报:从“轻微免罚”看涉企执法方式转变
  • 腾讯一季度营收增长13%,马化腾:战略性的AI投入将带来长期回报
  • 七旬男子驾“老头乐”酒驾被查,曾有两次酒驾两次肇事记录
  • 京东美团饿了么等外卖平台被约谈