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

【C++】模拟实现map和set

1. 调整之前实现的红黑树的insert

1.1 整体框架的搭建

新建两个头文件,Mymap.hMyset.h,一个源文件test.cpp,然后把之前实现的红黑树拷贝一份过来。

为了和库里面的一些东西区分开,我们还是把所有自己实现的内容都放在自己的命名空间里。

Mymap.h中搭建框架。key参数就⽤K,value参数就⽤V。

代码语言:javascript

AI代码解释

#include "RBTree.h" namespace lyj { template<class K, class V> class map { private: RBTree<K, pair<K, V>> _t; }; }

Myset.h中搭建框架。

代码语言:javascript

AI代码解释

#include "RBTree.h" namespace lyj { template<class K> class set { private: RBTree<K, K> _t; }; }

直接对这个RBTree.h进行修改。

首先就是把模板参数改掉,红⿊树中的数据类型我们使⽤T

代码语言:javascript

AI代码解释

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) {} };

key参数就⽤K,这里的T就是决定到底是set还是map的。

如果我们传一个K过去就是set,传pair过去就是map。

1.2 insert的修改

因为我们把红⿊树中的数据类型T来表示了,也就是_data,这个_data是一个泛型,可能是set的K,可能是map的pair,用以前的逻辑就不能满足这个比较。

这个_kv换成_data也没用,不适用于set的K场景,并且pair自身支持的比较方法也不是我们想要的,我们需要任何时候都只比较K。

此时我们就需要实现一个仿函数。就是取出K来,set中就取K,map中就取first。

Myset.hset类里实现仿函数Set_Key_Of_T

代码语言:javascript

AI代码解释

struct Set_Key_Of_T { const K& operator()(const K& key) { return key; } };

set里这个仿函数就是给 key直接返回key就行。

Mymap.hmap类里实现仿函数Map_Key_Of_T

代码语言:javascript

AI代码解释

struct Map_Key_Of_T { const K& operator()(const pair<K, V>& kv) { return kv.first; } };

map的仿函数就是返回pair里的first。

有仿函数之后,红黑树的模板参数也要多加一个了,叫KeyOfT。

代码语言:javascript

AI代码解释

template<class K, class T, class KeyOfT> class RBTree { //... }

insert里的比较逻辑就要要成用这个仿函数写的逻辑。

map的find返回的是一个pair,这个pair的first是一个迭代器,second是一个bool值,所以这里的返回值也要修改一下。

代码语言:javascript

AI代码解释

pair<Iterator, bool> insert(const T& data) { }

插入成功,就返回新插入的值的迭代器和true,插入失败就返回已经存在的这个值的迭代器和false。所以整体代码如下。

代码语言:javascript

AI代码解释

pair<Iterator, bool> insert(const T& data) { if (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; //根节点为黑色 return make_pair(Iterator(_root, _root), true); } Node* parent = nullptr; Node* cur = _root; KeyOfT com; //仿函数 while (cur) { if (com(cur->_data) > com(data)) { parent = cur; cur = cur->_left; } else if (com(cur->_data) < com(data)) { parent = cur; cur = cur->_right; } else return make_pair(Iterator(cur, _root), false); } cur = new Node(data); Node* newnode = cur; //记录新插入的节点 cur->_col = RED; //新插入节点为红色 if (com(cur->_data) < com(parent->_data)) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; if (parent == grandfather->_left) //u在右 { Node* uncle = grandfather->_right; if (uncle && uncle->_col == RED) //u存在且为红 { parent->_col = BLACK; //u和p变黑 uncle->_col = BLACK; grandfather->_col = RED;//g变红 cur = grandfather; //继续向上更新 parent = cur->_parent; } else //u不存在 或 u存在且为黑 { if (cur == parent->_left) //单旋 { rotateR(grandfather);//以g为旋转点右旋 parent->_col = BLACK; //变色 grandfather->_col = RED; } else //双旋 { rotateL(parent); //先对p左旋 rotateR(grandfather);//再对g右旋 //变色 cur->_col = BLACK; grandfather->_col = RED; } break; } } else //u在左 { Node* uncle = grandfather->_left; if (uncle && uncle->_col == RED) //u存在且为红 { parent->_col = BLACK; //p和u变黑 uncle->_col = BLACK; grandfather->_col = RED;//g变红 cur = grandfather; //继续向上更新 parent = cur->_parent; } else //u不存在 或 存在且为黑 { if (cur == parent->_right) //单旋 { rotateL(grandfather);//以g为中心左旋 parent->_col = BLACK; //p变黑 grandfather->_col = RED;//g变红 } else //双旋 { rotateR(parent);//先以p为中心右旋 rotateL(grandfather);//再以g为中心左旋 cur->_col = BLACK; //c变黑 grandfather->_col = RED;//g变红 } break; } } } _root->_col = BLACK; return make_pair(Iterator(newnode, _root), true); }

上面的代码相比之前实现的insert,只有插入部分的代码修改了,旋转部分没做修改。

然后我们用set的insert测试一下。

Myset.h中加上insert的函数,在set类public实现。

代码语言:javascript

AI代码解释

public: pair<iterator, bool> insert(const K& key) //插入 { return _t.insert(key); } private: RBTree<K, K, Set_Key_Of_T> _t;

Mymap.h中加上insert的函数,在map类public实现。

代码语言:javascript

AI代码解释

public: pair<iterator, bool> insert(const pair<K, V>& kv) //插入 { return _t.insert(kv); } private: RBTree<K, pair<K, V>, Map_Key_Of_T> _t;

test.cpp中测试。如果没报错,目前这个insert就是对的。

代码语言:javascript

AI代码解释

#include "Myset.h" #include "Mymap.h" int main() { lyj::set<int> s; //用自己实现的set s.insert(5); s.insert(3); s.insert(2); s.insert(4); s.insert(1); return 0; }

2. iterator 迭代器的实现

2.1 部分运算符重载

这里需要同时考虑普通的迭代器和const迭代器,所以还是要一个类模板来实现。

代码语言:javascript

AI代码解释

template<class T, class ref, class ptr> struct RBTree_Iterator { typedef RBTreeNode<T> Node; typedef RBTree_Iterator<T, ref, ptr> Self; };

还需要一个节点的指针,并写一个构造函数。

代码语言:javascript

AI代码解释

template<class T, class ref, class ptr> struct RBTree_Iterator { typedef RBTreeNode<T> Node; typedef RBTree_Iterator<T, ref, ptr> Self; Node* _node; RBTree_Iterator(Node* node) :_node(node) {} };

然后还是在类里实现一下operator*, operator->, operator!= 和 operator== 这4个运算符重载。

代码语言:javascript

AI代码解释

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; }

然后在RBTree类里在重命名一下。

代码语言:javascript

AI代码解释

typedef typename RBTree_Iterator<T, T&, T*> Iterator; typedef typename RBTree_Iterator<T, const T&, const T*> const_Iterator;

2.2 迭代器
2.2.1 begin和end

以下图为例,map和set的迭代器⾛的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10 所在结点的迭代器。

end()如何表⽰呢?图中,当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18

到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针

置为nullptr,我们⽤nullptr去充当end

RBTree.hRBTree类中public实现。

代码语言:javascript

AI代码解释

Iterator Begin() { Node* cur = _root; while (cur && cur->_left) { cur = cur->_left; //找最左节点 } return Iterator(cur); } const_Iterator Begin() const //const迭代器 { Node* cur = _root; while (cur && cur->_left) { cur = cur->_left; //找最左节点 } return Iterator(cur); }

代码语言:javascript

AI代码解释

Iterator End() { return Iterator(nullptr); } const_Iterator End() const //const迭代器 { return Iterator(nullptr); }

Myset.hset类里要封装一下。

代码语言:javascript

AI代码解释

public: typedef typename RBTree<K, K, Set_Key_Of_T>::Iterator iterator; typedef typename RBTree<K, K, Set_Key_Of_T>::const_Iterator const_iterator; iterator begin() { return _t.Begin(); } iterator end() { return _t.End(); } const_iterator begin() const //const迭代器 { return _t.Begin(); } const_iterator end() const { return _t.End(); }

Mymap.hmap类里也要封装一下。

代码语言:javascript

AI代码解释

public: typedef typename RBTree<K, pair<K, V>, Map_Key_Of_T>::Iterator iterator; typedef typename RBTree<K, pair<K, V>, Map_Key_Of_T>::const_Iterator const_iterator; iterator begin() { return _t.Begin(); } iterator end() { return _t.End(); } const_iterator begin() const //const迭代器 { return _t.Begin(); } const_iterator end() const { return _t.End(); }
2.2.2 ++和--

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

  • 迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。
  • 迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上找。

如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结点的⽗亲。

如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。

www.dongchedi.com/article/7598662619591098905
www.dongchedi.com/article/7598661663487148606
www.dongchedi.com/article/7598661165073383960
www.dongchedi.com/article/7598661590610706968
www.dongchedi.com/article/7598659598723203609
www.dongchedi.com/article/7598660331644518974
www.dongchedi.com/article/7598659182870970942
www.dongchedi.com/article/7598659511842849304
www.dongchedi.com/article/7598658577318265369
www.dongchedi.com/article/7598660363760206398
www.dongchedi.com/article/7598659848598962750
www.dongchedi.com/article/7598658231610884632
www.dongchedi.com/article/7598655027276071449
www.dongchedi.com/article/7598656521844949566
www.dongchedi.com/article/7598656663327261246
www.dongchedi.com/article/7598657570517058110
www.dongchedi.com/article/7598656249982550590
www.dongchedi.com/article/7598655008087196222
www.dongchedi.com/article/7598654082278408728
www.dongchedi.com/article/7598654850569667096
www.dongchedi.com/article/7598653079990911513
www.dongchedi.com/article/7598653503171052094
www.dongchedi.com/article/7598653873355948569
www.dongchedi.com/article/7598651016690975257
www.dongchedi.com/article/7598653584766632510
www.dongchedi.com/article/7598650859257397785
www.dongchedi.com/article/7598650344322925081

http://www.jsqmd.com/news/291969/

相关文章:

  • Cursor功能优化指南:理解限制机制与合规使用方案
  • 模型微调前准备:DeepSeek-R1作为基座模型的适配性分析
  • 如何清除重新来?fft npainting lama重置按钮使用方法
  • 智谱开源Glyph体验分享:长文本变图像处理新思路
  • YOLO11参数详解:train.py关键配置解读
  • Llama3-8B省钱部署方案:单卡3060实现高性能推理案例
  • 工业自动化中RS485和RS232通信协议选型指南:全面讲解
  • 汽车电子中I2C中断TC3配置:系统学习与实践指南
  • 如何突破Cursor功能限制:专业级解决方案全解析
  • 实测对比:传统方法 vs fft npainting lama修复效果差异
  • YOLO26低成本部署方案:中小企业也能轻松上手的实战指南
  • NewBie-image-Exp0.1影视预研案例:角色概念图自动化生成实战
  • STM32低功耗应用中I2C读写EEPROM代码优化技巧
  • Qwen3-0.6B API调用超时?网络配置优化实战指南
  • ESP32教程:使用Arduino IDE实现蓝牙通信实战案例
  • 低成本高效率:自建AI手机助理详细教程
  • 2026年AI图像生成入门必看:Qwen开源模型+ComfyUI镜像实战
  • CAPL脚本中定时器在CAN测试中的使用:全面讲解
  • Sambert开发避坑指南:常见报错及解决方案汇总
  • GLM-Edge-V-5B:5B轻量模型让边缘设备秒懂图文!
  • eide入门必看:新手快速上手开发环境搭建指南
  • Cute_Animal_For_Kids_Qwen_Image避坑指南:常见报错与解决方案
  • 麦橘超然显存不足?float8量化部署案例让低显存设备流畅运行
  • 教育资源获取新方式:tchMaterial-parser工具使用指南
  • 手把手教你跑通Qwen-Image-Layered,无需GPU也能上手
  • 识别结果能复制吗?Seaco Paraformer导出技巧揭秘
  • fft npainting lama快捷键大全:Ctrl+V粘贴与撤销技巧
  • GPT-OSS-20B高可用部署:双卡容错机制配置
  • NewBie-image-Exp0.1企业应用案例:自动化动漫素材生成部署流程
  • 通义千问3-14B完整部署:Windows+WSL环境实操手册