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

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

文章目录

  • 模拟实现 map 和 set
    • 1. 如何复用前面写过的红黑树
    • 2. 支持 Insert
    • 3. 迭代器的实现
      • 3.1 RBTree 的 iterator
      • 3.2 map、set 的迭代器实现
    • 3.3 map 支持 []

前面我们实现了模拟实现了红黑树,今天就通过复用之前的代码来封装 map 和 set。

模拟实现 map 和 set

1. 如何复用前面写过的红黑树

map、set的底层都是红黑树,但是set在使用时需要传一个 K 模板参数就可以了,但是 map 则需要传一个 pair<K, V> 的模板参数,这就导致了虽然 set、map的底层都是红黑树,但是我们需要写 Key 和 pair<K, V> 两种结构的红黑树代码,这显然过于冗杂,那该怎么办呢?
是的,我们可以使用模板让编译器自己生成,虽说在编译器生成后还是两棵独立的红黑树代码,但是我们手写部分明显少了很多,也更方便后续的维护。
前面我实现红黑树的时候是使用 K,V 模板实现的,但是我们这里可以对其修改一下,变成 K,T 模板,T指的是这棵树存储的是 K 类型还是 pair<K, V> 类型

还有一个问题,我们这里使用的是 K,T 模板参数,既然 T 参数可以表示底层存储类型,为什么还要一个 K 模板参数呢?这样会不会很冗余?
你可以想想 Insert、find、erase 的场景,在 Insert 的时候确实是可以直接用 T 没错。但是如果你是 map ,你在 find、erase 的时候,是使用 pair<K, V> 的吗?显然不是吧,使用的是 K,所以这里需要额外使用一个 K 模板参数

所以我们的 RBTreeNode 直接用 T 模板参数表示它的存储的类型
这里又引申出来个问题,我们之前使用 K,V 的时候可以直接使用它的 first 进行比较,但是这里我们并不知道它的类型到底是 K 还是 pair ,所以这里我们需要引入一个KeyOfT 仿函数来帮我们支持比较。

// 修改前
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class V>
class RBTree {}// 修改后
template<class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class Tclass KeyOfT>
class RBTree {}

在 map 里面实现 MapKeyOfT,在 set 里面实现 SetKeyOfT,用于各自支持比较

	template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;}template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};private:RBTree<K, const K, SetKeyOfT> _t;};

我们前面说过在红黑树类前有一个 KeyOfT 模板参数,等需要使用的时候,就在要使用的地方通过 KeyOfT 实例化一个对象,再把要比较的对象传过去再进行比较。

// 类似于这样的使用方式
KeyOfT kot;
if (kot(cur->_data) < kot(data))

2. 支持 Insert

因为我们在 map、set 类中分别存在着一个对应的红黑树对象,所以我们想的应该是直接通过调用底层红黑树的 Insert 函数来实现,我们外层的 map、set 仅仅只是一层壳。

	template<class K>class set{bool insert(const K& key){return _t.Insert(key);}};template<class K, class V>class map{pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}}

3. 迭代器的实现

要想支持 map、set 的迭代器,得现在底层支持 RBTree 的迭代器

3.1 RBTree 的 iterator

还是老规矩,迭代器模板参数数量为三个,分别为 T、Ref、Ptr,以便于在使用时通过控制参数是否为 const 类型来实例化为 普通迭代器 和 const 迭代器。

RBTree 的迭代器和链表的迭代器比较相似,因为都是链式结果。最大的区别在于它们++、-- 的方式不同。因为RBTree 的底层是一棵树,所以相对而言会复杂许多。

operator++
因为红黑树是一棵自平衡的二叉搜索树,所以我们对它 ++ 的设计就是通过模仿中序的方式 来实现的。

那么就来讨论一下中序。
中序代表着这一节点已经使用,而下一节点是右子树的最左(最小)节点,所以:

  • 如果当前节点存在右孩子的话,就先取它的右节点,再取这个右孩子的最左节点
  • 如果当前节点不存在右孩子的话:
    • 如果当前节点是父节点的左,根据中序左->根->右,那么下一节点就是当前节点的父节点
    • 如果当前节点是父节点的右, 当前节点访问完了,就表示父节点的子树访问完了。那我们就模拟回溯的行为,往根的祖先中去找,直到找到一个节点,它是父亲的左节点,那么那个节点的父亲节点就是下一个要访问的节点

如何表示 begin 呢?
按照二叉搜索树的性质,它第一个访问位置为整棵树的最左节点,那么就是它

如何表示 end 呢?
按照上面++的思路,当我们找完最后一个有效节点之后,你再按照上面的思路进行一次++,你会发现你到了 _root 节点的父节点的位置。_root 节点的父节点为空,所以我们将 end 设置为 nullptr

operator–
迭代器 – 的实现刚好和 ++ 反过来,它的访问顺序是 右->根->左。

  • 因为 end 位置是 nullptr,所以需要特判一下,直接返回整棵树的最右节点即可
  • 如果左子树不为空,按照中序,我们可以确定下一个访问位置是左子树的最后一个节点(最右节点)
  • 如果左子树为空,下一个节点是 孩子是父节点的左节点 的那个父节点

其他的和之前实现其他迭代器的方式是一样的

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;Node* _root;RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}Self operator++(){if (_node->_right){// 右不为空,中序下一个访问节点是右子树的最左(最小)节点Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{// 右为空,祖先里面孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--(){if (_node == nullptr) // --end(){// --end(),特殊处理,走到中序最后一个节点,整棵树的最右节点Node* rightMost = _root;while (rightMost && rightMost->_right)rightMost = rightMost->_right;_node = rightMost;}else if (_node->_left){// 左子树不为空,中序左子树最后一个Node* rightMost = _root->_left;while (rightMost->_right)rightMost = rightMost->_right;_node = rightMost;}else{// 孩子是父亲右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

3.2 map、set 的迭代器实现

都是通过调用底层红黑树的迭代器来实现的。

// map:
namespace zkp
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:// 这里 typename 是为了声明这是类型typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end()  const{return _t.End();}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};};// set中:
namespace zkp
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end()  const{return _t.End();}private:// 反正 set 的key不允许修改,直接加 constRBTree<K, const K, SetKeyOfT> _t;};
}

3.3 map 支持 []

我们在使用库中的 map 时,是可以直接使用 [] 进行插入和修改的,而要支持这个,则需要修改底层红黑树 Insert 的返回值,和 map 中 insert 的返回值

		// map 中:pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}// set 中:// 这里只是为了维护代码堆统一性,并无实际作用pair<iterator, bool> insert(const K& key){return _t.Insert(key);}// 底层红黑树的插入pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//return pair<Iterator, bool>(Iterator(_root, _root), true);return { Iterator(_root, _root), true }; // 直接用 C++11 的方式写吧}// 插入部分KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return { Iterator(cur, _root), false };}}cur = new Node(data);// 保存新节点,等下返回的时候用Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}// 链接父亲cur->_parent = parent;// 父亲是红色节点,出现连续红色节点,开始处理while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//   g// p   uNode* uncle = grandfather->_right;// 如果叔叔节点也是红色,直接变色并继续向上调整就可以了,不需要旋转if (uncle && uncle->_col == RED){// 变色uncle->_col = parent->_col = BLACK;grandfather->_col = RED;// 将 g 作为新的 c 继续向上调整cur = grandfather;parent = cur->_parent;}else{// 到这就该考虑 p、c 同侧还是异侧的情况了// p、c同侧单旋,异侧双旋if (cur == parent->_left){// 同侧单旋//		g//	  p   u//  cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 异侧双旋//		g//	  p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}// 单旋双旋都是直接跳出循环break;}}else{//   g// u   pNode* uncle = grandfather->_left;// 叔叔为红,变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 向上处理cur = grandfather;parent = cur->_parent;}else{// 还是考虑同侧异侧的问题,代码和上面刚好对称if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 保证根节点为黑色节点_root->_col = BLACK;return { Iterator(newnode, _root), true };}

相关文章:

  • 记录将网站从http升级https
  • Linux(7)——进程(概念篇)
  • 万亿参数背后的算力密码:大模型训练的分布式架构与自动化运维全解析
  • 【RichTextEditor】 【分析2】RichTextEditor设置文字内容背景色
  • 毕业论文格式(Word)
  • python 自动生成不同行高的word
  • 攻防世界——Web题 unseping 反序列化绕过
  • 计算机视觉与深度学习 | 基于 YOLOv8 + BeautyGAN + CodeFormer + Face Parsing 实现简单的人脸美颜
  • Spring Security探索与应用
  • 如何进行CAN一致性测试
  • 从稳定到卓越:服务器部署后的四大核心运维策略
  • 传奇各种怪物一览/图像/爆率/产出/刷新地/刷新时间/刷怪时间
  • LeetCode 2942.查找包含给定字符的单词:使用库函数完成
  • vs2022 Qt Visual Studio Tools插件设置
  • 人工智能100问☞第31问:如何评估一个AI模型的性能?
  • IPC进程间通信详解
  • 索引下探(Index Condition Pushdown,简称ICP)
  • MCP与AI模型的多语言支持:让人工智能更懂世界
  • 数据库6——综合实验-水果商店进阶一
  • Axure酒店管理系统原型
  • 重庆网站建设找重庆万为/软件开发培训
  • 类似聚划算的网站怎么建设/seo研究中心道一老师
  • 广州市品牌网站建设企业/汽车品牌推广策划方案
  • 学习css网站开发/厦门seo推广优化
  • 妹子ui wordpress/长沙seo技术培训
  • 做足球经理头像的网站/免费关键词挖掘工具