C++ 红黑树封装实现myset 和mymap

1.源码及框架分析

SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等⼏个头⽂件 中。

map和set的实现结构框架核⼼部分截取出来如下:

• 通过下图对框架的分析,我们可以看到源码中rb_tree⽤了⼀个巧妙的泛型思想实现,rb_tree是实 现key的搜索场景,还是key/value的搜索场景不是直接写死的,⽽是由第⼆个模板参数Value决定 _rb_tree_node中存储的数据类型。

• set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第⼆个模板参数给的是 pair,这样⼀颗红⿊树既可以实现key搜索场景的set,也可以实现key/value搜索场 景的map。

• 要注意⼀下,源码⾥⾯模板参数是⽤T代表value,⽽内部写的value_type不是我们我们⽇常 key/value场景中说的value,源码中的value_type反⽽是红⿊树结点中存储的真实的数据的类型。

• rb_tree第⼆个模板参数Value已经控制了红⿊树结点中存储的数据类型,为什么还要传第⼀个模板 参数Key呢?尤其是set,两个模板参数是⼀样的,这是很多同学这时的⼀个疑问。要注意的是对于 map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形 参的类型的。对于set⽽⾔两个参数是⼀样的,但是对于map⽽⾔就完全不⼀样了,map insert的 是pair对象,但是find和ease的是Key对象。

• 吐槽⼀下,这⾥源码命名⻛格⽐较乱,set模板参数⽤的Key命名,map⽤的是Key和T命名,⽽ rb_tree⽤的⼜是Key和Value,可⻅⼤佬有时写代码也不规范,乱弹琴。

2.模拟实现set和map

2.1实现出复用红黑树的框架,并支持insert

• 参考源码框架,map和set复⽤之前我们实现的红⿊树。

• 我们这⾥相⽐源码调整⼀下,key参数就⽤K,value参数就⽤V,红⿊树中的数据类型,我们使⽤ T。

• 其次因为RBTree实现了泛型不知道T参数到底是K,还是pair,那么insert内部进⾏插⼊逻辑 ⽐较时,就没办法进⾏⽐较,因为pair的默认⽀持的是key和value⼀起参与⽐较,我们需要时的任 何时候只⽐较key,所以我们在map和set层分别实现⼀个MapKeyOfT和SetKeyOfT的仿函数传给 RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进⾏⽐较,具体 细节参考如下代码实现。

2.2支持iterator的实现

iterator核⼼源代码

iterator实现思路分析

• iterator实现的⼤框架跟list的iterator思路是⼀致的,⽤⼀个类型封装结点的指针,再通过重载运算 符实现,迭代器像指针⼀样访问的⾏为。

• 这⾥的难点是operator++和operator--的实现。之前使⽤部分,我们分析了,map和set的迭代器⾛ 的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10 所在结点的迭代器。

• 迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。

• 迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点 是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。

• 迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也 访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上 找。

• 如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结 点的⽗亲;如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。

• 如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完 了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找 到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。如下图:it指向15,15右为空,15是10 的右,15所在⼦树话访问完了,10所在⼦树也访问完了,继续往上找,10是18的左,那么下⼀个 访问的结点就是18。

• end()如何表⽰呢?如下图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18 到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针 置为nullptr,我们⽤nullptr去充当end。需要注意的是stl源码空,红⿊树增加了⼀个哨兵位头结点 做为end(),这哨兵位头结点和根互为⽗亲,左指向最左结点,右指向最右结点。相⽐我们⽤ nullptr作为end(),差别不⼤,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处 理⼀下,让迭代器结点指向最右结点。具体参考迭代器--实现。

• 迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点-> 左⼦树,具体参考下⾯代码实现。

• 迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点-> 左⼦树,具体参考下⾯代码实现。

• set的iterator也不⽀持修改,我们把set的第⼆个模板参数改成const K即可, RBTreeconst K,const K, SetKeyOfT> _t;

• map的iterator不⽀持修改key但是可以修改value,我们把map的第⼆个模板参数pair的第⼀个参 数改成const K即可, RBTree<K,pair<const K,V>, MapKeyOfT> _t;

• ⽀持完整的迭代器还有很多细节需要修改,具体参考下⾯题的代码。

2.3map支持[]

• map要⽀持[]主要需要修改insert返回值⽀持,修改RBtree中的insert返回值为 pair<Iterator,bool> Insert(const T& data)

• 有了insert⽀持[]实现就很简单了,具体参考下⾯代码实现

2.4整体代码实现

封装myset和mymap

### C++红黑树实现与用法 #### 红黑树的基本结构 在 C++ 中,红黑树是一种自平衡二叉查找树。其节点通常由以下几个部分组成:左子节点指针 `_left`、右子节点指针 `_right`、父节点指针 `_parent`、表示颜色的枚举变量 `_col` 以及存储数据的数据成员 `_data`[^2]。 以下是红黑树节点的一个基本定义: ```cpp enum Color { RED, BLACK }; template<class T> struct RBTreeNode { RBTreeNode<T>* _left; RBTreeNode<T>* _right; RBTreeNode<T>* _parent; Color _col; T _data; RBTreeNode(const T& data) : _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED), _data(data) {} }; ``` #### 插入操作 红黑树的插入操作分为两个阶段:首先是普通的二叉搜索树插入过程;其次是通过一系列的颜色调整旋转来恢复红黑树的性质[^1]。具体来说: - **普通插入**:按照二叉搜索树的方式找到合适的位置并插入新节点。 - **修复操作**:如果插入破坏了红黑树的性质,则需要执行重新着色或旋转操作以恢复这些性质。 下面是一个简单的插入函数框架: ```cpp void Insert(RBTreeNode<int>* node); void FixInsertViolation(RBTreeNode<int>* node); // 假设存在一个根节点 root RBTreeNode<int>* root = nullptr; void Insert(int value) { RBTreeNode<int>* newNode = new RBTreeNode<int>(value); if (!root) { root = newNode; root->_col = BLACK; // 根节点必须是黑色 return; } RBTreeNode<int>* current = root; while (true) { if (current->_data > value) { if (!current->_left) { current->_left = newNode; newNode->_parent = current; break; } else { current = current->_left; } } else { if (!current->_right) { current->_right = newNode; newNode->_parent = current; break; } else { current = current->_right; } } } FixInsertViolation(newNode); // 调整违反的性质 } ``` #### 删除操作 删除操作相对复杂一些,涉及更多的情况判断可能的多次旋转。当删除某个节点时,可能会导致红黑树失去平衡,因此需要进行相应的修复操作[^3]。 #### 性能比较 相比于 AVL 树,红黑树虽然不是严格的平衡树,但在大多数情况下能够提供更好的性能。特别是在频繁插入删除的情况下,由于红黑树每次最多只需三次旋转即可完成再平衡,而 AVL 树则可能需要 O(log N) 次旋转,这使得红黑树更适合动态场景下的应用。 #### 使用场景 C++ STL 容器 `std::set` `std::map` 底层均采用红黑树作为其实现方式之一。这意味着开发者可以直接利用标准库的功能而不必手动构建红黑树实例。例如: ```cpp #include <set> int main() { std::set<int> mySet; mySet.insert(50); mySet.insert(30); mySet.insert(70); return 0; } ``` 上述代码片段展示了如何使用基于红黑树的 `std::set` 来管理有序集合。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值